<commit_before>use std::collections::HashMap;
use super::{ProbeError,Result,calculate_time_difference};

pub type Interfaces = HashMap<String, NetworkTraffic>;

/// Measurement of network traffic at a certain time.
#[derive(Debug,PartialEq)]
pub struct NetworkTrafficMeasurement {
    pub precise_time_ns: u64,
    pub interfaces: Interfaces
}

impl NetworkTrafficMeasurement {
    /// Calculate the network traffic per minute based on this measurement and a measurement in the
    /// future. It is advisable to make the next measurement roughly a minute from this one for the
    /// most reliable result.
    pub fn calculate_per_minute(&self, next_measurement: &NetworkTrafficMeasurement) -> Result<NetworkTrafficPerMinute> {
        let time_difference = try!(calculate_time_difference(self.precise_time_ns, next_measurement.precise_time_ns));

        let mut interfaces = Interfaces::new();

        for (name, traffic) in self.interfaces.iter() {
            let next_traffic = match next_measurement.interfaces.get(name) {
                Some(interface) => interface,
                None => return Err(ProbeError::UnexpectedContent(format!("{} is not present in the next measurement", name)))
            };
            interfaces.insert(
                name.to_string(),
                NetworkTraffic {
                    received: try!(super::time_adjusted("received", next_traffic.received, traffic.received, time_difference)),
                    transmitted: try!(super::time_adjusted("transmitted", next_traffic.transmitted, traffic.transmitted, time_difference))
                }
            );
        }

        Ok(NetworkTrafficPerMinute {
            interfaces: interfaces
        })
    }
}

/// Network traffic in kilobytes.
#[derive(Debug,PartialEq)]
pub struct NetworkTraffic {
    pub received: u64,
    pub transmitted: u64
}

/// Network traffic for a certain minute, calculated based on two measurements.
#[derive(Debug,PartialEq)]
pub struct NetworkTrafficPerMinute {
    pub interfaces: Interfaces
}

#[cfg(target_os = "linux")]
pub fn read() -> Result<NetworkTrafficMeasurement> {
    os::read()
}

#[cfg(target_os = "linux")]
mod os {
    use std::io::BufRead;
    use std::path::Path;
    use time;

    use super::{NetworkTraffic,Interfaces,NetworkTrafficMeasurement};
    use super::super::{Result,file_to_buf_reader,parse_u64};
    use error::ProbeError;

    #[inline]
    pub fn read() -> Result<NetworkTrafficMeasurement> {
        read_and_parse_network(&Path::new("/proc/net/dev"))
    }

    #[inline]
    pub fn read_and_parse_network(path: &Path) -> Result<NetworkTrafficMeasurement> {
        let reader = try!(file_to_buf_reader(path));
        let precise_time_ns = time::precise_time_ns();

        let lines: Vec<String> = try!(reader.lines().collect());
        let positions = try!(get_positions(lines[1].as_ref()));

        let mut interfaces = Interfaces::new();
        for line in &lines[2..] {
            let segments: Vec<&str> = line.split_whitespace().collect();
            let name = segments[0].trim_matches(':').to_owned();

            if segments.len() < positions.transmit_bytes {
                return Err(ProbeError::UnexpectedContent(
                    format!(
                        "Expected at least {} items, had {} for '{}'",
                        positions.transmit_bytes,
                        segments.len(),
                        name
                    )
                ))
            }

            let traffic = NetworkTraffic {
                received: try!(parse_u64(segments[positions.receive_bytes])),
                transmitted: try!(parse_u64(segments[positions.transmit_bytes]))
            };

            interfaces.insert(name, traffic);
        }

        Ok(NetworkTrafficMeasurement {
            precise_time_ns: precise_time_ns,
            interfaces: interfaces
        })
    }

    #[derive(Debug,PartialEq)]
    pub struct Positions {
        pub receive_bytes: usize,
        pub transmit_bytes: usize
    }

    /// Get the positions of the `bytes` field for both the receive and transmit segment
    #[inline]
    pub fn get_positions(header_line: &str) -> Result<Positions> {
        let groups: Vec<&str> = header_line.split("|").collect();
        if groups.len() != 3 {
            return Err(ProbeError::UnexpectedContent("Incorrect number of segments".to_owned()))
        }
        let receive_group: Vec<&str> = groups[1].split_whitespace().collect();
        let transmit_group: Vec<&str> = groups[2].split_whitespace().collect();

        let receive_pos = try!(receive_group.iter().position(|&e| e == "bytes").ok_or(ProbeError::UnexpectedContent("bytes field not found for receive".to_string())));
        let transmit_pos = try!(transmit_group.iter().position(|&e| e == "bytes").ok_or(ProbeError::UnexpectedContent("bytes field not found for transmit".to_string())));

        // We start with 1 here because the first (name) segment always has one column.
        Ok(Positions {
            receive_bytes: 1 + receive_pos,
            transmit_bytes: 1 + receive_group.len() + transmit_pos
        })
    }
}

#[cfg(test)]
mod tests {
    use std::path::Path;
    use time;
    use super::super::ProbeError;
    use super::{Interfaces,NetworkTraffic,NetworkTrafficMeasurement};

    #[test]
    fn test_read_network() {
        assert!(super::read().is_ok());
        assert!(!super::read().unwrap().interfaces.is_empty());
    }

    #[test]
    fn test_read_and_parse_network() {
        let path = Path::new("fixtures/linux/network/proc_net_dev");
        let measurement = super::os::read_and_parse_network(&path).unwrap();
        assert!(measurement.precise_time_ns < time::precise_time_ns());

        let interfaces = measurement.interfaces;
        assert_eq!(3, interfaces.len());

        let lo = interfaces.get("lo").unwrap();
        assert_eq!(560, lo.received);
        assert_eq!(560, lo.transmitted);

        let eth0 = interfaces.get("eth0").unwrap();
        assert_eq!(254972, eth0.received);
        assert_eq!(72219, eth0.transmitted);

        let eth1 = interfaces.get("eth1").unwrap();
        assert_eq!(354972, eth1.received);
        assert_eq!(82219, eth1.transmitted);
    }

    #[test]
    fn test_read_and_parse_network_wrong_path() {
        let path = Path::new("/nonsense");
        match super::os::read_and_parse_network(&path) {
            Err(ProbeError::IO(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_read_and_parse_network_incomplete() {
        let path = Path::new("fixtures/linux/network/proc_net_dev_incomplete");
        match super::os::read_and_parse_network(&path) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_read_and_parse_network_garbage() {
        let path = Path::new("fixtures/linux/network/proc_net_dev_garbage");
        match super::os::read_and_parse_network(&path) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_get_positions() {
      let line = "face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed";

      assert_eq!(
          super::os::Positions {
            receive_bytes: 1,
            transmit_bytes: 9
          },
          super::os::get_positions(line).unwrap()
      )
    }

    #[test]
    fn test_get_positions_fields_missing() {
        let line = "face";

        match super::os::get_positions(line) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_get_positions_bytes_field_missing() {
        let line = "face |bates    packets errs drop fifo frame compressed multicast|bates    packets errs drop fifo colls carrier compressed";

        match super::os::get_positions(line) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_calculate_per_minute_full_minute() {
        let mut interfaces1 = Interfaces::new();
        interfaces1.insert("eth0".to_string(), NetworkTraffic{received: 1000, transmitted: 1000});
        interfaces1.insert("eth1".to_string(), NetworkTraffic{received: 2000, transmitted: 3000});
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: interfaces1
        };

        let mut interfaces2 = Interfaces::new();
        interfaces2.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 2600});
        interfaces2.insert("eth1".to_string(), NetworkTraffic{received: 3000, transmitted: 4600});
        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 120_000_000_000,
            interfaces: interfaces2
        };

        let per_minute = measurement1.calculate_per_minute(&measurement2).unwrap();
        assert_eq!(2, per_minute.interfaces.len());

        let eth0 = per_minute.interfaces.get("eth0").unwrap();
        assert_eq!(1000, eth0.received);
        assert_eq!(1600, eth0.transmitted);

        let eth1 = per_minute.interfaces.get("eth0").unwrap();
        assert_eq!(1000, eth1.received);
        assert_eq!(1600, eth1.transmitted);
    }

    #[test]
    fn test_calculate_per_minute_partial_minute() {
        let mut interfaces1 = Interfaces::new();
        interfaces1.insert("eth0".to_string(), NetworkTraffic{received: 1000, transmitted: 1000});
        interfaces1.insert("eth1".to_string(), NetworkTraffic{received: 2000, transmitted: 3000});
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: interfaces1
        };

        let mut interfaces2 = Interfaces::new();
        interfaces2.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 2600});
        interfaces2.insert("eth1".to_string(), NetworkTraffic{received: 3000, transmitted: 4600});
        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 90_000_000_000,
            interfaces: interfaces2
        };

        let per_minute = measurement1.calculate_per_minute(&measurement2).unwrap();
        assert_eq!(2, per_minute.interfaces.len());

        let eth0 = per_minute.interfaces.get("eth0").unwrap();
        assert_eq!(500, eth0.received);
        assert_eq!(800, eth0.transmitted);

        let eth1 = per_minute.interfaces.get("eth0").unwrap();
        assert_eq!(500, eth1.received);
        assert_eq!(800, eth1.transmitted);
    }

    #[test]
    fn test_calculate_per_minute_wrong_times() {
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 90_000_000_000,
            interfaces: Interfaces::new()
        };

        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: Interfaces::new()
        };

        match measurement1.calculate_per_minute(&measurement2) {
            Err(ProbeError::InvalidInput(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_calculate_per_minute_values_lower() {
        let mut interfaces1 = Interfaces::new();
        interfaces1.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 3000});
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: interfaces1
        };

        let mut interfaces2 = Interfaces::new();
        interfaces2.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 2600});
        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 120_000_000_000,
            interfaces: interfaces2
        };

        match measurement1.calculate_per_minute(&measurement2) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_calculate_per_minute_different_interfaces() {
        let mut interfaces1 = Interfaces::new();
        interfaces1.insert("eth1".to_string(), NetworkTraffic{received: 2000, transmitted: 3000});
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: interfaces1
        };

        let mut interfaces2 = Interfaces::new();
        interfaces2.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 2600});
        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 120_000_000_000,
            interfaces: interfaces2
        };

        match measurement1.calculate_per_minute(&measurement2) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }
}
<commit_msg>Fix incorrect scale in doc for NetworkTraffic<commit_after>use std::collections::HashMap;
use super::{ProbeError,Result,calculate_time_difference};

pub type Interfaces = HashMap<String, NetworkTraffic>;

/// Measurement of network traffic at a certain time.
#[derive(Debug,PartialEq)]
pub struct NetworkTrafficMeasurement {
    pub precise_time_ns: u64,
    pub interfaces: Interfaces
}

impl NetworkTrafficMeasurement {
    /// Calculate the network traffic per minute based on this measurement and a measurement in the
    /// future. It is advisable to make the next measurement roughly a minute from this one for the
    /// most reliable result.
    pub fn calculate_per_minute(&self, next_measurement: &NetworkTrafficMeasurement) -> Result<NetworkTrafficPerMinute> {
        let time_difference = try!(calculate_time_difference(self.precise_time_ns, next_measurement.precise_time_ns));

        let mut interfaces = Interfaces::new();

        for (name, traffic) in self.interfaces.iter() {
            let next_traffic = match next_measurement.interfaces.get(name) {
                Some(interface) => interface,
                None => return Err(ProbeError::UnexpectedContent(format!("{} is not present in the next measurement", name)))
            };
            interfaces.insert(
                name.to_string(),
                NetworkTraffic {
                    received: try!(super::time_adjusted("received", next_traffic.received, traffic.received, time_difference)),
                    transmitted: try!(super::time_adjusted("transmitted", next_traffic.transmitted, traffic.transmitted, time_difference))
                }
            );
        }

        Ok(NetworkTrafficPerMinute {
            interfaces: interfaces
        })
    }
}

/// Network traffic in bytes.
#[derive(Debug,PartialEq)]
pub struct NetworkTraffic {
    pub received: u64,
    pub transmitted: u64
}

/// Network traffic for a certain minute, calculated based on two measurements.
#[derive(Debug,PartialEq)]
pub struct NetworkTrafficPerMinute {
    pub interfaces: Interfaces
}

#[cfg(target_os = "linux")]
pub fn read() -> Result<NetworkTrafficMeasurement> {
    os::read()
}

#[cfg(target_os = "linux")]
mod os {
    use std::io::BufRead;
    use std::path::Path;
    use time;

    use super::{NetworkTraffic,Interfaces,NetworkTrafficMeasurement};
    use super::super::{Result,file_to_buf_reader,parse_u64};
    use error::ProbeError;

    #[inline]
    pub fn read() -> Result<NetworkTrafficMeasurement> {
        read_and_parse_network(&Path::new("/proc/net/dev"))
    }

    #[inline]
    pub fn read_and_parse_network(path: &Path) -> Result<NetworkTrafficMeasurement> {
        let reader = try!(file_to_buf_reader(path));
        let precise_time_ns = time::precise_time_ns();

        let lines: Vec<String> = try!(reader.lines().collect());
        let positions = try!(get_positions(lines[1].as_ref()));

        let mut interfaces = Interfaces::new();
        for line in &lines[2..] {
            let segments: Vec<&str> = line.split_whitespace().collect();
            let name = segments[0].trim_matches(':').to_owned();

            if segments.len() < positions.transmit_bytes {
                return Err(ProbeError::UnexpectedContent(
                    format!(
                        "Expected at least {} items, had {} for '{}'",
                        positions.transmit_bytes,
                        segments.len(),
                        name
                    )
                ))
            }

            let traffic = NetworkTraffic {
                received: try!(parse_u64(segments[positions.receive_bytes])),
                transmitted: try!(parse_u64(segments[positions.transmit_bytes]))
            };

            interfaces.insert(name, traffic);
        }

        Ok(NetworkTrafficMeasurement {
            precise_time_ns: precise_time_ns,
            interfaces: interfaces
        })
    }

    #[derive(Debug,PartialEq)]
    pub struct Positions {
        pub receive_bytes: usize,
        pub transmit_bytes: usize
    }

    /// Get the positions of the `bytes` field for both the receive and transmit segment
    #[inline]
    pub fn get_positions(header_line: &str) -> Result<Positions> {
        let groups: Vec<&str> = header_line.split("|").collect();
        if groups.len() != 3 {
            return Err(ProbeError::UnexpectedContent("Incorrect number of segments".to_owned()))
        }
        let receive_group: Vec<&str> = groups[1].split_whitespace().collect();
        let transmit_group: Vec<&str> = groups[2].split_whitespace().collect();

        let receive_pos = try!(receive_group.iter().position(|&e| e == "bytes").ok_or(ProbeError::UnexpectedContent("bytes field not found for receive".to_string())));
        let transmit_pos = try!(transmit_group.iter().position(|&e| e == "bytes").ok_or(ProbeError::UnexpectedContent("bytes field not found for transmit".to_string())));

        // We start with 1 here because the first (name) segment always has one column.
        Ok(Positions {
            receive_bytes: 1 + receive_pos,
            transmit_bytes: 1 + receive_group.len() + transmit_pos
        })
    }
}

#[cfg(test)]
mod tests {
    use std::path::Path;
    use time;
    use super::super::ProbeError;
    use super::{Interfaces,NetworkTraffic,NetworkTrafficMeasurement};

    #[test]
    fn test_read_network() {
        assert!(super::read().is_ok());
        assert!(!super::read().unwrap().interfaces.is_empty());
    }

    #[test]
    fn test_read_and_parse_network() {
        let path = Path::new("fixtures/linux/network/proc_net_dev");
        let measurement = super::os::read_and_parse_network(&path).unwrap();
        assert!(measurement.precise_time_ns < time::precise_time_ns());

        let interfaces = measurement.interfaces;
        assert_eq!(3, interfaces.len());

        let lo = interfaces.get("lo").unwrap();
        assert_eq!(560, lo.received);
        assert_eq!(560, lo.transmitted);

        let eth0 = interfaces.get("eth0").unwrap();
        assert_eq!(254972, eth0.received);
        assert_eq!(72219, eth0.transmitted);

        let eth1 = interfaces.get("eth1").unwrap();
        assert_eq!(354972, eth1.received);
        assert_eq!(82219, eth1.transmitted);
    }

    #[test]
    fn test_read_and_parse_network_wrong_path() {
        let path = Path::new("/nonsense");
        match super::os::read_and_parse_network(&path) {
            Err(ProbeError::IO(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_read_and_parse_network_incomplete() {
        let path = Path::new("fixtures/linux/network/proc_net_dev_incomplete");
        match super::os::read_and_parse_network(&path) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_read_and_parse_network_garbage() {
        let path = Path::new("fixtures/linux/network/proc_net_dev_garbage");
        match super::os::read_and_parse_network(&path) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_get_positions() {
      let line = "face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed";

      assert_eq!(
          super::os::Positions {
            receive_bytes: 1,
            transmit_bytes: 9
          },
          super::os::get_positions(line).unwrap()
      )
    }

    #[test]
    fn test_get_positions_fields_missing() {
        let line = "face";

        match super::os::get_positions(line) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_get_positions_bytes_field_missing() {
        let line = "face |bates    packets errs drop fifo frame compressed multicast|bates    packets errs drop fifo colls carrier compressed";

        match super::os::get_positions(line) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_calculate_per_minute_full_minute() {
        let mut interfaces1 = Interfaces::new();
        interfaces1.insert("eth0".to_string(), NetworkTraffic{received: 1000, transmitted: 1000});
        interfaces1.insert("eth1".to_string(), NetworkTraffic{received: 2000, transmitted: 3000});
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: interfaces1
        };

        let mut interfaces2 = Interfaces::new();
        interfaces2.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 2600});
        interfaces2.insert("eth1".to_string(), NetworkTraffic{received: 3000, transmitted: 4600});
        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 120_000_000_000,
            interfaces: interfaces2
        };

        let per_minute = measurement1.calculate_per_minute(&measurement2).unwrap();
        assert_eq!(2, per_minute.interfaces.len());

        let eth0 = per_minute.interfaces.get("eth0").unwrap();
        assert_eq!(1000, eth0.received);
        assert_eq!(1600, eth0.transmitted);

        let eth1 = per_minute.interfaces.get("eth0").unwrap();
        assert_eq!(1000, eth1.received);
        assert_eq!(1600, eth1.transmitted);
    }

    #[test]
    fn test_calculate_per_minute_partial_minute() {
        let mut interfaces1 = Interfaces::new();
        interfaces1.insert("eth0".to_string(), NetworkTraffic{received: 1000, transmitted: 1000});
        interfaces1.insert("eth1".to_string(), NetworkTraffic{received: 2000, transmitted: 3000});
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: interfaces1
        };

        let mut interfaces2 = Interfaces::new();
        interfaces2.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 2600});
        interfaces2.insert("eth1".to_string(), NetworkTraffic{received: 3000, transmitted: 4600});
        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 90_000_000_000,
            interfaces: interfaces2
        };

        let per_minute = measurement1.calculate_per_minute(&measurement2).unwrap();
        assert_eq!(2, per_minute.interfaces.len());

        let eth0 = per_minute.interfaces.get("eth0").unwrap();
        assert_eq!(500, eth0.received);
        assert_eq!(800, eth0.transmitted);

        let eth1 = per_minute.interfaces.get("eth0").unwrap();
        assert_eq!(500, eth1.received);
        assert_eq!(800, eth1.transmitted);
    }

    #[test]
    fn test_calculate_per_minute_wrong_times() {
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 90_000_000_000,
            interfaces: Interfaces::new()
        };

        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: Interfaces::new()
        };

        match measurement1.calculate_per_minute(&measurement2) {
            Err(ProbeError::InvalidInput(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_calculate_per_minute_values_lower() {
        let mut interfaces1 = Interfaces::new();
        interfaces1.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 3000});
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: interfaces1
        };

        let mut interfaces2 = Interfaces::new();
        interfaces2.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 2600});
        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 120_000_000_000,
            interfaces: interfaces2
        };

        match measurement1.calculate_per_minute(&measurement2) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }

    #[test]
    fn test_calculate_per_minute_different_interfaces() {
        let mut interfaces1 = Interfaces::new();
        interfaces1.insert("eth1".to_string(), NetworkTraffic{received: 2000, transmitted: 3000});
        let measurement1 = NetworkTrafficMeasurement{
            precise_time_ns: 60_000_000_000,
            interfaces: interfaces1
        };

        let mut interfaces2 = Interfaces::new();
        interfaces2.insert("eth0".to_string(), NetworkTraffic{received: 2000, transmitted: 2600});
        let measurement2 = NetworkTrafficMeasurement{
            precise_time_ns: 120_000_000_000,
            interfaces: interfaces2
        };

        match measurement1.calculate_per_minute(&measurement2) {
            Err(ProbeError::UnexpectedContent(_)) => (),
            r => panic!("Unexpected result: {:?}", r)
        }
    }
}
<|endoftext|>


<commit_before>use std::convert::AsRef;
use std::fs::{walk_dir, PathExt};
use std::path::{Path, PathBuf};
use std::collections::BTreeMap;

use cronparse::{CrontabFile, CrontabFileError, CrontabFileErrorKind};
use cronparse::crontab::{EnvVarEntry, CrontabEntry, ToCrontabEntry};
use log::{Logger, LogLevel};

pub fn process_crontab_dir<T: ToCrontabEntry>(dir: &str, logger: &mut Logger) {
    let files = walk_dir(dir).and_then(|fs| fs.map(|r| r.map(|p| p.path()))
                                       .filter(|r| r.as_ref().map(|p| p.is_file()).unwrap_or(true))
                                       .collect::<Result<Vec<PathBuf>, _>>());
    match files {
        Err(err) => log!(logger, LogLevel::Error, "error processing directory {}: {}", dir, err),
        Ok(files) => for file in files {
            process_crontab_file::<T, _>(file, logger as &mut Logger);
        }
    }
}

pub fn process_crontab_file<T: ToCrontabEntry, P: AsRef<Path>>(path: P, logger: &mut Logger) {
    CrontabFile::<T>::new(path.as_ref()).map(|crontab| {
        let mut env = BTreeMap::new();
        for entry in crontab {
            match entry {
                Ok(CrontabEntry::EnvVar(EnvVarEntry(name, value))) => { env.insert(name, value); },
                Ok(data) => generate_systemd_units(path.as_ref(), data, &env),
                Err(err @ CrontabFileError { kind: CrontabFileErrorKind::Io(_), .. }) => log!(logger, LogLevel::Warning, "error accessing file {}: {}", path.as_ref().display(), err),
                Err(err @ CrontabFileError { kind: CrontabFileErrorKind::Parse(_), .. }) => log!(logger, LogLevel::Warning, "skipping file {} due to parsing error: {}", path.as_ref().display(), err),
            }
        }
    }).unwrap_or_else(|err| {
        log!(logger, LogLevel::Error, "error parsing file {}: {}", path.as_ref().display(), err);
    });
}

fn generate_systemd_units(path: &Path, entry: CrontabEntry, env: &BTreeMap<String, String>) {
    println!("{} => {:?}, {:?}", path.display(), entry, env);
}

<commit_msg>pass logger to generate<commit_after>use std::convert::AsRef;
use std::fs::{walk_dir, PathExt};
use std::path::{Path, PathBuf};
use std::collections::BTreeMap;

use cronparse::{CrontabFile, CrontabFileError, CrontabFileErrorKind};
use cronparse::crontab::{EnvVarEntry, CrontabEntry, ToCrontabEntry};
use log::{Logger, LogLevel};

pub fn process_crontab_dir<T: ToCrontabEntry>(dir: &str, logger: &mut Logger) {
    let files = walk_dir(dir).and_then(|fs| fs.map(|r| r.map(|p| p.path()))
                                       .filter(|r| r.as_ref().map(|p| p.is_file()).unwrap_or(true))
                                       .collect::<Result<Vec<PathBuf>, _>>());
    match files {
        Err(err) => log!(logger, LogLevel::Error, "error processing directory {}: {}", dir, err),
        Ok(files) => for file in files {
            process_crontab_file::<T, _>(file, logger as &mut Logger);
        }
    }
}

pub fn process_crontab_file<T: ToCrontabEntry, P: AsRef<Path>>(path: P, logger: &mut Logger) {
    CrontabFile::<T>::new(path.as_ref()).map(|crontab| {
        let mut env = BTreeMap::new();
        for entry in crontab {
            match entry {
                Ok(CrontabEntry::EnvVar(EnvVarEntry(name, value))) => { env.insert(name, value); },
                Ok(data) => generate_systemd_units(path.as_ref(), data, &env, logger),
                Err(err @ CrontabFileError { kind: CrontabFileErrorKind::Io(_), .. }) => log!(logger, LogLevel::Warning, "error accessing file {}: {}", path.as_ref().display(), err),
                Err(err @ CrontabFileError { kind: CrontabFileErrorKind::Parse(_), .. }) => log!(logger, LogLevel::Warning, "skipping file {} due to parsing error: {}", path.as_ref().display(), err),
            }
        }
    }).unwrap_or_else(|err| {
        log!(logger, LogLevel::Error, "error parsing file {}: {}", path.as_ref().display(), err);
    });
}

fn generate_systemd_units(path: &Path, entry: CrontabEntry, env: &BTreeMap<String, String>, logger: &mut Logger) {
    log!(logger, LogLevel::Info, "{} => {:?}, {:?}", path.display(), entry, env);
}

<|endoftext|>


<commit_before>use libc;
use std::mem::forget;

use qvariant::*;
use utils::*;
use types::*;
use qmeta::*;

pub struct QObject {
    ptr: DosQObject,
}

extern "C" {

    fn dos_qobject_create(dObjectPointer: *const Box<QObjectMacro>,
                          metaObject: DosQMetaObject,
                          dObjectCallback: DObjectCallback)
                          -> DosQObject;
}

/// Called when a slot should be executed
/// @param self The pointer to the QObject in the binded language
/// @param slotName The slotName as DosQVariant. It should not be deleted
/// @param argc The number of arguments
/// @param argv An array of DosQVariant pointers. They should not be deleted
type DObjectCallback = extern "C" fn(*const Box<QObjectMacro>,
                                     DosQVariant,
                                     i32,
                                     *const DosQVariant);

impl QObject {
    pub fn new(obj: Box<QObjectMacro>) -> QObject {
        unsafe {
            extern "C" fn callback(obj: *const Box<QObjectMacro>,
                                   qvar: DosQVariant,
                                   argc: i32,
                                   argv: *const DosQVariant) {
                println!("CALLBACK HERE");
            }
            let meta = QMeta::new_for_qobject(obj.qmeta());

            let res = QObject {
                ptr: dos_qobject_create(&obj as *const Box<QObjectMacro>,
                                        get_dos_qmeta(&meta),
                                        callback),
            };
            forget(obj);
            forget(meta);
            res
        }
    }
}
<commit_msg>Added some definitions for future work on connecting signals and slots<commit_after>use libc;
use std::mem::forget;

use qvariant::*;
use utils::*;
use types::*;
use qmeta::*;

pub struct QObject {
    ptr: DosQObject,
}

extern "C" {

    fn dos_qobject_create(dObjectPointer: *const Box<QObjectMacro>,
                          metaObject: DosQMetaObject,
                          dObjectCallback: DObjectCallback)
                          -> DosQObject;
    fn dos_qobject_signal_connect(senderVPtr: DosQObject,
                                  signal: *const libc::c_char,
                                  receiverVPtr: DosQObject,
                                  method: *const libc::c_char,
                                  qtype: i32)
                                  -> bool;
}

macro_rules! QT_connect {
    ($sender:ident, $signal:ident, $receiver:ident, $method:tt) => {{
        unimplemented!()
    }}
}

/// This enum describes the types of connection that can be used between signals and slots.
/// In particular, it determines whether a particular signal is delivered to a slot immediately or queued for delivery at a later time.
pub enum QtConnectionType {
    /// **(Default)** If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.
    AutoConnection = 0,
    /// The slot is invoked immediately when the signal is emitted. The slot is executed in the signalling thread.
    DirectConnection = 1,
    /// The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.
    QueuedConnection = 2,
    /// Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.
    BlockingQueuedConnection = 3,
    /// This is a flag that can be combined with any one of the above connection types, using a bitwise OR. When Qt::UniqueConnection is set, QObject::connect() will fail if the connection already exists (i.e. if the same signal is already connected to the same slot for the same pair of objects). This flag was introduced in Qt 4.6.
    UniqueConnection = 0x80,
}

/// Called when a slot should be executed
/// @param self The pointer to the QObject in the binded language
/// @param slotName The slotName as DosQVariant. It should not be deleted
/// @param argc The number of arguments
/// @param argv An array of DosQVariant pointers. They should not be deleted
type DObjectCallback = extern "C" fn(*const Box<QObjectMacro>,
                                     DosQVariant,
                                     i32,
                                     *const DosQVariant);

impl QObject {
    pub fn new(obj: Box<QObjectMacro>) -> QObject {
        unsafe {
            extern "C" fn callback(obj: *const Box<QObjectMacro>,
                                   qvar: DosQVariant,
                                   argc: i32,
                                   argv: *const DosQVariant) {
                println!("CALLBACK HERE");
            }
            let meta = QMeta::new_for_qobject(obj.qmeta());

            let res = QObject {
                ptr: dos_qobject_create(&obj as *const Box<QObjectMacro>,
                                        get_dos_qmeta(&meta),
                                        callback),
            };
            forget(obj);
            forget(meta);
            res
        }
    }
}
<|endoftext|>


<commit_before>//! Module with helpers for dealing with RFC 5322

use super::header::{Header, HeaderMap};
use super::rfc2047::decode_rfc2047;

trait Rfc5322Character {
    /// Is considered a special character by RFC 5322 Section 3.2.3
    fn is_special(&self) -> bool;
    /// Is considered to be a VCHAR by RFC 5234 Appendix B.1
    fn is_vchar(&self) -> bool;
    /// Is considered to be field text as defined by RFC 5322 Section 3.6.8
    fn is_ftext(&self) -> bool;

    fn is_atext(&self) -> bool {
        self.is_vchar() && !self.is_special()
    }
}

impl Rfc5322Character for char {
    fn is_ftext(&self) -> bool {
        match *self {
            '!'...'9' | ';'...'~' => true,
            _ => false,
        }
    }

    fn is_special(&self) -> bool {
        match *self {
            '(' | ')' | '<' | '>' | '[' | ']' | ':' | ';' | '@' | '\\' | ',' | '.' | '\"' | ' ' => true,
            _ => false
        }
    }

    fn is_vchar(&self) -> bool {
        match *self {
            '!'...'~' => true,
            _ => false,
        }
    }
}

/// RFC 5322 base parser for parsing
///  `atom`, `dot-atom`, `quoted-string`, `phrase`
///
/// This should prove useful for parsing other things that appear in RFC 5322,
/// as most are based off these core items.
///
/// It also implements a stack for tracking the position.
/// This allows the simple implementation of backtracking, by pushing the position
/// before a test and popping it if the test should fail.
pub struct Rfc5322Parser<'s> {
    s: &'s str,
    pos: uint,
    pos_stack: Vec<uint>,
}

impl<'s> Rfc5322Parser<'s> {
    /// Make a new parser, initialized with the given string.
    pub fn new(source: &'s str) -> Rfc5322Parser<'s> {
        Rfc5322Parser {
            s: source,
            pos: 0u,
            pos_stack: Vec::new(),
        }
    }

    /// Push the current position onto the stack.
    pub fn push_position(&mut self) {
        self.pos_stack.push(self.pos);
    }

    /// Move the position back to the last entry pushed
    pub fn pop_position(&mut self) {
        match self.pos_stack.pop() {
            Some(pos) => { self.pos = pos; },
            None => fail!("Popped position stack too far"),
        }
    }

    /// Consume a message from the input.
    ///
    /// Returns as a map of the headers and the body text.
    ///
    /// A message is defined as:
    ///
    /// `fields = *field
    /// body = text
    /// message = fields CRLF body`
    pub fn consume_message(&mut self) -> Option<(HeaderMap, String)> {
        let mut headers = HeaderMap::new();
        while !self.eof() {
            let header = self.consume_header();
            if header.is_some() {
                headers.insert(header.unwrap());
            }

            // Check end of headers as marked by CRLF
            if !self.eof() && self.peek() == '\r' {
                assert_eq!(self.consume_char(), '\r');
                assert_eq!(self.consume_char(), '\n');
                break;
            }
        }

        // Whatever remains is the body
        let body = self.s.slice_chars(self.pos, self.s.len()).to_string();
        self.pos = self.s.len();

        Some((headers, body))
    }

    /// Consume a header from the input.
    ///
    /// A header is defined as:
    ///
    /// `ftext = "!".."9" / ";".."~"
    /// field-name = 1*ftext
    /// field = field-name *LWSP ":" unstructured
    pub fn consume_header(&mut self) -> Option<Header> {
        // Parse field-name
        let field_name = self.consume_while(|c| { c.is_ftext() });
        self.consume_linear_whitespace();
        if field_name.len() == 0 || self.eof() || self.peek() != ':' {
            // Fail to parse if we didn't see a field, we're at the end of input
            // or we haven't just seen a ":"
            None
        } else {
            // Consume the ":" and any leading whitespace
            self.consume_char();
            self.consume_linear_whitespace();
            let field_value = self.consume_unstructured();

            assert!(self.consume_char() == '\r');
            assert!(self.consume_char() == '\n');

            Some(Header::new(field_name, field_value))
        }
    }

    /// Consume an unstructured from the input.
    pub fn consume_unstructured(&mut self) -> String {
        let mut result = String::new();
        while !self.eof() {
            if self.peek() == '\r' {
                // Check for folding whitespace, if it wasn't, then
                // we're done parsing
                if !self.consume_folding_whitespace() {
                    break;
                }
            }

            result.push_str(self.consume_while(|c| {
                c.is_vchar() || c == ' ' || c == '\t'
            }).as_slice())
        }
        result
    }

    /// Consume folding whitespace.
    ///
    /// This is a CRLF followed by one or more whitespace character.
    ///
    /// Returns true if whitespace was consume
    pub fn consume_folding_whitespace(&mut self) -> bool {
        // Remember where we were, in case this isn't folding whitespace
        let current_position = self.pos;
        let is_fws = if !self.eof() && self.consume_char() == '\r' {
            if !self.eof() && self.consume_char() == '\n' {
                match self.consume_char() {
                    ' ' | '\t' => true,
                    _ => false,
                }
            } else {
                false
            }
        } else {
            false
        };

        if is_fws {
            // This was a folding whitespace, so consume all linear whitespace
            self.consume_linear_whitespace();
        } else {
            // Reset back if we didn't see a folding whitespace
            self.pos = current_position;
        }

        is_fws
    }

    /// Consume a phrase from the input.
    ///
    /// A phrase is defined as:
    ///
    /// `word = atom / quoted-string
    /// phrase = 1*word`
    ///
    /// If `allow_dot_atom` is true, then `atom` can be a `dot-atom` in this phrase.
    pub fn consume_phrase(&mut self, allow_dot_atom: bool) -> Option<String> {
        let mut phrase = String::new();

        while !self.eof() {
            self.consume_linear_whitespace();
            let word = if self.peek() == '"' {
                // Word is a quoted string
                self.consume_quoted_string()
            } else if self.peek().is_atext() {
                self.consume_atom(allow_dot_atom)
            } else {
                // If it's not a quoted string, or an atom, it's no longer
                // in a phrase, so stop.
                break
            };

            if word.is_some() {
                // Unwrap word so it lives long enough...
                // XXX: word in this scope is `String`, in the parent scope, is `Option<String>`
                let word = word.unwrap();
                let w_slice = word.as_slice();
                // RFC 2047 encoded words start with =?, end with ?=
                let decoded_word =
                    if w_slice.starts_with("=?") && w_slice.ends_with("?=") {
                        match decode_rfc2047(w_slice) {
                            Some(w) => w,
                            None => w_slice.to_string(),
                        }
                    } else {
                        w_slice.to_string()
                    };

                // Make sure we put a leading space on, if this isn't the first insertion
                if phrase.len() > 0 {
                    phrase.push_str(" ");
                }
                phrase.push_str(decoded_word.as_slice());
            } else {
                return None
            }
        }

        if phrase.len() > 0 {
            Some(phrase)
        } else {
            None
        }
    }

    /// Consume a quoted string from the input
    pub fn consume_quoted_string(&mut self) -> Option<String> {
        if self.peek() != '"' {
            // Fail if we were called wrong
            None
        } else {
            let mut quoted_string = String::new();
            let mut inside_escape = false;
            let mut terminated = false;
            // Consume the leading "
            self.consume_char();
            while !terminated && !self.eof() {
                match self.peek() {
                    '\\' if !inside_escape => {
                        // If we were not already being escaped, consume the
                        // escape character and mark that we're being escaped.
                        self.consume_char();
                        inside_escape = true;
                    },
                    '"' if !inside_escape => {
                        // If this is a DQUOTE and we haven't seen an escape character,
                        // consume it and mark that we should break from the loop
                        self.consume_char();
                        terminated = true;
                    },
                    _ => {
                        // Any old character gets pushed in
                        quoted_string.push(self.consume_char());
                        // Clear any escape character state we have
                        inside_escape = false;
                    },
                }
            }

            if inside_escape || !terminated {
                // Return an error state if we're still expecting a character
                None
            } else {
                Some(quoted_string)
            }
        }
    }

    /// Consume an atom from the input.
    ///
    /// If `allow_dot` is true, then also allow '.' to be considered as an
    /// atext character.
    pub fn consume_atom(&mut self, allow_dot: bool) -> Option<String> {
        if !self.peek().is_atext() {
            None
        } else {
            Some(self.consume_while(|c| {
                c.is_atext() || (allow_dot && c == '.')
            }))
        }
    }

    /// Consume LWSP (Linear whitespace)
    pub fn consume_linear_whitespace(&mut self) {
        self.consume_while(|c| { c == '\t' || c == ' ' });
    }

    /// Consume a single character from the input.
    pub fn consume_char(&mut self) -> char {
        if self.eof() {
            fail!("Consuming beyond end of input");
        }
        let ch_range = self.s.char_range_at(self.pos);
        self.pos = ch_range.next;
        ch_range.ch
    }

    /// Consume a set of characters, each passed to `test` until this function
    /// returns false.
    ///
    /// The position after calling this function will be pointing to the character
    /// which caused a false result from `test`.
    ///
    /// Returns the string of characters that returned true for the test function.
    pub fn consume_while(&mut self, test: |char| -> bool) -> String {
        let mut res = String::new();
        while !self.eof() && test(self.peek()) {
            res.push(self.consume_char())
        }
        res
    }

    /// Peek at the current character.
    ///
    /// Note that this does not do any bounds checking.
    pub fn peek(&self) -> char {
        self.s.char_at(self.pos)
    }

    /// Returns true if we have reached the end of the input.
    #[inline]
    pub fn eof(&self) -> bool {
        self.pos >= self.s.len()
    }

}

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

    struct PhraseTestCase<'s> {
        input: &'s str,
        output: &'s str,
        name: &'s str,
    }

    #[test]
    fn test_consume_phrase() {
        let tests = [
            PhraseTestCase {
                input: "\"test phrase\"", output: "test phrase",
                name: "Simple quoted-string"
            },
            PhraseTestCase {
                input: "\"test \\\"phrase\\\"\"", output: "test \"phrase\"",
                name: "quoted-string with escape character"
            },
            PhraseTestCase {
                input: "\"=?utf-8?q?encoded=20q-string?=\"", output: "encoded q-string",
                name: "Encoded quoted-string"
            },
            PhraseTestCase {
                input: "atom test", output: "atom test",
                name: "Collection of atoms"
            },
            PhraseTestCase {
                input: "=?utf-8?q?encoded=20atom?=", output: "encoded atom",
                name: "Encoded atom"
            },
            PhraseTestCase {
                input: "Mix of atoms \"and quoted strings\"", output: "Mix of atoms and quoted strings",
                name: "Mix of atoms and quoted strings"
            },
            PhraseTestCase {
                input: "=?utf-8?q?encoded=20atoms?= mixed with \"unencoded\" \"=?utf-8?b?YW5kIGVuY29kZWQgcS1zdHJpbmdz?=\"",
                output: "encoded atoms mixed with unencoded and encoded q-strings",
                name: "Mix of atoms, q-strings of differing encodings"
            },
            PhraseTestCase {
                input: "\"John Smith\" <test@example.org>", output: "John Smith",
                name: "Stop consuming phrase at \"special\" character",
            }
        ];

        for t in tests.iter() {
            let mut p = Rfc5322Parser::new(t.input);
            let phrase = p.consume_phrase(false);
            assert!(phrase.is_some(), format!("{} returned Some", t.name));
            let test_name = format!("{} == {} for {}", phrase.clone().unwrap(), t.output, t.name);
            assert!(phrase.unwrap() == t.output.to_string(), test_name);
        }
    }

    struct MessageTestCase<'s> {
        input: &'s str,
        headers: Vec<(&'s str, &'s str)>,
        body: &'s str,
    }

    #[test]
    fn test_consume_message() {
        let tests = vec![
            MessageTestCase {
                input: "From: \"Joe Blogs\" <joe@example.org>\r\n\r\nBody",
                headers: vec![
                    ("From", "\"Joe Blogs\" <joe@example.org>"),
                ],
                body: "Body",
            },
            MessageTestCase {
                input: "From: \"Joe Blogs\" <joe@example.org>\r\n\r\nMultiline\r\nBody",
                headers: vec![
                    ("From", "\"Joe Blogs\" <joe@example.org>"),
                ],
                body: "Multiline\r\nBody",
            },
            MessageTestCase {
                input: "From: \"Joe Blogs\" <joe@example.org>\r\nTo: \"John Doe\" <john@example.org>\r\n\r\nMultiple headers",
                headers: vec![
                    ("From", "\"Joe Blogs\" <joe@example.org>"),
                    ("To", "\"John Doe\" <john@example.org>"),
                ],
                body: "Multiple headers",
            },
            MessageTestCase {
                input: "Folded-Header: Some content that is \r\n\t wrapped with a tab.\r\n\r\nFolding whitespace test",
                headers: vec![
                    ("Folded-Header", "Some content that is wrapped with a tab."),
                ],
                body: "Folding whitespace test",
            },
            MessageTestCase {
                input: "Folded-Header: Some content that is \r\n  wrapped with spaces.\r\n\r\nFolding whitespace test",
                headers: vec![
                    ("Folded-Header", "Some content that is wrapped with spaces."),
                ],
                body: "Folding whitespace test",
            },
        ];

        for test in tests.iter() {
            let mut p = Rfc5322Parser::new(test.input);
            let message = p.consume_message();
            match message {
                Some((headers, body)) => {
                    assert_eq!(body, test.body.to_string());
                    for &(header_title, header_value) in test.headers.iter() {
                        let matching_headers = headers.find(&header_title.to_string()).unwrap();
                        assert!(matching_headers.iter().filter(|h| {
                            let val: String = h.get_value().unwrap();
                            val == header_value.to_string()
                        }).count() > 0);
                    }
                },
                None => fail!("Failed to parse message"),
            };
        }
    }
}
<commit_msg>Fix up docstrings for the Parser.<commit_after>//! Module with helpers for dealing with RFC 5322

use super::header::{Header, HeaderMap};
use super::rfc2047::decode_rfc2047;

trait Rfc5322Character {
    /// Is considered a special character by RFC 5322 Section 3.2.3
    fn is_special(&self) -> bool;
    /// Is considered to be a VCHAR by RFC 5234 Appendix B.1
    fn is_vchar(&self) -> bool;
    /// Is considered to be field text as defined by RFC 5322 Section 3.6.8
    fn is_ftext(&self) -> bool;

    fn is_atext(&self) -> bool {
        self.is_vchar() && !self.is_special()
    }
}

impl Rfc5322Character for char {
    fn is_ftext(&self) -> bool {
        match *self {
            '!'...'9' | ';'...'~' => true,
            _ => false,
        }
    }

    fn is_special(&self) -> bool {
        match *self {
            '(' | ')' | '<' | '>' | '[' | ']' | ':' | ';' | '@' | '\\' | ',' | '.' | '\"' | ' ' => true,
            _ => false
        }
    }

    fn is_vchar(&self) -> bool {
        match *self {
            '!'...'~' => true,
            _ => false,
        }
    }
}

/// RFC 5322 base parser for parsing
///  `atom`, `dot-atom`, `quoted-string`, `phrase`, `message`
///
/// This should prove useful for parsing other things that appear in RFC 5322,
/// as most are based off these core items.
///
/// It also implements a stack for tracking the position.
/// This allows the simple implementation of backtracking, by pushing the position
/// before a test and popping it if the test should fail.
pub struct Rfc5322Parser<'s> {
    s: &'s str,
    pos: uint,
    pos_stack: Vec<uint>,
}

impl<'s> Rfc5322Parser<'s> {
    /// Make a new parser, initialized with the given string.
    pub fn new(source: &'s str) -> Rfc5322Parser<'s> {
        Rfc5322Parser {
            s: source,
            pos: 0u,
            pos_stack: Vec::new(),
        }
    }

    /// Push the current position onto the stack.
    pub fn push_position(&mut self) {
        self.pos_stack.push(self.pos);
    }

    /// Move the position back to the last entry pushed
    pub fn pop_position(&mut self) {
        match self.pos_stack.pop() {
            Some(pos) => { self.pos = pos; },
            None => fail!("Popped position stack too far"),
        }
    }

    /// Consume a message from the input.
    ///
    /// Returns as a map of the headers and the body text.
    ///
    /// A message is defined as:
    ///
    /// `fields = *field
    /// body = text
    /// message = fields CRLF body`
    pub fn consume_message(&mut self) -> Option<(HeaderMap, String)> {
        let mut headers = HeaderMap::new();
        while !self.eof() {
            let header = self.consume_header();
            if header.is_some() {
                headers.insert(header.unwrap());
            }

            // Check end of headers as marked by CRLF
            if !self.eof() && self.peek() == '\r' {
                assert_eq!(self.consume_char(), '\r');
                assert_eq!(self.consume_char(), '\n');
                break;
            }
        }

        // Whatever remains is the body
        let body = self.s.slice_chars(self.pos, self.s.len()).to_string();
        self.pos = self.s.len();

        Some((headers, body))
    }

    /// Consume a header from the input.
    ///
    /// A header is defined as:
    ///
    /// `ftext = "!".."9" / ";".."~"
    /// field-name = 1*ftext
    /// field = field-name *LWSP ":" unstructured`
    pub fn consume_header(&mut self) -> Option<Header> {
        // Parse field-name
        let field_name = self.consume_while(|c| { c.is_ftext() });
        self.consume_linear_whitespace();
        if field_name.len() == 0 || self.eof() || self.peek() != ':' {
            // Fail to parse if we didn't see a field, we're at the end of input
            // or we haven't just seen a ":"
            None
        } else {
            // Consume the ":" and any leading whitespace
            self.consume_char();
            self.consume_linear_whitespace();
            let field_value = self.consume_unstructured();

            assert!(self.consume_char() == '\r');
            assert!(self.consume_char() == '\n');

            Some(Header::new(field_name, field_value))
        }
    }

    /// Consume an unstructured from the input.
    pub fn consume_unstructured(&mut self) -> String {
        let mut result = String::new();
        while !self.eof() {
            if self.peek() == '\r' {
                // Check for folding whitespace, if it wasn't, then
                // we're done parsing
                if !self.consume_folding_whitespace() {
                    break;
                }
            }

            result.push_str(self.consume_while(|c| {
                c.is_vchar() || c == ' ' || c == '\t'
            }).as_slice())
        }
        result
    }

    /// Consume folding whitespace.
    ///
    /// This is a CRLF followed by one or more whitespace character.
    ///
    /// Returns true if whitespace was consume
    pub fn consume_folding_whitespace(&mut self) -> bool {
        // Remember where we were, in case this isn't folding whitespace
        let current_position = self.pos;
        let is_fws = if !self.eof() && self.consume_char() == '\r' {
            if !self.eof() && self.consume_char() == '\n' {
                match self.consume_char() {
                    ' ' | '\t' => true,
                    _ => false,
                }
            } else {
                false
            }
        } else {
            false
        };

        if is_fws {
            // This was a folding whitespace, so consume all linear whitespace
            self.consume_linear_whitespace();
        } else {
            // Reset back if we didn't see a folding whitespace
            self.pos = current_position;
        }

        is_fws
    }

    /// Consume a phrase from the input.
    ///
    /// A phrase is defined as:
    ///
    /// `word = atom / quoted-string
    /// phrase = 1*word`
    ///
    /// If `allow_dot_atom` is true, then `atom` can be a `dot-atom` in this phrase.
    pub fn consume_phrase(&mut self, allow_dot_atom: bool) -> Option<String> {
        let mut phrase = String::new();

        while !self.eof() {
            self.consume_linear_whitespace();
            let word = if self.peek() == '"' {
                // Word is a quoted string
                self.consume_quoted_string()
            } else if self.peek().is_atext() {
                self.consume_atom(allow_dot_atom)
            } else {
                // If it's not a quoted string, or an atom, it's no longer
                // in a phrase, so stop.
                break
            };

            if word.is_some() {
                // Unwrap word so it lives long enough...
                // XXX: word in this scope is `String`, in the parent scope, is `Option<String>`
                let word = word.unwrap();
                let w_slice = word.as_slice();
                // RFC 2047 encoded words start with =?, end with ?=
                let decoded_word =
                    if w_slice.starts_with("=?") && w_slice.ends_with("?=") {
                        match decode_rfc2047(w_slice) {
                            Some(w) => w,
                            None => w_slice.to_string(),
                        }
                    } else {
                        w_slice.to_string()
                    };

                // Make sure we put a leading space on, if this isn't the first insertion
                if phrase.len() > 0 {
                    phrase.push_str(" ");
                }
                phrase.push_str(decoded_word.as_slice());
            } else {
                return None
            }
        }

        if phrase.len() > 0 {
            Some(phrase)
        } else {
            None
        }
    }

    /// Consume a quoted string from the input
    pub fn consume_quoted_string(&mut self) -> Option<String> {
        if self.peek() != '"' {
            // Fail if we were called wrong
            None
        } else {
            let mut quoted_string = String::new();
            let mut inside_escape = false;
            let mut terminated = false;
            // Consume the leading "
            self.consume_char();
            while !terminated && !self.eof() {
                match self.peek() {
                    '\\' if !inside_escape => {
                        // If we were not already being escaped, consume the
                        // escape character and mark that we're being escaped.
                        self.consume_char();
                        inside_escape = true;
                    },
                    '"' if !inside_escape => {
                        // If this is a DQUOTE and we haven't seen an escape character,
                        // consume it and mark that we should break from the loop
                        self.consume_char();
                        terminated = true;
                    },
                    _ => {
                        // Any old character gets pushed in
                        quoted_string.push(self.consume_char());
                        // Clear any escape character state we have
                        inside_escape = false;
                    },
                }
            }

            if inside_escape || !terminated {
                // Return an error state if we're still expecting a character
                None
            } else {
                Some(quoted_string)
            }
        }
    }

    /// Consume an atom from the input.
    ///
    /// If `allow_dot` is true, then also allow '.' to be considered as an
    /// atext character.
    pub fn consume_atom(&mut self, allow_dot: bool) -> Option<String> {
        if !self.peek().is_atext() {
            None
        } else {
            Some(self.consume_while(|c| {
                c.is_atext() || (allow_dot && c == '.')
            }))
        }
    }

    /// Consume LWSP (Linear whitespace)
    pub fn consume_linear_whitespace(&mut self) {
        self.consume_while(|c| { c == '\t' || c == ' ' });
    }

    /// Consume a single character from the input.
    pub fn consume_char(&mut self) -> char {
        if self.eof() {
            fail!("Consuming beyond end of input");
        }
        let ch_range = self.s.char_range_at(self.pos);
        self.pos = ch_range.next;
        ch_range.ch
    }

    /// Consume a set of characters, each passed to `test` until this function
    /// returns false.
    ///
    /// The position after calling this function will be pointing to the character
    /// which caused a false result from `test`.
    ///
    /// Returns the string of characters that returned true for the test function.
    pub fn consume_while(&mut self, test: |char| -> bool) -> String {
        let mut res = String::new();
        while !self.eof() && test(self.peek()) {
            res.push(self.consume_char())
        }
        res
    }

    /// Peek at the current character.
    ///
    /// Note that this does not do any bounds checking.
    pub fn peek(&self) -> char {
        self.s.char_at(self.pos)
    }

    /// Returns true if we have reached the end of the input.
    #[inline]
    pub fn eof(&self) -> bool {
        self.pos >= self.s.len()
    }

}

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

    struct PhraseTestCase<'s> {
        input: &'s str,
        output: &'s str,
        name: &'s str,
    }

    #[test]
    fn test_consume_phrase() {
        let tests = [
            PhraseTestCase {
                input: "\"test phrase\"", output: "test phrase",
                name: "Simple quoted-string"
            },
            PhraseTestCase {
                input: "\"test \\\"phrase\\\"\"", output: "test \"phrase\"",
                name: "quoted-string with escape character"
            },
            PhraseTestCase {
                input: "\"=?utf-8?q?encoded=20q-string?=\"", output: "encoded q-string",
                name: "Encoded quoted-string"
            },
            PhraseTestCase {
                input: "atom test", output: "atom test",
                name: "Collection of atoms"
            },
            PhraseTestCase {
                input: "=?utf-8?q?encoded=20atom?=", output: "encoded atom",
                name: "Encoded atom"
            },
            PhraseTestCase {
                input: "Mix of atoms \"and quoted strings\"", output: "Mix of atoms and quoted strings",
                name: "Mix of atoms and quoted strings"
            },
            PhraseTestCase {
                input: "=?utf-8?q?encoded=20atoms?= mixed with \"unencoded\" \"=?utf-8?b?YW5kIGVuY29kZWQgcS1zdHJpbmdz?=\"",
                output: "encoded atoms mixed with unencoded and encoded q-strings",
                name: "Mix of atoms, q-strings of differing encodings"
            },
            PhraseTestCase {
                input: "\"John Smith\" <test@example.org>", output: "John Smith",
                name: "Stop consuming phrase at \"special\" character",
            }
        ];

        for t in tests.iter() {
            let mut p = Rfc5322Parser::new(t.input);
            let phrase = p.consume_phrase(false);
            assert!(phrase.is_some(), format!("{} returned Some", t.name));
            let test_name = format!("{} == {} for {}", phrase.clone().unwrap(), t.output, t.name);
            assert!(phrase.unwrap() == t.output.to_string(), test_name);
        }
    }

    struct MessageTestCase<'s> {
        input: &'s str,
        headers: Vec<(&'s str, &'s str)>,
        body: &'s str,
    }

    #[test]
    fn test_consume_message() {
        let tests = vec![
            MessageTestCase {
                input: "From: \"Joe Blogs\" <joe@example.org>\r\n\r\nBody",
                headers: vec![
                    ("From", "\"Joe Blogs\" <joe@example.org>"),
                ],
                body: "Body",
            },
            MessageTestCase {
                input: "From: \"Joe Blogs\" <joe@example.org>\r\n\r\nMultiline\r\nBody",
                headers: vec![
                    ("From", "\"Joe Blogs\" <joe@example.org>"),
                ],
                body: "Multiline\r\nBody",
            },
            MessageTestCase {
                input: "From: \"Joe Blogs\" <joe@example.org>\r\nTo: \"John Doe\" <john@example.org>\r\n\r\nMultiple headers",
                headers: vec![
                    ("From", "\"Joe Blogs\" <joe@example.org>"),
                    ("To", "\"John Doe\" <john@example.org>"),
                ],
                body: "Multiple headers",
            },
            MessageTestCase {
                input: "Folded-Header: Some content that is \r\n\t wrapped with a tab.\r\n\r\nFolding whitespace test",
                headers: vec![
                    ("Folded-Header", "Some content that is wrapped with a tab."),
                ],
                body: "Folding whitespace test",
            },
            MessageTestCase {
                input: "Folded-Header: Some content that is \r\n  wrapped with spaces.\r\n\r\nFolding whitespace test",
                headers: vec![
                    ("Folded-Header", "Some content that is wrapped with spaces."),
                ],
                body: "Folding whitespace test",
            },
        ];

        for test in tests.iter() {
            let mut p = Rfc5322Parser::new(test.input);
            let message = p.consume_message();
            match message {
                Some((headers, body)) => {
                    assert_eq!(body, test.body.to_string());
                    for &(header_title, header_value) in test.headers.iter() {
                        let matching_headers = headers.find(&header_title.to_string()).unwrap();
                        assert!(matching_headers.iter().filter(|h| {
                            let val: String = h.get_value().unwrap();
                            val == header_value.to_string()
                        }).count() > 0);
                    }
                },
                None => fail!("Failed to parse message"),
            };
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2017 Dmitry Tantsur <divius.inside@gmail.com>
//
// 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.

//! Session structure definition.

use std::cell::Ref;
use std::collections::HashMap;

use hyper::{Client, Get, Url};
use hyper::client::{IntoUrl, Response};
use hyper::header::Headers;
use hyper::method::Method;
use serde::Deserialize;

use super::{ApiError, ApiResult, ApiVersion, ApiVersionRequest};
use super::auth::AuthMethod;
use super::service::{ApiVersioning, RequestBuilder, ServiceInfo, ServiceType};
use super::utils;

/// An OpenStack API session.
///
/// The session object serves as a wrapper around an HTTP(s) client, handling
/// authentication, accessing the service catalog and token refresh.
///
/// The session object also owns region and endpoint interface to use.
///
/// Finally, the session object is responsible for API version negotiation.
#[derive(Debug)]
pub struct Session {
    auth: Box<AuthMethod>,
    client: Client,
    cached_info: utils::MapCache<(&'static str, String), ServiceInfo>,
    api_versions: HashMap<&'static str, (ApiVersion, Headers)>,
    region: Option<String>,
    endpoint_interface: String
}

impl Session {
    /// Create a new session with a given authentication plugin.
    ///
    /// The resulting session will use the default endpoint interface (usually,
    /// public) and the first available region.
    pub fn new<Auth: AuthMethod + 'static>(auth_method: Auth) -> Session {
        let ep = auth_method.default_endpoint_interface();
        let region = auth_method.default_region();
        Session {
            auth: Box::new(auth_method),
            client: utils::http_client(),
            cached_info: utils::MapCache::new(),
            api_versions: HashMap::new(),
            region: region,
            endpoint_interface: ep
        }
    }

    /// Convert this session into one using the given region.
    ///
    /// Negotiated API versions are reset to their default values.
    pub fn with_region<S: Into<String>>(self, region: S) -> Session {
        Session {
            auth: self.auth,
            client: self.client,
            // ServiceInfo has to be refreshed
            cached_info: utils::MapCache::new(),
            // Different regions potentially have different API versions?
            api_versions: HashMap::new(),
            region: Some(region.into()),
            endpoint_interface: self.endpoint_interface
        }
    }

    /// Convert this session into one using the given endpoint interface.
    ///
    /// Negotiated API versions are kept in the new object.
    pub fn with_endpoint_interface<S>(self, endpoint_interface: S)
            -> Session where S: Into<String> {
        Session {
            auth: self.auth,
            client: self.client,
            // ServiceInfo has to be refreshed
            cached_info: utils::MapCache::new(),
            api_versions: self.api_versions,
            region: self.region,
            endpoint_interface: endpoint_interface.into()
        }
    }

    /// Get a reference to the authentication method in use.
    pub fn auth_method(&self) -> &AuthMethod {
        self.auth.as_ref()
    }

    /// Get an API version used for given service.
    pub fn api_version<Srv: ServiceType>(&self) -> Option<ApiVersion> {
        self.api_versions.get(Srv::catalog_type()).map(|x| x.0)
    }

    /// Get a copy of headers to send for given service.
    ///
    /// Currently only includes API version headers.
    pub fn service_headers<Srv: ServiceType>(&self) -> Headers {
        self.api_versions.get(Srv::catalog_type()).map(|x| x.1.clone())
            .unwrap_or_else(Headers::new)
    }

    /// Get service info for the given service.
    ///
    /// If endpoint interface is not provided, the default for this session
    /// is used.
    pub fn get_service_info<Srv>(&self, endpoint_interface: Option<String>)
            -> ApiResult<ServiceInfo> where Srv: ServiceType {
        let ep = endpoint_interface.unwrap_or(self.endpoint_interface.clone());
        let info = try!(self.get_service_info_ref::<Srv>(ep));
        Ok(info.clone())
    }

    /// Negotiate an API version with the service.
    ///
    /// Negotiation is based on version information returned from the root
    /// endpoint. If no minimum version is returned, the current version is
    /// assumed to be the only supported version.
    ///
    /// The resulting API version is cached for this session.
    pub fn negotiate_api_version<Srv>(&mut self, requested: ApiVersionRequest)
            -> ApiResult<ApiVersion>
            where Srv: ServiceType + ApiVersioning {
        let ep = self.endpoint_interface.clone();
        let key = try!(self.ensure_service_info::<Srv>(ep));
        let info = self.cached_info.get_ref(&key).unwrap();

        match info.pick_api_version(requested.clone()) {
            Some(ver) => {
                let hdrs = try!(Srv::api_version_headers(ver));
                let _ = self.api_versions.insert(Srv::catalog_type(),
                                                 (ver, hdrs));
                info!("Negotiated API version {} for {} API",
                      ver, Srv::catalog_type());
                Ok(ver)
            },
            None => {
                let error = ApiError::UnsupportedApiVersion {
                    requested: requested,
                    minimum: info.minimum_version.clone(),
                    maximum: info.current_version.clone()
                };
                warn!("API negotiation failed for {} API: {}",
                      Srv::catalog_type(), error);
                Err(error)
            }
        }
    }

    /// Prepare an HTTP request with authentication.
    pub fn request<'a, U>(&'a self, method: Method, url: U, headers: Headers)
            -> ApiResult<RequestBuilder<'a>> where U: IntoUrl {
        let real_url = try!(url.into_url());
        self.auth.request(&self.client, method, real_url, headers)
    }

    /// Send a simple GET request.
    pub fn http_get<U>(&self, url: U) -> ApiResult<Response> where U: IntoUrl {
        try!(self.request(Get, url, Headers::new())).send()
    }

    /// Send a simple GET request returning a JSON
    pub fn get_json<U, D>(&self, url: U) -> ApiResult<D>
            where U: IntoUrl, D: Deserialize {
        try!(self.request(Get, url, Headers::new())).fetch_json()
    }

    fn ensure_service_info<Srv>(&self, endpoint_interface: String)
            -> ApiResult<(&'static str, String)> where Srv: ServiceType {
        let key = (Srv::catalog_type(), endpoint_interface);

        try!(self.cached_info.ensure_value(key.clone(), |_| {
            self.get_catalog_endpoint(Srv::catalog_type())
                .and_then(|ep| Srv::service_info(ep, self))
        }));

        Ok(key)
    }

    fn get_catalog_endpoint<S>(&self, service_type: S) -> ApiResult<Url>
            where S: Into<String> {
        self.auth.get_endpoint(&self.client,
                               service_type.into(),
                               Some(self.endpoint_interface.clone()),
                               self.region.clone())
    }

    fn get_service_info_ref<Srv>(&self, endpoint_interface: String)
            -> ApiResult<Ref<ServiceInfo>> where Srv: ServiceType {
        let key = try!(self.ensure_service_info::<Srv>(endpoint_interface));
        Ok(self.cached_info.get_ref(&key).unwrap())
    }
}

impl Clone for Session {
    fn clone(&self) -> Session {
        Session {
            auth: self.auth.clone(),
            // NOTE: hyper::Client does not support Clone
            client: utils::http_client(),
            cached_info: self.cached_info.clone(),
            api_versions: self.api_versions.clone(),
            region: self.region.clone(),
            endpoint_interface: self.endpoint_interface.clone()
        }
    }
}

#[cfg(test)]
pub mod test {
    #![allow(missing_debug_implementations)]
    #![allow(unused_results)]

    use std::collections::HashMap;
    use std::io::Read;

    use hyper;
    use hyper::header::{ContentLength, Headers};
    use hyper::status::StatusCode;

    use super::super::ApiError;
    use super::super::auth::{AuthMethod, Identity, NoAuth};
    use super::super::utils;
    use super::Session;

    pub fn new_with_params<Auth>(auth: Auth, cli: hyper::Client,
                                 region: Option<&str>)
            -> Session where Auth: AuthMethod + 'static {
        let ep = auth.default_endpoint_interface();
        Session {
            auth: Box::new(auth),
            client: cli,
            cached_info: utils::MapCache::new(),
            api_versions: HashMap::new(),
            region: region.map(From::from),
            endpoint_interface: ep
        }
    }

    fn session_with_identity() -> Session {
        let id = Identity::new("http://127.0.2.1").unwrap()
            .with_user("user", "pa$$w0rd", "example.com")
            .with_project_scope("cool project", "example.com")
            .create().unwrap();
        let cli = hyper::Client::with_connector(MockCatalog::default());
        let ep = id.default_endpoint_interface();
        Session {
            auth: Box::new(id),
            client: cli,
            cached_info: utils::MapCache::new(),
            api_versions: HashMap::new(),
            region: None,
            endpoint_interface: ep
        }
    }

    mock_connector!(MockHttp {
        "http://127.0.0.1" => "HTTP/1.1 200 OK\r\n\
                               Server: Mock.Mock\r\n\
                               \r\n\
                               {}"
        "http://127.0.0.2" => "HTTP/1.1 404 NOT FOUND\r\n\
                               Server: Mock.Mock\r\n\
                               \r\n\
                               {}"
    });

    // Copied from keystone API reference.
    const EXAMPLE_CATALOG_RESPONSE: &'static str = r#"
    {
        "catalog": [
            {
                "endpoints": [
                    {
                        "id": "39dc322ce86c4111b4f06c2eeae0841b",
                        "interface": "public",
                        "region": "RegionOne",
                        "url": "http://localhost:5000"
                    },
                    {
                        "id": "ec642f27474842e78bf059f6c48f4e99",
                        "interface": "internal",
                        "region": "RegionOne",
                        "url": "http://localhost:5000"
                    },
                    {
                        "id": "c609fc430175452290b62a4242e8a7e8",
                        "interface": "admin",
                        "region": "RegionOne",
                        "url": "http://localhost:35357"
                    }
                ],
                "id": "4363ae44bdf34a3981fde3b823cb9aa2",
                "type": "identity",
                "name": "keystone"
            }
        ],
        "links": {
            "self": "https://example.com/identity/v3/catalog",
            "previous": null,
            "next": null
        }
    }"#;

    mock_connector!(MockCatalog {
        "http://127.0.2.1" => String::from("HTTP/1.1 200 OK\r\n\
                                            Server: Mock.Mock\r\n\
                                            X-Subject-Token: abcdef\r\n
                                            \r\n") + EXAMPLE_CATALOG_RESPONSE
    });

    #[test]
    fn test_session_request() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = new_with_params(NoAuth::new("http://127.0.0.1/").unwrap(),
                                cli, None);

        let mut resp = s.request(hyper::Post, "http://127.0.0.1/",
                                 Headers::new()).unwrap()
            .body("body").header(ContentLength(4u64)).send().unwrap();

        let mut s = String::new();
        resp.read_to_string(&mut s).unwrap();
        assert_eq!(&s, "{}");
    }

    #[test]
    fn test_session_request_error() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = new_with_params(NoAuth::new("http://127.0.0.2/").unwrap(),
                                cli, None);

        let err = s.request(hyper::Post, "http://127.0.0.2/",
                            Headers::new()).unwrap()
            .body("body").header(ContentLength(4u64)).send().err().unwrap();

        match err {
            ApiError::HttpError(StatusCode::NotFound, ..) => (),
            other => panic!("Unexpected {}", other)
        }
    }

    #[test]
    fn test_session_request_unchecked_error() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = new_with_params(NoAuth::new("http://127.0.0.2/").unwrap(),
                                cli, None);

        let mut resp = s.request(hyper::Post, "http://127.0.0.2/",
                                 Headers::new()).unwrap()
            .body("body").header(ContentLength(4u64)).send_unchecked()
            .unwrap();

        assert_eq!(resp.status, StatusCode::NotFound);

        let mut s = String::new();
        resp.read_to_string(&mut s).unwrap();
        assert_eq!(&s, "{}");
    }

    #[test]
    fn test_session_get_catalog_endpoint() {
        let session = session_with_identity();

        let e1 = session.get_catalog_endpoint("identity").unwrap();
        assert_eq!(&e1.to_string(), "http://localhost:5000/");

        match session.get_catalog_endpoint("foo").err().unwrap() {
            ApiError::EndpointNotFound(ref endp) => assert_eq!(endp, "foo"),
            other => panic!("Unexpected {}", other)
        };
    }

    #[test]
    fn test_session_get_catalog_endpoint_with_endpoint_interface() {
        let sess1 = session_with_identity().with_endpoint_interface("admin");
        let e2 = sess1.get_catalog_endpoint("identity").unwrap();
        assert_eq!(&e2.to_string(), "http://localhost:35357/");

        let sess2 = session_with_identity().with_endpoint_interface("unknown");
        match sess2.get_catalog_endpoint("identity").err().unwrap() {
            ApiError::EndpointNotFound(ref endp) =>
                assert_eq!(endp, "identity"),
            other => panic!("Unexpected {}", other)
        };
    }

    #[test]
    fn test_session_get_catalog_endpoint_with_region() {
        let sess1 = session_with_identity().with_region("RegionOne");
        let e1 = sess1.get_catalog_endpoint("identity").unwrap();
        assert_eq!(&e1.to_string(), "http://localhost:5000/");

        let sess2 = session_with_identity().with_region("unknown");
        match sess2.get_catalog_endpoint("identity").err().unwrap() {
            ApiError::EndpointNotFound(ref endp) =>
                assert_eq!(endp, "identity"),
            other => panic!("Unexpected {}", other)
        };
    }

    #[test]
    fn test_session_get_catalog_endpoint_with_region_and_endpoint_interface() {
        let session = session_with_identity().with_region("RegionOne")
            .with_endpoint_interface("admin");
        let e1 = session.get_catalog_endpoint("identity").unwrap();
        assert_eq!(&e1.to_string(), "http://localhost:35357/");
    }
}
<commit_msg>chore(session): remove unused get_json<commit_after>// Copyright 2017 Dmitry Tantsur <divius.inside@gmail.com>
//
// 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.

//! Session structure definition.

use std::cell::Ref;
use std::collections::HashMap;

use hyper::{Client, Get, Url};
use hyper::client::{IntoUrl, Response};
use hyper::header::Headers;
use hyper::method::Method;

use super::{ApiError, ApiResult, ApiVersion, ApiVersionRequest};
use super::auth::AuthMethod;
use super::service::{ApiVersioning, RequestBuilder, ServiceInfo, ServiceType};
use super::utils;

/// An OpenStack API session.
///
/// The session object serves as a wrapper around an HTTP(s) client, handling
/// authentication, accessing the service catalog and token refresh.
///
/// The session object also owns region and endpoint interface to use.
///
/// Finally, the session object is responsible for API version negotiation.
#[derive(Debug)]
pub struct Session {
    auth: Box<AuthMethod>,
    client: Client,
    cached_info: utils::MapCache<(&'static str, String), ServiceInfo>,
    api_versions: HashMap<&'static str, (ApiVersion, Headers)>,
    region: Option<String>,
    endpoint_interface: String
}

impl Session {
    /// Create a new session with a given authentication plugin.
    ///
    /// The resulting session will use the default endpoint interface (usually,
    /// public) and the first available region.
    pub fn new<Auth: AuthMethod + 'static>(auth_method: Auth) -> Session {
        let ep = auth_method.default_endpoint_interface();
        let region = auth_method.default_region();
        Session {
            auth: Box::new(auth_method),
            client: utils::http_client(),
            cached_info: utils::MapCache::new(),
            api_versions: HashMap::new(),
            region: region,
            endpoint_interface: ep
        }
    }

    /// Convert this session into one using the given region.
    ///
    /// Negotiated API versions are reset to their default values.
    pub fn with_region<S: Into<String>>(self, region: S) -> Session {
        Session {
            auth: self.auth,
            client: self.client,
            // ServiceInfo has to be refreshed
            cached_info: utils::MapCache::new(),
            // Different regions potentially have different API versions?
            api_versions: HashMap::new(),
            region: Some(region.into()),
            endpoint_interface: self.endpoint_interface
        }
    }

    /// Convert this session into one using the given endpoint interface.
    ///
    /// Negotiated API versions are kept in the new object.
    pub fn with_endpoint_interface<S>(self, endpoint_interface: S)
            -> Session where S: Into<String> {
        Session {
            auth: self.auth,
            client: self.client,
            // ServiceInfo has to be refreshed
            cached_info: utils::MapCache::new(),
            api_versions: self.api_versions,
            region: self.region,
            endpoint_interface: endpoint_interface.into()
        }
    }

    /// Get a reference to the authentication method in use.
    pub fn auth_method(&self) -> &AuthMethod {
        self.auth.as_ref()
    }

    /// Get an API version used for given service.
    pub fn api_version<Srv: ServiceType>(&self) -> Option<ApiVersion> {
        self.api_versions.get(Srv::catalog_type()).map(|x| x.0)
    }

    /// Get a copy of headers to send for given service.
    ///
    /// Currently only includes API version headers.
    pub fn service_headers<Srv: ServiceType>(&self) -> Headers {
        self.api_versions.get(Srv::catalog_type()).map(|x| x.1.clone())
            .unwrap_or_else(Headers::new)
    }

    /// Get service info for the given service.
    ///
    /// If endpoint interface is not provided, the default for this session
    /// is used.
    pub fn get_service_info<Srv>(&self, endpoint_interface: Option<String>)
            -> ApiResult<ServiceInfo> where Srv: ServiceType {
        let ep = endpoint_interface.unwrap_or(self.endpoint_interface.clone());
        let info = try!(self.get_service_info_ref::<Srv>(ep));
        Ok(info.clone())
    }

    /// Negotiate an API version with the service.
    ///
    /// Negotiation is based on version information returned from the root
    /// endpoint. If no minimum version is returned, the current version is
    /// assumed to be the only supported version.
    ///
    /// The resulting API version is cached for this session.
    pub fn negotiate_api_version<Srv>(&mut self, requested: ApiVersionRequest)
            -> ApiResult<ApiVersion>
            where Srv: ServiceType + ApiVersioning {
        let ep = self.endpoint_interface.clone();
        let key = try!(self.ensure_service_info::<Srv>(ep));
        let info = self.cached_info.get_ref(&key).unwrap();

        match info.pick_api_version(requested.clone()) {
            Some(ver) => {
                let hdrs = try!(Srv::api_version_headers(ver));
                let _ = self.api_versions.insert(Srv::catalog_type(),
                                                 (ver, hdrs));
                info!("Negotiated API version {} for {} API",
                      ver, Srv::catalog_type());
                Ok(ver)
            },
            None => {
                let error = ApiError::UnsupportedApiVersion {
                    requested: requested,
                    minimum: info.minimum_version.clone(),
                    maximum: info.current_version.clone()
                };
                warn!("API negotiation failed for {} API: {}",
                      Srv::catalog_type(), error);
                Err(error)
            }
        }
    }

    /// Prepare an HTTP request with authentication.
    pub fn request<'a, U>(&'a self, method: Method, url: U, headers: Headers)
            -> ApiResult<RequestBuilder<'a>> where U: IntoUrl {
        let real_url = try!(url.into_url());
        self.auth.request(&self.client, method, real_url, headers)
    }

    /// Send a simple GET request.
    pub fn http_get<U>(&self, url: U) -> ApiResult<Response> where U: IntoUrl {
        try!(self.request(Get, url, Headers::new())).send()
    }

    fn ensure_service_info<Srv>(&self, endpoint_interface: String)
            -> ApiResult<(&'static str, String)> where Srv: ServiceType {
        let key = (Srv::catalog_type(), endpoint_interface);

        try!(self.cached_info.ensure_value(key.clone(), |_| {
            self.get_catalog_endpoint(Srv::catalog_type())
                .and_then(|ep| Srv::service_info(ep, self))
        }));

        Ok(key)
    }

    fn get_catalog_endpoint<S>(&self, service_type: S) -> ApiResult<Url>
            where S: Into<String> {
        self.auth.get_endpoint(&self.client,
                               service_type.into(),
                               Some(self.endpoint_interface.clone()),
                               self.region.clone())
    }

    fn get_service_info_ref<Srv>(&self, endpoint_interface: String)
            -> ApiResult<Ref<ServiceInfo>> where Srv: ServiceType {
        let key = try!(self.ensure_service_info::<Srv>(endpoint_interface));
        Ok(self.cached_info.get_ref(&key).unwrap())
    }
}

impl Clone for Session {
    fn clone(&self) -> Session {
        Session {
            auth: self.auth.clone(),
            // NOTE: hyper::Client does not support Clone
            client: utils::http_client(),
            cached_info: self.cached_info.clone(),
            api_versions: self.api_versions.clone(),
            region: self.region.clone(),
            endpoint_interface: self.endpoint_interface.clone()
        }
    }
}

#[cfg(test)]
pub mod test {
    #![allow(missing_debug_implementations)]
    #![allow(unused_results)]

    use std::collections::HashMap;
    use std::io::Read;

    use hyper;
    use hyper::header::{ContentLength, Headers};
    use hyper::status::StatusCode;

    use super::super::ApiError;
    use super::super::auth::{AuthMethod, Identity, NoAuth};
    use super::super::utils;
    use super::Session;

    pub fn new_with_params<Auth>(auth: Auth, cli: hyper::Client,
                                 region: Option<&str>)
            -> Session where Auth: AuthMethod + 'static {
        let ep = auth.default_endpoint_interface();
        Session {
            auth: Box::new(auth),
            client: cli,
            cached_info: utils::MapCache::new(),
            api_versions: HashMap::new(),
            region: region.map(From::from),
            endpoint_interface: ep
        }
    }

    fn session_with_identity() -> Session {
        let id = Identity::new("http://127.0.2.1").unwrap()
            .with_user("user", "pa$$w0rd", "example.com")
            .with_project_scope("cool project", "example.com")
            .create().unwrap();
        let cli = hyper::Client::with_connector(MockCatalog::default());
        let ep = id.default_endpoint_interface();
        Session {
            auth: Box::new(id),
            client: cli,
            cached_info: utils::MapCache::new(),
            api_versions: HashMap::new(),
            region: None,
            endpoint_interface: ep
        }
    }

    mock_connector!(MockHttp {
        "http://127.0.0.1" => "HTTP/1.1 200 OK\r\n\
                               Server: Mock.Mock\r\n\
                               \r\n\
                               {}"
        "http://127.0.0.2" => "HTTP/1.1 404 NOT FOUND\r\n\
                               Server: Mock.Mock\r\n\
                               \r\n\
                               {}"
    });

    // Copied from keystone API reference.
    const EXAMPLE_CATALOG_RESPONSE: &'static str = r#"
    {
        "catalog": [
            {
                "endpoints": [
                    {
                        "id": "39dc322ce86c4111b4f06c2eeae0841b",
                        "interface": "public",
                        "region": "RegionOne",
                        "url": "http://localhost:5000"
                    },
                    {
                        "id": "ec642f27474842e78bf059f6c48f4e99",
                        "interface": "internal",
                        "region": "RegionOne",
                        "url": "http://localhost:5000"
                    },
                    {
                        "id": "c609fc430175452290b62a4242e8a7e8",
                        "interface": "admin",
                        "region": "RegionOne",
                        "url": "http://localhost:35357"
                    }
                ],
                "id": "4363ae44bdf34a3981fde3b823cb9aa2",
                "type": "identity",
                "name": "keystone"
            }
        ],
        "links": {
            "self": "https://example.com/identity/v3/catalog",
            "previous": null,
            "next": null
        }
    }"#;

    mock_connector!(MockCatalog {
        "http://127.0.2.1" => String::from("HTTP/1.1 200 OK\r\n\
                                            Server: Mock.Mock\r\n\
                                            X-Subject-Token: abcdef\r\n
                                            \r\n") + EXAMPLE_CATALOG_RESPONSE
    });

    #[test]
    fn test_session_request() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = new_with_params(NoAuth::new("http://127.0.0.1/").unwrap(),
                                cli, None);

        let mut resp = s.request(hyper::Post, "http://127.0.0.1/",
                                 Headers::new()).unwrap()
            .body("body").header(ContentLength(4u64)).send().unwrap();

        let mut s = String::new();
        resp.read_to_string(&mut s).unwrap();
        assert_eq!(&s, "{}");
    }

    #[test]
    fn test_session_request_error() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = new_with_params(NoAuth::new("http://127.0.0.2/").unwrap(),
                                cli, None);

        let err = s.request(hyper::Post, "http://127.0.0.2/",
                            Headers::new()).unwrap()
            .body("body").header(ContentLength(4u64)).send().err().unwrap();

        match err {
            ApiError::HttpError(StatusCode::NotFound, ..) => (),
            other => panic!("Unexpected {}", other)
        }
    }

    #[test]
    fn test_session_request_unchecked_error() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = new_with_params(NoAuth::new("http://127.0.0.2/").unwrap(),
                                cli, None);

        let mut resp = s.request(hyper::Post, "http://127.0.0.2/",
                                 Headers::new()).unwrap()
            .body("body").header(ContentLength(4u64)).send_unchecked()
            .unwrap();

        assert_eq!(resp.status, StatusCode::NotFound);

        let mut s = String::new();
        resp.read_to_string(&mut s).unwrap();
        assert_eq!(&s, "{}");
    }

    #[test]
    fn test_session_get_catalog_endpoint() {
        let session = session_with_identity();

        let e1 = session.get_catalog_endpoint("identity").unwrap();
        assert_eq!(&e1.to_string(), "http://localhost:5000/");

        match session.get_catalog_endpoint("foo").err().unwrap() {
            ApiError::EndpointNotFound(ref endp) => assert_eq!(endp, "foo"),
            other => panic!("Unexpected {}", other)
        };
    }

    #[test]
    fn test_session_get_catalog_endpoint_with_endpoint_interface() {
        let sess1 = session_with_identity().with_endpoint_interface("admin");
        let e2 = sess1.get_catalog_endpoint("identity").unwrap();
        assert_eq!(&e2.to_string(), "http://localhost:35357/");

        let sess2 = session_with_identity().with_endpoint_interface("unknown");
        match sess2.get_catalog_endpoint("identity").err().unwrap() {
            ApiError::EndpointNotFound(ref endp) =>
                assert_eq!(endp, "identity"),
            other => panic!("Unexpected {}", other)
        };
    }

    #[test]
    fn test_session_get_catalog_endpoint_with_region() {
        let sess1 = session_with_identity().with_region("RegionOne");
        let e1 = sess1.get_catalog_endpoint("identity").unwrap();
        assert_eq!(&e1.to_string(), "http://localhost:5000/");

        let sess2 = session_with_identity().with_region("unknown");
        match sess2.get_catalog_endpoint("identity").err().unwrap() {
            ApiError::EndpointNotFound(ref endp) =>
                assert_eq!(endp, "identity"),
            other => panic!("Unexpected {}", other)
        };
    }

    #[test]
    fn test_session_get_catalog_endpoint_with_region_and_endpoint_interface() {
        let session = session_with_identity().with_region("RegionOne")
            .with_endpoint_interface("admin");
        let e1 = session.get_catalog_endpoint("identity").unwrap();
        assert_eq!(&e1.to_string(), "http://localhost:35357/");
    }
}
<|endoftext|>