<commit_before>use crate::convert::*;
use crate::folded_multiply::*;
use core::hash::Hasher;
use std::hint::unreachable_unchecked;

/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [`AHasher`] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
    buffer: [u64; 2],
    sum: [u64; 2],
    key: u128,
}

impl AHasher {
    /// Creates a new hasher keyed to the provided keys.
    /// # Example
    ///
    /// ```
    /// use std::hash::Hasher;
    /// use ahash::AHasher;
    ///
    /// let mut hasher = AHasher::new_with_keys(123, 456);
    ///
    /// hasher.write_u32(1989);
    /// hasher.write_u8(11);
    /// hasher.write_u8(9);
    /// hasher.write(b"Huh?");
    ///
    /// println!("Hash is {:x}!", hasher.finish());
    /// ```
    #[inline]
    pub fn new_with_keys(key0: u64, key1: u64) -> Self {
        Self {
            buffer: [key0, key1],
            sum: [key1, key0],
            key: [key0, key1].convert(),
        }
    }

    #[cfg(test)]
    pub(crate) fn test_with_keys(key1: u64, key2: u64) -> AHasher {
        use crate::random_state::scramble_keys;
        let (k1, k2) = scramble_keys(key1, key2);
        AHasher {
            buffer: [k1, k2],
            sum: [k2, k1],
            key: [k1, k2].convert(),
        }
    }

    #[inline(always)]
    fn hash_in(&mut self, new_value: u128) {
        self.buffer = aeshash(self.buffer.convert(), new_value).convert();
        self.sum = add_by_64s(self.sum, self.buffer);
    }

    #[inline(always)]
    fn hash_in_2(&mut self, v1: u128, v2: u128) {
        self.hash_in(v1);
        self.hash_in(v2);
    }

    #[inline(always)]
    fn hash_in_b0(&mut self, data: &[u8]) {
        self.hash_in(0);
    }
    #[inline(always)]
    fn hash_in_b1(&mut self, data: &[u8]) {
        self.hash_in(data[0] as u128);
    }
    #[inline(always)]
    fn hash_in_b2(&mut self, data: &[u8]) {
        self.hash_in(data.read_u16().0 as u128);
    }
    #[inline(always)]
    fn hash_in_b3(&mut self, data: &[u8]) {
        let value: [u64; 2] = [data.read_u16().0 as u64, data.read_last_u16() as u64];
        self.hash_in(value.convert());
    }
    #[inline(always)]
    fn hash_in_b4(&mut self, data: &[u8]) {
        self.hash_in(data.read_u32().0 as u128);
    }
    #[inline(always)]
    fn hash_in_b5_8(&mut self, data: &[u8]) {
        let value: [u64; 2] = [data.read_u32().0 as u64, data.read_last_u32() as u64];
        self.hash_in(value.convert());
    }
}

/// Provides methods to hash all of the primitive types.
impl Hasher for AHasher {
    #[inline]
    fn write_u8(&mut self, i: u8) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u16(&mut self, i: u16) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u32(&mut self, i: u32) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u128(&mut self, i: u128) {
        self.hash_in(i);
    }

    #[inline]
    fn write_usize(&mut self, i: usize) {
        self.write_u64(i as u64);
    }

    #[inline]
    fn write_u64(&mut self, i: u64) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write(&mut self, input: &[u8]) {
        let mut data = input;
        let length = data.len() as u64;
        //This will be scrambled by the first AES round in any branch.
        self.buffer[1] = self.buffer[1].wrapping_add(length);
        //A 'binary search' on sizes reduces the number of comparisons.
        if data.len() <= 8 {
            match data.len() {
                0 => self.hash_in_b0(data),
                1 => self.hash_in_b1(data),
                2 => self.hash_in_b2(data),
                3 => self.hash_in_b3(data),
                4 => self.hash_in_b4(data),
                5 => self.hash_in_b5_8(data),
                6 => self.hash_in_b5_8(data),
                7 => self.hash_in_b5_8(data),
                8 => self.hash_in_b5_8(data),
                _ => unsafe { unreachable_unchecked() }, //enum is exhaustive
            }
        } else {
            if data.len() > 32 {
                if data.len() > 64 {
                    let tail = data.read_last_u128x4();
                    let mut current: [u128; 4] = [self.key; 4];
                    current[0] = aeshash(current[0], tail[0]);
                    current[1] = aeshash(current[1], tail[1]);
                    current[2] = aeshash(current[2], tail[2]);
                    current[3] = aeshash(current[3], tail[3]);
                    let mut sum: [u64; 2] = add_by_64s(add_by_64s(current[0].convert(), current[1].convert()),
                                                       add_by_64s(current[2].convert(), current[3].convert()));
                    while data.len() > 64 {
                        let (blocks, rest) = data.read_u128x4();
                        current[0] = aeshash(current[0], blocks[0]);
                        sum = add_by_64s(sum, current[0].convert());
                        current[1] = aeshash(current[1], blocks[1]);
                        sum = add_by_64s(sum, current[1].convert());
                        current[2] = aeshash(current[2], blocks[2]);
                        sum = add_by_64s(sum, current[2].convert());
                        current[3] = aeshash(current[3], blocks[3]);
                        sum = add_by_64s(sum, current[3].convert());
                        data = rest;
                    }
                    self.hash_in(current[0]);
                    self.hash_in(current[1]);
                    self.hash_in(current[2]);
                    self.hash_in(current[3]);
                    self.sum = add_by_64s(self.sum, sum);
                } else {
                    //len 33-64
                    let (head, _) = data.read_u128x2();
                    let tail = data.read_last_u128x2();
                    self.hash_in_2(head[0], head[1]);
                    self.hash_in_2(tail[0], tail[1]);
                }
            } else {
                if data.len() > 16 {
                    //len 17-32
                    self.hash_in_2(data.read_u128().0, data.read_last_u128());
                } else {
                    //len 9-16
                    let value: [u64; 2] = [data.read_u64().0, data.read_last_u64()];
                    self.hash_in(value.convert());
                }
            }
        }
    }
    #[inline]
    fn finish(&self) -> u64 {
        let result: [u64; 2] = aeshashx2(self.sum.convert(), self.buffer.convert(), self.key).convert();
        result[0].wrapping_add(result[1])
    }
}

#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[inline(always)]
fn aeshash(value: u128, xor: u128) -> u128 {
    #[cfg(target_arch = "x86")]
    use core::arch::x86::*;
    #[cfg(target_arch = "x86_64")]
    use core::arch::x86_64::*;
    use core::mem::transmute;
    unsafe {
        let value = transmute(value);
        transmute(_mm_aesdec_si128(value, transmute(xor)))
    }
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[inline(always)]
fn aeshashx2(value: u128, k1: u128, k2: u128) -> u128 {
    #[cfg(target_arch = "x86")]
    use core::arch::x86::*;
    #[cfg(target_arch = "x86_64")]
    use core::arch::x86_64::*;
    use core::mem::transmute;
    unsafe {
        let value = transmute(value);
        let value = _mm_aesdec_si128(value, transmute(k1));
        transmute(_mm_aesdec_si128(value, transmute(k2)))
    }
}

#[cfg(test)]
mod tests {
    use crate::aes_hash::*;
    use crate::convert::Convert;
    use std::collections::HashMap;
    use std::hash::BuildHasherDefault;

    #[cfg(feature = "compile-time-rng")]
    #[test]
    fn test_builder() {
        let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
        map.insert(1, 3);
    }

    #[cfg(feature = "compile-time-rng")]
    #[test]
    fn test_default() {
        let hasher_a = AHasher::default();
        assert_ne!(0, hasher_a.buffer[0]);
        assert_ne!(0, hasher_a.buffer[1]);
        assert_ne!(hasher_a.buffer[0], hasher_a.buffer[1]);
        let hasher_b = AHasher::default();
        assert_eq!(hasher_a.buffer[0], hasher_b.buffer[0]);
        assert_eq!(hasher_a.buffer[1], hasher_b.buffer[1]);
    }

    #[test]
    fn test_hash() {
        let mut result: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
        let value: [u64; 2] = [1 << 32, 0xFEDCBA9876543210];
        result = aeshash(value.convert(), result.convert()).convert();
        result = aeshash(result.convert(), result.convert()).convert();
        let mut result2: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
        let value2: [u64; 2] = [1, 0xFEDCBA9876543210];
        result2 = aeshash(value2.convert(), result2.convert()).convert();
        result2 = aeshash(result2.convert(), result.convert()).convert();
        let result: [u8; 16] = result.convert();
        let result2: [u8; 16] = result2.convert();
        assert_ne!(hex::encode(result), hex::encode(result2));
    }

    #[test]
    fn test_conversion() {
        let input: &[u8] = "dddddddd".as_bytes();
        let bytes: u64 = as_array!(input, 8).convert();
        assert_eq!(bytes, 0x6464646464646464);
    }
}
<commit_msg>Remove computed jump<commit_after>use crate::convert::*;
use crate::folded_multiply::*;
use core::hash::Hasher;
use std::hint::unreachable_unchecked;

/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [`AHasher`] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
    buffer: [u64; 2],
    sum: [u64; 2],
    key: u128,
}

impl AHasher {
    /// Creates a new hasher keyed to the provided keys.
    /// # Example
    ///
    /// ```
    /// use std::hash::Hasher;
    /// use ahash::AHasher;
    ///
    /// let mut hasher = AHasher::new_with_keys(123, 456);
    ///
    /// hasher.write_u32(1989);
    /// hasher.write_u8(11);
    /// hasher.write_u8(9);
    /// hasher.write(b"Huh?");
    ///
    /// println!("Hash is {:x}!", hasher.finish());
    /// ```
    #[inline]
    pub fn new_with_keys(key0: u64, key1: u64) -> Self {
        Self {
            buffer: [key0, key1],
            sum: [key1, key0],
            key: [key0, key1].convert(),
        }
    }

    #[cfg(test)]
    pub(crate) fn test_with_keys(key1: u64, key2: u64) -> AHasher {
        use crate::random_state::scramble_keys;
        let (k1, k2) = scramble_keys(key1, key2);
        AHasher {
            buffer: [k1, k2],
            sum: [k2, k1],
            key: [k1, k2].convert(),
        }
    }

    #[inline(always)]
    fn hash_in(&mut self, new_value: u128) {
        self.buffer = aeshash(self.buffer.convert(), new_value).convert();
        self.sum = add_by_64s(self.sum, self.buffer);
    }

    #[inline(always)]
    fn hash_in_2(&mut self, v1: u128, v2: u128) {
        self.hash_in(v1);
        self.hash_in(v2);
    }
}

/// Provides methods to hash all of the primitive types.
impl Hasher for AHasher {
    #[inline]
    fn write_u8(&mut self, i: u8) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u16(&mut self, i: u16) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u32(&mut self, i: u32) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u128(&mut self, i: u128) {
        self.hash_in(i);
    }

    #[inline]
    fn write_usize(&mut self, i: usize) {
        self.write_u64(i as u64);
    }

    #[inline]
    fn write_u64(&mut self, i: u64) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write(&mut self, input: &[u8]) {
        let mut data = input;
        let length = data.len() as u64;
        //This will be scrambled by the first AES round in any branch.
        self.buffer[1] = self.buffer[1].wrapping_add(length);
        //A 'binary search' on sizes reduces the number of comparisons.
        if data.len() <= 8 {
            let value: [u64; 2] = if data.len() >= 2 {
                if data.len() >= 4 {
                    //len 4-8
                    [data.read_u32().0 as u64, data.read_last_u32() as u64]
                } else {
                    //len 2-3
                    [data.read_u16().0 as u64, data[data.len() - 1] as u64]
                }
            } else {
                if data.len() > 0 {
                    [data[0] as u64, 0]
                } else {
                    [0, 0]
                }
            };
            self.hash_in(value.convert());
        } else {
            if data.len() > 32 {
                if data.len() > 64 {
                    let tail = data.read_last_u128x4();
                    let mut current: [u128; 4] = [self.key; 4];
                    current[0] = aeshash(current[0], tail[0]);
                    current[1] = aeshash(current[1], tail[1]);
                    current[2] = aeshash(current[2], tail[2]);
                    current[3] = aeshash(current[3], tail[3]);
                    let mut sum: [u64; 2] = add_by_64s(add_by_64s(current[0].convert(), current[1].convert()),
                                                       add_by_64s(current[2].convert(), current[3].convert()));
                    while data.len() > 64 {
                        let (blocks, rest) = data.read_u128x4();
                        current[0] = aeshash(current[0], blocks[0]);
                        sum = add_by_64s(sum, current[0].convert());
                        current[1] = aeshash(current[1], blocks[1]);
                        sum = add_by_64s(sum, current[1].convert());
                        current[2] = aeshash(current[2], blocks[2]);
                        sum = add_by_64s(sum, current[2].convert());
                        current[3] = aeshash(current[3], blocks[3]);
                        sum = add_by_64s(sum, current[3].convert());
                        data = rest;
                    }
                    self.hash_in(current[0]);
                    self.hash_in(current[1]);
                    self.hash_in(current[2]);
                    self.hash_in(current[3]);
                    self.sum = add_by_64s(self.sum, sum);
                } else {
                    //len 33-64
                    let (head, _) = data.read_u128x2();
                    let tail = data.read_last_u128x2();
                    self.hash_in_2(head[0], head[1]);
                    self.hash_in_2(tail[0], tail[1]);
                }
            } else {
                if data.len() > 16 {
                    //len 17-32
                    self.hash_in_2(data.read_u128().0, data.read_last_u128());
                } else {
                    //len 9-16
                    let value: [u64; 2] = [data.read_u64().0, data.read_last_u64()];
                    self.hash_in(value.convert());
                }
            }
        }
    }
    #[inline]
    fn finish(&self) -> u64 {
        let result: [u64; 2] = aeshashx2(self.sum.convert(), self.buffer.convert(), self.key).convert();
        result[0].wrapping_add(result[1])
    }
}

#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[inline(always)]
fn aeshash(value: u128, xor: u128) -> u128 {
    #[cfg(target_arch = "x86")]
    use core::arch::x86::*;
    #[cfg(target_arch = "x86_64")]
    use core::arch::x86_64::*;
    use core::mem::transmute;
    unsafe {
        let value = transmute(value);
        transmute(_mm_aesdec_si128(value, transmute(xor)))
    }
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)))]
#[inline(always)]
fn aeshashx2(value: u128, k1: u128, k2: u128) -> u128 {
    #[cfg(target_arch = "x86")]
    use core::arch::x86::*;
    #[cfg(target_arch = "x86_64")]
    use core::arch::x86_64::*;
    use core::mem::transmute;
    unsafe {
        let value = transmute(value);
        let value = _mm_aesdec_si128(value, transmute(k1));
        transmute(_mm_aesdec_si128(value, transmute(k2)))
    }
}

#[cfg(test)]
mod tests {
    use crate::aes_hash::*;
    use crate::convert::Convert;
    use std::collections::HashMap;
    use std::hash::BuildHasherDefault;

    #[cfg(feature = "compile-time-rng")]
    #[test]
    fn test_builder() {
        let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
        map.insert(1, 3);
    }

    #[cfg(feature = "compile-time-rng")]
    #[test]
    fn test_default() {
        let hasher_a = AHasher::default();
        assert_ne!(0, hasher_a.buffer[0]);
        assert_ne!(0, hasher_a.buffer[1]);
        assert_ne!(hasher_a.buffer[0], hasher_a.buffer[1]);
        let hasher_b = AHasher::default();
        assert_eq!(hasher_a.buffer[0], hasher_b.buffer[0]);
        assert_eq!(hasher_a.buffer[1], hasher_b.buffer[1]);
    }

    #[test]
    fn test_hash() {
        let mut result: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
        let value: [u64; 2] = [1 << 32, 0xFEDCBA9876543210];
        result = aeshash(value.convert(), result.convert()).convert();
        result = aeshash(result.convert(), result.convert()).convert();
        let mut result2: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
        let value2: [u64; 2] = [1, 0xFEDCBA9876543210];
        result2 = aeshash(value2.convert(), result2.convert()).convert();
        result2 = aeshash(result2.convert(), result.convert()).convert();
        let result: [u8; 16] = result.convert();
        let result2: [u8; 16] = result2.convert();
        assert_ne!(hex::encode(result), hex::encode(result2));
    }

    #[test]
    fn test_conversion() {
        let input: &[u8] = "dddddddd".as_bytes();
        let bytes: u64 = as_array!(input, 8).convert();
        assert_eq!(bytes, 0x6464646464646464);
    }
}
<|endoftext|>


<commit_before>use hyper::server::Request;
use std::sync::Mutex;
extern crate redis;
extern crate byteorder;

use common::*;
use self::byteorder::{BigEndian, WriteBytesExt};
use std::net::IpAddr;
extern crate time;
use std::error::Error;
use std::str::FromStr;

enum IPVersion {
    V4,
    V6
}

pub fn announce(req: &Request, redis_connection: &Mutex<redis::Connection>,
    announce_interval: u32,
    drop_threshold: u32) -> Result<Vec<u8>, String>
{
    // Get which ip version we are serving
    let ip_version = match req.remote_addr.ip() {
        IpAddr::V4(_) => IPVersion::V4,
        IpAddr::V6(_) => IPVersion::V6,
    };

    let query_hashmap = query_hashmap(&req.uri);

    // Check we have everything we need
    let info_hash = try!(query_hashmap.get("info_hash")
        .ok_or_else(|| "No info_hash specified".to_owned())
        .and_then(|i| parse_info_hash(i)));
    debug!("    infohash={}", info_hash);
    /*let peer_id = try!(query_hashmap.get("peer_id")
        .ok_or_else(|| "No peer_id specified".to_owned())
        .and_then(|i| parse_peer_id(i)));*/
    let port = try!(query_hashmap.get("port")
        .ok_or_else(|| "No port specified".to_owned())
        .and_then(|i| i.parse::<u16>().map_err(|_| "Invalid port specified".to_owned())));
    let left = try!(query_hashmap.get("left")
        .ok_or_else(|| "No left specified".to_owned())
        .and_then(|i| i.parse::<u64>().map_err(|_| "Invalid left specified".to_owned())));
    let compact = try!(query_hashmap.get("compact")
        .ok_or_else(|| "No compact specified".to_owned())
        .and_then(|i| i.parse::<u64>().map_err(|_| "Invalid compact specified".to_owned())));
    if compact != 1 {
        return Err("This tracker only supports compact responses".to_owned());
    }
    let numwant = try!(query_hashmap.get("numwant")
        .unwrap_or(&"50")
        .parse::<u64>().map_err(|_| "Invalid numwant specified".to_owned()));

    // Get ip
    let ip: Option<IpAddr>;
    match ip_version {
        IPVersion::V4 => {
            ip = query_hashmap.get("ip").
                and_then(|i| IpAddr::from_str(i).ok()).
                // only acccept it if it's an ipv4 address
                and_then(|i| if let IpAddr::V4(_) = i { Some(i) } else { None });
        }
        IPVersion::V6 => {
            ip = query_hashmap.get("ipv6").
                or(query_hashmap.get("ip")).
                and_then(|i| IpAddr::from_str(i).ok()).
                // only acccept it if it's an ipv6 address
                and_then(|i| if let IpAddr::V6(_) = i { Some(i) } else { None });
        }
    }
    // Fallback on connection ip if it was invalid or not specified
    let ip = ip.unwrap_or(req.remote_addr.ip());
    debug!("    ip={}, port={}", ip.to_string(), port);

    // Generate compact peer entry
    let peer = generate_peer(ip, port);
    let size_of_peer = &peer.len();
    assert_eq!(*size_of_peer, if let IPVersion::V6 = ip_version {18} else {6});

    // Get ready for redis queries
    let key_base = format!("torrent:{}", info_hash);
    let key_seeds = format!("{}:seeds{}", key_base, if let IPVersion::V6 = ip_version {"6"} else {""});
    let key_peers = format!("{}:peers{}", key_base, if let IPVersion::V6 = ip_version {"6"} else {""});
    let time_now = time::get_time().sec*1000;
    let time_drop = time_now - (drop_threshold as i64)*1000;
    let mut pipe = redis::pipe();

    // Prune out old entries
    pipe.cmd("ZREMRANGEBYSCORE").arg(&*key_seeds).arg(0).arg(time_drop).ignore();
    pipe.cmd("ZREMRANGEBYSCORE").arg(&*key_peers).arg(0).arg(time_drop).ignore();

    // Get total count of peers/seeds
    pipe.cmd("ZCARD").arg(&*key_seeds);
    pipe.cmd("ZCARD").arg(&*key_peers);

    // Get peers and seeds
    pipe.cmd("ZRANGE").arg(&*key_seeds).arg(0).arg(numwant);
    pipe.cmd("ZRANGE").arg(&*key_peers).arg(0).arg(numwant);

    // Add
    if left == 0 {
        pipe.cmd("ZADD").arg(&*key_seeds).arg(time_now).arg(peer).ignore();
    }
    else {
        pipe.cmd("ZADD").arg(&*key_peers).arg(time_now).arg(peer).ignore();
    }

    // Unlock mutex and go!
    let results: (u32, u32, Vec<Vec<u8>>, Vec<Vec<u8>>);
    {
        let r = redis_connection.lock().unwrap();
        results = try!(pipe.query(&(*r))
            .map_err(|e| format!("Redis error: {}", e.description())));
    }

    let (total_seeds, total_peers, seeds, peers) = results;
    debug!("    seeds={}, peers={}", total_seeds, total_peers);

    // Begin building output
    let mut response: Vec<u8> = Vec::new();
    response.extend(format!("d8:completei{}e10:incompletei{}e8:intervali{}e",
        total_seeds, total_peers, announce_interval).bytes());
    match ip_version {
        IPVersion::V4 => response.extend(b"5:peers"),
        IPVersion::V6 => response.extend(b"6:peers6"),
    }

    // dont give seeds to seeds
    if left == 0 {
        response.extend(format!("{}:", size_of_peer*peers.len()).bytes());
        for i in peers {
            response.extend(i);
        }
    }
    else {
        response.extend(format!("{}:", size_of_peer*seeds.len() + size_of_peer*peers.len()).bytes());
        for i in seeds {
            response.extend(i);
        }
        for i in peers {
            response.extend(i);
        }
    }
    response.extend(b"e");

    return Ok(response);
}

fn generate_peer(ip: IpAddr, port: u16) -> Vec<u8> {
    let mut retval: Vec<u8>;
    match ip {
        IpAddr::V4(ip_v4) => {
            retval = Vec::with_capacity(6);
            retval.extend(ip_v4.octets().iter());
        },
        IpAddr::V6(ip_v6) => {
            retval = Vec::with_capacity(18);
            //retval.extend(ip_v6.octets().iter()); not yet in stable
            for i in ip_v6.segments().iter() {
                retval.write_u16::<BigEndian>(*i).unwrap();
            }
        }
    }
    retval.write_u16::<BigEndian>(port).unwrap();
    return retval;
}

#[test]
fn generate_peer_test() {
    assert_eq!(generate_peer(IpAddr::from_str("127.0.0.1").unwrap(), 0x3039), &[127, 0, 0, 1, 0x30, 0x39]);
    assert_eq!(generate_peer(IpAddr::from_str("abcd:ef01:2345:6789:abcd:ef01:2345:6789").unwrap(), 0x1234), &[0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0x12, 0x34]);
}
<commit_msg>announce: parse event parameter<commit_after>use hyper::server::Request;
use std::sync::Mutex;
extern crate redis;
extern crate byteorder;

use common::*;
use self::byteorder::{BigEndian, WriteBytesExt};
use std::net::IpAddr;
extern crate time;
use std::error::Error;
use std::str::FromStr;

enum IPVersion {
    V4,
    V6
}

enum Event {
    Unspecified,
    Started,
    Stopped,
    Completed,
}

pub fn announce(req: &Request, redis_connection: &Mutex<redis::Connection>,
    announce_interval: u32,
    drop_threshold: u32) -> Result<Vec<u8>, String>
{
    // Get which ip version we are serving
    let ip_version = match req.remote_addr.ip() {
        IpAddr::V4(_) => IPVersion::V4,
        IpAddr::V6(_) => IPVersion::V6,
    };

    let query_hashmap = query_hashmap(&req.uri);

    // Check we have everything we need
    let info_hash = try!(query_hashmap.get("info_hash")
        .ok_or_else(|| "No info_hash specified".to_owned())
        .and_then(|i| parse_info_hash(i)));
    debug!("    infohash={}", info_hash);
    /*let peer_id = try!(query_hashmap.get("peer_id")
        .ok_or_else(|| "No peer_id specified".to_owned())
        .and_then(|i| parse_peer_id(i)));*/
    let port = try!(query_hashmap.get("port")
        .ok_or_else(|| "No port specified".to_owned())
        .and_then(|i| i.parse::<u16>().map_err(|_| "Invalid port specified".to_owned())));
    let left = try!(query_hashmap.get("left")
        .ok_or_else(|| "No left specified".to_owned())
        .and_then(|i| i.parse::<u64>().map_err(|_| "Invalid left specified".to_owned())));
    let compact = try!(query_hashmap.get("compact")
        .ok_or_else(|| "No compact specified".to_owned())
        .and_then(|i| i.parse::<u64>().map_err(|_| "Invalid compact specified".to_owned())));
    if compact != 1 {
        return Err("This tracker only supports compact responses".to_owned());
    }
    let numwant = try!(query_hashmap.get("numwant")
        .unwrap_or(&"50")
        .parse::<u64>().map_err(|_| "Invalid numwant specified".to_owned()));
    let event;
    match query_hashmap.get("event") {
        Some(i) => {
            if *i == "started" {
                event = Event::Started;
            }
            else if *i == "stopped" {
                event = Event::Stopped;
            }
            else if *i == "completed" {
                event = Event::Completed;
            }
            else {
                return Err("Event parameter not understood".to_owned());
            }
        }
        None => { event = Event::Unspecified; },
    }

    // Get ip
    let ip: Option<IpAddr>;
    match ip_version {
        IPVersion::V4 => {
            ip = query_hashmap.get("ip").
                and_then(|i| IpAddr::from_str(i).ok()).
                // only acccept it if it's an ipv4 address
                and_then(|i| if let IpAddr::V4(_) = i { Some(i) } else { None });
        }
        IPVersion::V6 => {
            ip = query_hashmap.get("ipv6").
                or(query_hashmap.get("ip")).
                and_then(|i| IpAddr::from_str(i).ok()).
                // only acccept it if it's an ipv6 address
                and_then(|i| if let IpAddr::V6(_) = i { Some(i) } else { None });
        }
    }
    // Fallback on connection ip if it was invalid or not specified
    let ip = ip.unwrap_or(req.remote_addr.ip());
    debug!("    ip={}, port={}", ip.to_string(), port);

    // Generate compact peer entry
    let peer = generate_peer(ip, port);
    let size_of_peer = &peer.len();
    assert_eq!(*size_of_peer, if let IPVersion::V6 = ip_version {18} else {6});

    // Get ready for redis queries
    let key_base = format!("torrent:{}", info_hash);
    let key_seeds = format!("{}:seeds{}", key_base, if let IPVersion::V6 = ip_version {"6"} else {""});
    let key_peers = format!("{}:peers{}", key_base, if let IPVersion::V6 = ip_version {"6"} else {""});
    let time_now = time::get_time().sec*1000;
    let time_drop = time_now - (drop_threshold as i64)*1000;
    let mut pipe = redis::pipe();

    // Prune out old entries
    pipe.cmd("ZREMRANGEBYSCORE").arg(&*key_seeds).arg(0).arg(time_drop).ignore();
    pipe.cmd("ZREMRANGEBYSCORE").arg(&*key_peers).arg(0).arg(time_drop).ignore();

    // Get total count of peers/seeds
    pipe.cmd("ZCARD").arg(&*key_seeds);
    pipe.cmd("ZCARD").arg(&*key_peers);

    // Get peers and seeds
    pipe.cmd("ZRANGE").arg(&*key_seeds).arg(0).arg(numwant);
    pipe.cmd("ZRANGE").arg(&*key_peers).arg(0).arg(numwant);

    // Add
    if left == 0 {
        pipe.cmd("ZADD").arg(&*key_seeds).arg(time_now).arg(peer).ignore();
    }
    else {
        pipe.cmd("ZADD").arg(&*key_peers).arg(time_now).arg(peer).ignore();
    }

    // Unlock mutex and go!
    let results: (u32, u32, Vec<Vec<u8>>, Vec<Vec<u8>>);
    {
        let r = redis_connection.lock().unwrap();
        results = try!(pipe.query(&(*r))
            .map_err(|e| format!("Redis error: {}", e.description())));
    }

    let (total_seeds, total_peers, seeds, peers) = results;
    debug!("    seeds={}, peers={}", total_seeds, total_peers);

    // Begin building output
    let mut response: Vec<u8> = Vec::new();
    response.extend(format!("d8:completei{}e10:incompletei{}e8:intervali{}e",
        total_seeds, total_peers, announce_interval).bytes());
    match ip_version {
        IPVersion::V4 => response.extend(b"5:peers"),
        IPVersion::V6 => response.extend(b"6:peers6"),
    }

    // dont give seeds to seeds
    if left == 0 {
        response.extend(format!("{}:", size_of_peer*peers.len()).bytes());
        for i in peers {
            response.extend(i);
        }
    }
    else {
        response.extend(format!("{}:", size_of_peer*seeds.len() + size_of_peer*peers.len()).bytes());
        for i in seeds {
            response.extend(i);
        }
        for i in peers {
            response.extend(i);
        }
    }
    response.extend(b"e");

    return Ok(response);
}

fn generate_peer(ip: IpAddr, port: u16) -> Vec<u8> {
    let mut retval: Vec<u8>;
    match ip {
        IpAddr::V4(ip_v4) => {
            retval = Vec::with_capacity(6);
            retval.extend(ip_v4.octets().iter());
        },
        IpAddr::V6(ip_v6) => {
            retval = Vec::with_capacity(18);
            //retval.extend(ip_v6.octets().iter()); not yet in stable
            for i in ip_v6.segments().iter() {
                retval.write_u16::<BigEndian>(*i).unwrap();
            }
        }
    }
    retval.write_u16::<BigEndian>(port).unwrap();
    return retval;
}

#[test]
fn generate_peer_test() {
    assert_eq!(generate_peer(IpAddr::from_str("127.0.0.1").unwrap(), 0x3039), &[127, 0, 0, 1, 0x30, 0x39]);
    assert_eq!(generate_peer(IpAddr::from_str("abcd:ef01:2345:6789:abcd:ef01:2345:6789").unwrap(), 0x1234), &[0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0x12, 0x34]);
}
<|endoftext|>


<commit_before>pub type Time = f32;

pub struct Behavior<'a, A> where A: 'a {
  closure: Box<Fn(Time) -> A + 'a>,
  next: Option<(Time, &'a Behavior<'a, A>)>
}

impl<'a, A> Behavior<'a, A> {
  pub fn new<F>(act: F, next: Option<(Time, &'a Self)>) -> Self where F: Fn(Time) -> A + 'a {
    Behavior {
      closure: Box::new(act),
      next: next
    }
  }

  pub fn run(&'a self, t: Time) -> (A, &'a Self) {
    let next = self.next;

    match next {
      Some((node_t, node)) if t >= node_t => node.run(t),
      _ => ((self.closure)(t), self)
    }
  }
}
<commit_msg>Added switch function to Behavior.<commit_after>pub type Time = f32;

pub struct Behavior<'a, A> where A: 'a {
  closure: Box<Fn(Time) -> A + 'a>,
  next: Option<(Time, &'a Behavior<'a, A>)>
}

impl<'a, A> Behavior<'a, A> {
  pub fn new<F>(act: F, next: Option<(Time, &'a Self)>) -> Self where F: Fn(Time) -> A + 'a {
    Behavior {
      closure: Box::new(act),
      next: next
    }
  }

  pub fn run(&'a self, t: Time) -> (A, &'a Self) {
    let next = self.next;

    match next {
      Some((node_t, node)) if t >= node_t => node.run(t),
      _ => ((self.closure)(t), self)
    }
  }

  pub fn switch(&mut self, t: Time, sw: &'a Self) {
    self.next = Some((t, sw))
  }
}
<|endoftext|>


<commit_before>extern crate extra;

use std::io;
use std::io::{BufRead, BufReader};
use std::env;
use std::fs::File;
use std::path::Path;
use std::process::exit;

static MAN_PAGE: &str = /* @MANSTART{grep} */ r#"
NAME
    grep - print lines matching a pattern

SYNOPSIS
    grep [-h | --help] [-n --line-number] PATTERN [FILE...]

DESCRIPTION
    grep searches the named input FILEs for lines containing a match to the given PATTERN. If no
    files are specified, grep searches the standard input. grep prints the matching lines.

OPTIONS
    -h
    --help
        Print this manual page.

    -v
    --invert-match
        Invert matching.

    -c
    --count
        Print count of matching lines, instead of those lines.

    -n
    --line-number
        Prefix each line of output with the line number of the match.
"#; /* @MANEND */

#[derive(Copy, Clone)]
struct Flags {
    line_numbers: bool,
    invert_match: bool,
    count: bool,
}

impl Flags {
    fn new() -> Flags {
        Flags { line_numbers: false, invert_match: false, count: false }
    }
}

fn main() {
    let args = env::args().skip(1);
    let stdin = io::stdin();
    let stdin = stdin.lock();

    let mut flags = Flags::new();
    let mut pattern = String::new();
    let mut files = Vec::with_capacity(args.len());
    for arg in args {
        if arg.starts_with('-') {
            match arg.as_str() {
                "-h" | "--help" => {
                    print!("{}", MAN_PAGE);
                },
                "-n" | "--line-number" => flags.line_numbers = true,
                "-v" | "--invert-match" => flags.invert_match = true,
                "-c" | "--count" => flags.count = true,
                _ => {
                    eprintln!("Error, unknown option: {}", arg);
                    exit(1);
                }
            }
        } else if pattern.is_empty() {
            pattern = arg.clone();
        } else {
            match File::open(&Path::new(&arg)) {
                Ok(f) => files.push(f),
                Err(err) => {
                    eprintln!("Error opening {}: {}", arg, err);
                    exit(1);
                }
            }
        }
    }

    if pattern.is_empty() {
        eprintln!("You must provide a pattern");
        exit(1);
    }

    if files.is_empty() {
        do_simple_search(BufReader::new(stdin), &pattern, flags);
    } else {
        for f in files {
            do_simple_search(BufReader::new(f), &pattern, flags);
        }
    }
}

fn do_simple_search<T: BufRead>(reader: T, pattern: &str, flags: Flags) {
    let mut line_num = 0;
    let mut count = 0;
    for result in reader.lines() {
        line_num += 1;
        if let Ok(line) = result {
            let is_match = if flags.invert_match {
                !line.contains(pattern)
            } else {
                line.contains(pattern)
            };
            if is_match && flags.count {
                count += 1;
            } else if is_match {
                if flags.line_numbers {
                    print!("{}: ", line_num + 1);
                }
                println!("{}", line);
            }
        }
    }

    if flags.count {
        println!("{}", count);
    }
}
<commit_msg>Fix off-by-one error in line count<commit_after>extern crate extra;

use std::io;
use std::io::{BufRead, BufReader};
use std::env;
use std::fs::File;
use std::path::Path;
use std::process::exit;

static MAN_PAGE: &str = /* @MANSTART{grep} */ r#"
NAME
    grep - print lines matching a pattern

SYNOPSIS
    grep [-h | --help] [-n --line-number] PATTERN [FILE...]

DESCRIPTION
    grep searches the named input FILEs for lines containing a match to the given PATTERN. If no
    files are specified, grep searches the standard input. grep prints the matching lines.

OPTIONS
    -h
    --help
        Print this manual page.

    -v
    --invert-match
        Invert matching.

    -c
    --count
        Print count of matching lines, instead of those lines.

    -n
    --line-number
        Prefix each line of output with the line number of the match.
"#; /* @MANEND */

#[derive(Copy, Clone)]
struct Flags {
    line_numbers: bool,
    invert_match: bool,
    count: bool,
}

impl Flags {
    fn new() -> Flags {
        Flags { line_numbers: false, invert_match: false, count: false }
    }
}

fn main() {
    let args = env::args().skip(1);
    let stdin = io::stdin();
    let stdin = stdin.lock();

    let mut flags = Flags::new();
    let mut pattern = String::new();
    let mut files = Vec::with_capacity(args.len());
    for arg in args {
        if arg.starts_with('-') {
            match arg.as_str() {
                "-h" | "--help" => {
                    print!("{}", MAN_PAGE);
                },
                "-n" | "--line-number" => flags.line_numbers = true,
                "-v" | "--invert-match" => flags.invert_match = true,
                "-c" | "--count" => flags.count = true,
                _ => {
                    eprintln!("Error, unknown option: {}", arg);
                    exit(1);
                }
            }
        } else if pattern.is_empty() {
            pattern = arg.clone();
        } else {
            match File::open(&Path::new(&arg)) {
                Ok(f) => files.push(f),
                Err(err) => {
                    eprintln!("Error opening {}: {}", arg, err);
                    exit(1);
                }
            }
        }
    }

    if pattern.is_empty() {
        eprintln!("You must provide a pattern");
        exit(1);
    }

    if files.is_empty() {
        do_simple_search(BufReader::new(stdin), &pattern, flags);
    } else {
        for f in files {
            do_simple_search(BufReader::new(f), &pattern, flags);
        }
    }
}

fn do_simple_search<T: BufRead>(reader: T, pattern: &str, flags: Flags) {
    let mut count = 0;
    for (line_num, result) in reader.lines().enumerate() {
        if let Ok(line) = result {
            let is_match = if flags.invert_match {
                !line.contains(pattern)
            } else {
                line.contains(pattern)
            };
            if is_match && flags.count {
                count += 1;
            } else if is_match {
                if flags.line_numbers {
                    print!("{}: ", line_num + 1);
                }
                println!("{}", line);
            }
        }
    }

    if flags.count {
        println!("{}", count);
    }
}
<|endoftext|>


<commit_before>use std::io;
use std::io::Write;
use std::env;
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use mdbook::utils;
use mdbook::config;
use get_book_dir;

// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
    SubCommand::with_name("init")
        .about("Create boilerplate structure and files in the directory")
        // the {n} denotes a newline which will properly aligned in all help messages
        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory \
                         when omitted)'")
        .arg_from_usage("--theme 'Copies the default theme into your source folder'")
        .arg_from_usage("--force 'skip confirmation prompts'")
}

// Init command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
    let book_dir = get_book_dir(args);
    let mut builder = MDBook::init(&book_dir);
    let mut config = config::Config::default();

    // If flag `--theme` is present, copy theme to src
    if args.is_present("theme") {
        config.set("output.html.theme", "src/theme")?;
        // Skip this if `--force` is present
        if !args.is_present("force") {
            // Print warning
            println!();
            println!(
                "Copying the default theme to {}",
                builder.config().book.src.display()
            );
            println!("This could potentially overwrite files already present in that directory.");
            print!("\nAre you sure you want to continue? (y/n) ");

            // Read answer from user and exit if it's not 'yes'
            if confirm() {
                builder.copy_theme(true);
            }
        } else {
            builder.copy_theme(true);
        }
    }

    println!("\nDo you want a .gitignore to be created? (y/n)");

    if confirm() {
        builder.create_gitignore(true);
    }

    config.book.title = request_book_title();

    if let Some(author) = get_author_name() {
        debug!("Obtained user name from gitconfig: {:?}", author);
        config.book.authors.push(author);
        builder.with_config(config);
    }

    builder.build()?;
    println!("\nAll done, no errors...");

    Ok(())
}

/// Obtains author name from git config file if it can be located.
fn get_author_name() -> Option<String> {
    if let Some(home) = env::home_dir() {
        let git_config_path = home.join(".gitconfig");
        let content = utils::fs::file_to_string(git_config_path).unwrap();
        let user_name = content
            .lines()
            .filter(|x| !x.starts_with("#"))
            .map(|x| x.trim_left())
            .filter(|x| x.starts_with("name"))
            .next();
        user_name
            .and_then(|x| x.rsplit("=").next())
            .map(|x| x.trim().to_owned())
    } else {
        None
    }
}

/// Request book title from user and return if provided.
fn request_book_title() -> Option<String> {
    println!("What title would you like to give the book? ");
    io::stdout().flush().unwrap();
    let mut resp = String::new();
    io::stdin().read_line(&mut resp).unwrap();
    let resp = resp.trim();
    if resp.is_empty() {
        None
    } else {
        Some(resp.into())
    }
}

// Simple function for user confirmation
fn confirm() -> bool {
    io::stdout().flush().unwrap();
    let mut s = String::new();
    io::stdin().read_line(&mut s).ok();
    match &*s.trim() {
        "Y" | "y" | "yes" | "Yes" => true,
        _ => false,
    }
}
<commit_msg>Use `git config` to get author name in `mdbook init` (#649)<commit_after>use std::io;
use std::io::Write;
use std::process::Command;
use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook;
use mdbook::errors::Result;
use mdbook::utils;
use mdbook::config;
use get_book_dir;

// Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
    SubCommand::with_name("init")
        .about("Create boilerplate structure and files in the directory")
        // the {n} denotes a newline which will properly aligned in all help messages
        .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory \
                         when omitted)'")
        .arg_from_usage("--theme 'Copies the default theme into your source folder'")
        .arg_from_usage("--force 'skip confirmation prompts'")
}

// Init command implementation
pub fn execute(args: &ArgMatches) -> Result<()> {
    let book_dir = get_book_dir(args);
    let mut builder = MDBook::init(&book_dir);
    let mut config = config::Config::default();

    // If flag `--theme` is present, copy theme to src
    if args.is_present("theme") {
        config.set("output.html.theme", "src/theme")?;
        // Skip this if `--force` is present
        if !args.is_present("force") {
            // Print warning
            println!();
            println!(
                "Copying the default theme to {}",
                builder.config().book.src.display()
            );
            println!("This could potentially overwrite files already present in that directory.");
            print!("\nAre you sure you want to continue? (y/n) ");

            // Read answer from user and exit if it's not 'yes'
            if confirm() {
                builder.copy_theme(true);
            }
        } else {
            builder.copy_theme(true);
        }
    }

    println!("\nDo you want a .gitignore to be created? (y/n)");

    if confirm() {
        builder.create_gitignore(true);
    }

    config.book.title = request_book_title();

    if let Some(author) = get_author_name() {
        debug!("Obtained user name from gitconfig: {:?}", author);
        config.book.authors.push(author);
        builder.with_config(config);
    }

    builder.build()?;
    println!("\nAll done, no errors...");

    Ok(())
}

/// Obtains author name from git config file by running the `git config` command.
fn get_author_name() -> Option<String> {
    let output = Command::new("git")
        .args(&["config", "--get", "user.name"])
        .output()
        .ok()?;

    if output.status.success() {
        Some(String::from_utf8_lossy(&output.stdout).trim().to_owned())
    } else {
        None
    }
}

/// Request book title from user and return if provided.
fn request_book_title() -> Option<String> {
    println!("What title would you like to give the book? ");
    io::stdout().flush().unwrap();
    let mut resp = String::new();
    io::stdin().read_line(&mut resp).unwrap();
    let resp = resp.trim();
    if resp.is_empty() {
        None
    } else {
        Some(resp.into())
    }
}

// Simple function for user confirmation
fn confirm() -> bool {
    io::stdout().flush().unwrap();
    let mut s = String::new();
    io::stdin().read_line(&mut s).ok();
    match &*s.trim() {
        "Y" | "y" | "yes" | "Yes" => true,
        _ => false,
    }
}
<|endoftext|>


<commit_before>#![deny(warnings)]
#![feature(fs_time)]
extern crate coreutils;

use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::process::exit;
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};

use coreutils::extra::OptionalExt;

const MAN_PAGE: &'static str = r#"NAME
    test - perform tests on files and text

SYNOPSIS
    test [EXPRESSION]

DESCRIPTION
    Tests the expressions given and returns an exit status of 0 if true, else 1.

OPTIONS
    -n STRING
        the length of STRING is nonzero

    STRING
        equivalent to -n STRING

    -z STRING
        the length of STRING is zero

    STRING = STRING
        the strings are equivalent

    STRING != STRING
        the strings are not equal

    INTEGER -eq INTEGER
        the integers are equal

    INTEGER -ge INTEGER
        the first INTEGER is greater than or equal to the first INTEGER

    INTEGER -gt INTEGER
        the first INTEGER is greater than the first INTEGER

    INTEGER -le INTEGER
        the first INTEGER is less than or equal to the first INTEGER

    INTEGER -lt INTEGER
        the first INTEGER is less than the first INTEGER

    INTEGER -ne INTEGER
        the first INTEGER is not equal to the first INTEGER

    FILE -ef FILE
        both files have the same device and inode numbers

    FILE -nt FILE
        the first FILE is newer than the second FILE

    FILE -ot FILE
        the first file is older than the second FILE

    -b FILE
        FILE exists and is a block device

    -c FILE
        FILE exists and is a character device

    -d FILE
        FILE exists and is a directory

    -e FILE
        FILE exists

    -f FILE
        FILE exists and is a regular file

    -h FILE
        FILE exists and is a symbolic link (same as -L)

    -L FILE
        FILE exists and is a symbolic link (same as -h)

    -r FILE
        FILE exists and read permission is granted

    -s FILE
        FILE exists and has a file size greater than zero

    -w FILE
        FILE exists and write permission is granted

    -x FILE
        FILE exists and execute (or search) permission is granted

EXAMPLES
    Test if the file exists:
        test -e FILE && echo "The FILE exists" || echo "The FILE does not exist"

    Test if the file exists and is a regular file, and if so, write to it:
        test -f FILE && echo "Hello, FILE" >> FILE || echo "Cannot write to a directory"

    Test if 10 is greater than 5:
        test 10 -gt 5 && echo "10 is greater than 5" || echo "10 is not greater than 5"

    Test if the user is running a 64-bit OS (POSIX environment only):
        test $(getconf LONG_BIT) = 64 && echo "64-bit OS" || echo "32-bit OS"

AUTHOR
    Written by Michael Murphy.
"#;

const SUCCESS: i32 = 0;
const FAILED:  i32 = 1;

fn main() {
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let mut stderr = io::stderr();
    let mut arguments = std::env::args().skip(1);
    // TODO: Implement support for evaluating multiple expressions
    if let Some(first_argument) = arguments.next() {
        if first_argument.as_str() == "--help" {
            stdout.write_all(MAN_PAGE.as_bytes()).try(&mut stderr);
            stdout.flush().try(&mut stderr);
            exit(SUCCESS);
        }
        let mut characters = first_argument.chars().take(2);
        match characters.next().unwrap() {
            '-' => match_flag_argument(characters.next(), arguments.next()),
            _   => evaluate_expression(first_argument.as_str(), arguments.next(), arguments.next(),
                                       &mut stderr),
        }
    } else {
        exit(FAILED);
    }
}

/// Evaluate an expression of `VALUE -OPERATOR VALUE`.
fn evaluate_expression(first: &str, operator: Option<String>, second_argument: Option<String>,
                       stderr: &mut io::Stderr) {
    match operator {
        Some(op) => {
            let op = op.as_str();
            match second_argument {
                Some(second) => {
                    let second = second.as_str();
                    match op {
                        "=" | "==" => evaluate_bool(first == second),
                        "!=" => evaluate_bool(first != second),
                        "-eq" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left == right);
                        },
                        "-ge" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left >= right);
                        },
                        "-gt" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left > right);
                        },
                        "-le" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left <= right);
                        },
                        "-lt" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left < right);
                        },
                        "-ne" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left != right)
                        },
                        "-ef" => files_have_same_device_and_inode_numbers(first, second),
                        "-nt" => file_is_newer_than(first, second),
                        "-ot" => file_is_older_than(first, second),
                        _          => {
                            stderr.write_all(b"unknown condition: ").try(stderr);
                            stderr.write_all(op.as_bytes()).try(stderr);
                            stderr.write_all(&[b'\n']).try(stderr);
                            stderr.flush().try(stderr);
                            exit(FAILED);
                        }
                    }
                },
                None => {
                    stderr.write_all(b"parse error: condition expected\n").try(stderr);
                    stderr.flush().try(stderr);
                    exit(FAILED);
                }
            }
        },
        None => string_is_nonzero(Some(String::from(first)))
    }
}

/// Exits SUCCESS if both files have the same device and inode numbers
fn files_have_same_device_and_inode_numbers(first: &str, second: &str) {
    evaluate_bool(get_dev_and_inode(first) == get_dev_and_inode(second));
}

/// Obtains the device and inode numbers of the file specified
fn get_dev_and_inode(filename: &str) -> (u64, u64) {
    match fs::metadata(filename) {
        Ok(file) => (file.dev(), file.ino()),
        Err(_)   => exit(FAILED)
    }
}

/// Exits SUCCESS if the first file is newer than the second file.
fn file_is_newer_than(first: &str, second: &str) {
    evaluate_bool(get_modified_file_time(first) < get_modified_file_time(second));
}

/// Exits SUCCESS if the first file is older than the second file.
fn file_is_older_than(first: &str, second: &str) {
    evaluate_bool(get_modified_file_time(first) < get_modified_file_time(second));
}

/// Obtain the time the file was last modified as a `SystemTime` type.
fn get_modified_file_time(filename: &str) -> std::time::SystemTime {
    match fs::metadata(filename) {
        Ok(file) => match file.modified() {
            Ok(time) => return time,
            Err(_)   => exit(FAILED)
        },
        Err(_) => exit(FAILED)
    }
}

/// Attempt to parse a &str as a usize.
fn parse_integers(left: &str, right: &str, stderr: &mut io::Stderr) -> (usize, usize) {
    let mut parse_integer = |input: &str| -> usize {
        if let Ok(integer) = input.parse::<usize>() {
            integer
        } else {
            stderr.write_all(b"integer expression expected: ").try(stderr);
            stderr.write_all(input.as_bytes()).try(stderr);
            stderr.write_all(&[b'\n']).try(stderr);
            stderr.flush().try(stderr);
            exit(FAILED);
        }
    };
    (parse_integer(left), parse_integer(right))
}

/// Matches flag arguments to their respective functionaity when the `-` character is detected.
fn match_flag_argument(character: Option<char>, arguments: Option<String>) {
    if let Some(second_character) = character {
        // TODO: Implement missing flags
        match second_character {
            'b' => file_is_block_device(arguments),
            'c' => file_is_character_device(arguments),
            'd' => file_is_directory(arguments),
            'e' => file_exists(arguments),
            'f' => file_is_regular(arguments),
            //'g' => file_is_set_group_id(arguments),
            //'G' => file_is_owned_by_effective_group_id(arguments),
            'h' => file_is_symlink(arguments),
            //'k' => file_has_sticky_bit(arguments),
            'L' => file_is_symlink(arguments),
            //'O' => file_is_owned_by_effective_user_id(arguments),
            //'p' => file_is_named_pipe(arguments),
            'r' => file_has_read_permission(arguments),
            's' => file_size_is_greater_than_zero(arguments),
            'S' => file_is_socket(arguments),
            //'t' => file_descriptor_is_opened_on_a_terminal(arguments),
            'w' => file_has_write_permission(arguments),
            'x' => file_has_execute_permission(arguments),
            'n' => string_is_nonzero(arguments),
            'z' => string_is_zero(arguments),
            _ => exit(SUCCESS),
        }
    } else {
        exit(SUCCESS);
    }
}

/// Exits SUCCESS if the file size is greather than zero.
fn file_size_is_greater_than_zero(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.len() > 0),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file has read permissions. This function is rather low level because
/// Rust currently does not have a higher level abstraction for obtaining non-standard file modes.
/// To extract the permissions from the mode, the bitwise AND operator will be used and compared
/// with the respective read bits.
fn file_has_read_permission(file: Option<String>) {
    const USER_BIT:  u32 = 0b100000000;
    const GROUP_BIT: u32 = 0b100000;
    const GUEST_BIT: u32 = 0b100;

    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => {
                let permissions = metadata.permissions().mode();
                if permissions & USER_BIT == USER_BIT { exit(SUCCESS); }
                if permissions & GROUP_BIT == GROUP_BIT { exit(SUCCESS); }
                if permissions & GUEST_BIT == GUEST_BIT { exit(SUCCESS); }
                exit(FAILED);
            },
            Err(_) => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file has write permissions. This function is rather low level because
/// Rust currently does not have a higher level abstraction for obtaining non-standard file modes.
/// To extract the permissions from the mode, the bitwise AND operator will be used and compared
/// with the respective write bits.
fn file_has_write_permission(file: Option<String>) {
    const USER_BIT:  u32 = 0b10000000;
    const GROUP_BIT: u32 = 0b10000;
    const GUEST_BIT: u32 = 0b10;

    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => {
                let permissions = metadata.permissions().mode();
                if permissions & USER_BIT == USER_BIT { exit(SUCCESS); }
                if permissions & GROUP_BIT == GROUP_BIT { exit(SUCCESS); }
                if permissions & GUEST_BIT == GUEST_BIT { exit(SUCCESS); }
                exit(FAILED);
            },
            Err(_) => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file has execute permissions. This function is rather low level because
/// Rust currently does not have a higher level abstraction for obtaining non-standard file modes.
/// To extract the permissions from the mode, the bitwise AND operator will be used and compared
/// with the respective execute bits.
fn file_has_execute_permission(file: Option<String>) {
    const USER_BIT:  u32 = 0b1000000;
    const GROUP_BIT: u32 = 0b1000;
    const GUEST_BIT: u32 = 0b1;

    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => {
                let permissions = metadata.permissions().mode();
                if permissions & USER_BIT == USER_BIT { exit(SUCCESS); }
                if permissions & GROUP_BIT == GROUP_BIT { exit(SUCCESS); }
                if permissions & GUEST_BIT == GUEST_BIT { exit(SUCCESS); }
                exit(FAILED);
            },
            Err(_) => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file argument is a socket
fn file_is_socket(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_socket()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file argument is a block device
fn file_is_block_device(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_block_device()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file argument is a character device
fn file_is_character_device(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_char_device()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file exists
fn file_exists(file: Option<String>) {
    match file {
        Some(filepath) => evaluate_bool(Path::new(&filepath).exists()),
        None           => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file is a regular file
fn file_is_regular(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_file()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file is a directory
fn file_is_directory(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_dir()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file is a symbolic link
fn file_is_symlink(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_symlink()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the string is not empty
fn string_is_nonzero(string: Option<String>) {
    match string {
        Some(string) => evaluate_bool(!string.is_empty()),
        None         => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the string is empty
fn string_is_zero(string: Option<String>) {
    match string {
        Some(string) => evaluate_bool(string.is_empty()),
        None         => exit(SUCCESS)
    }
}

// Convert a boolean to it's respective exit code.
fn evaluate_bool(input: bool) { if input { exit(SUCCESS); } else { exit(FAILED); } }
<commit_msg>Add -S to MAN_PAGE<commit_after>#![deny(warnings)]
#![feature(fs_time)]
extern crate coreutils;

use std::fs;
use std::io::{self, Write};
use std::path::Path;
use std::process::exit;
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};

use coreutils::extra::OptionalExt;

const MAN_PAGE: &'static str = r#"NAME
    test - perform tests on files and text

SYNOPSIS
    test [EXPRESSION]

DESCRIPTION
    Tests the expressions given and returns an exit status of 0 if true, else 1.

OPTIONS
    -n STRING
        the length of STRING is nonzero

    STRING
        equivalent to -n STRING

    -z STRING
        the length of STRING is zero

    STRING = STRING
        the strings are equivalent

    STRING != STRING
        the strings are not equal

    INTEGER -eq INTEGER
        the integers are equal

    INTEGER -ge INTEGER
        the first INTEGER is greater than or equal to the first INTEGER

    INTEGER -gt INTEGER
        the first INTEGER is greater than the first INTEGER

    INTEGER -le INTEGER
        the first INTEGER is less than or equal to the first INTEGER

    INTEGER -lt INTEGER
        the first INTEGER is less than the first INTEGER

    INTEGER -ne INTEGER
        the first INTEGER is not equal to the first INTEGER

    FILE -ef FILE
        both files have the same device and inode numbers

    FILE -nt FILE
        the first FILE is newer than the second FILE

    FILE -ot FILE
        the first file is older than the second FILE

    -b FILE
        FILE exists and is a block device

    -c FILE
        FILE exists and is a character device

    -d FILE
        FILE exists and is a directory

    -e FILE
        FILE exists

    -f FILE
        FILE exists and is a regular file

    -h FILE
        FILE exists and is a symbolic link (same as -L)

    -L FILE
        FILE exists and is a symbolic link (same as -h)

    -r FILE
        FILE exists and read permission is granted

    -s FILE
        FILE exists and has a file size greater than zero

    -S FILE
        FILE exists and is a socket

    -w FILE
        FILE exists and write permission is granted

    -x FILE
        FILE exists and execute (or search) permission is granted

EXAMPLES
    Test if the file exists:
        test -e FILE && echo "The FILE exists" || echo "The FILE does not exist"

    Test if the file exists and is a regular file, and if so, write to it:
        test -f FILE && echo "Hello, FILE" >> FILE || echo "Cannot write to a directory"

    Test if 10 is greater than 5:
        test 10 -gt 5 && echo "10 is greater than 5" || echo "10 is not greater than 5"

    Test if the user is running a 64-bit OS (POSIX environment only):
        test $(getconf LONG_BIT) = 64 && echo "64-bit OS" || echo "32-bit OS"

AUTHOR
    Written by Michael Murphy.
"#;

const SUCCESS: i32 = 0;
const FAILED:  i32 = 1;

fn main() {
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let mut stderr = io::stderr();
    let mut arguments = std::env::args().skip(1);
    // TODO: Implement support for evaluating multiple expressions
    if let Some(first_argument) = arguments.next() {
        if first_argument.as_str() == "--help" {
            stdout.write_all(MAN_PAGE.as_bytes()).try(&mut stderr);
            stdout.flush().try(&mut stderr);
            exit(SUCCESS);
        }
        let mut characters = first_argument.chars().take(2);
        match characters.next().unwrap() {
            '-' => match_flag_argument(characters.next(), arguments.next()),
            _   => evaluate_expression(first_argument.as_str(), arguments.next(), arguments.next(),
                                       &mut stderr),
        }
    } else {
        exit(FAILED);
    }
}

/// Evaluate an expression of `VALUE -OPERATOR VALUE`.
fn evaluate_expression(first: &str, operator: Option<String>, second_argument: Option<String>,
                       stderr: &mut io::Stderr) {
    match operator {
        Some(op) => {
            let op = op.as_str();
            match second_argument {
                Some(second) => {
                    let second = second.as_str();
                    match op {
                        "=" | "==" => evaluate_bool(first == second),
                        "!=" => evaluate_bool(first != second),
                        "-eq" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left == right);
                        },
                        "-ge" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left >= right);
                        },
                        "-gt" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left > right);
                        },
                        "-le" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left <= right);
                        },
                        "-lt" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left < right);
                        },
                        "-ne" => {
                            let (left, right) = parse_integers(first, second, stderr);
                            evaluate_bool(left != right)
                        },
                        "-ef" => files_have_same_device_and_inode_numbers(first, second),
                        "-nt" => file_is_newer_than(first, second),
                        "-ot" => file_is_older_than(first, second),
                        _          => {
                            stderr.write_all(b"unknown condition: ").try(stderr);
                            stderr.write_all(op.as_bytes()).try(stderr);
                            stderr.write_all(&[b'\n']).try(stderr);
                            stderr.flush().try(stderr);
                            exit(FAILED);
                        }
                    }
                },
                None => {
                    stderr.write_all(b"parse error: condition expected\n").try(stderr);
                    stderr.flush().try(stderr);
                    exit(FAILED);
                }
            }
        },
        None => string_is_nonzero(Some(String::from(first)))
    }
}

/// Exits SUCCESS if both files have the same device and inode numbers
fn files_have_same_device_and_inode_numbers(first: &str, second: &str) {
    evaluate_bool(get_dev_and_inode(first) == get_dev_and_inode(second));
}

/// Obtains the device and inode numbers of the file specified
fn get_dev_and_inode(filename: &str) -> (u64, u64) {
    match fs::metadata(filename) {
        Ok(file) => (file.dev(), file.ino()),
        Err(_)   => exit(FAILED)
    }
}

/// Exits SUCCESS if the first file is newer than the second file.
fn file_is_newer_than(first: &str, second: &str) {
    evaluate_bool(get_modified_file_time(first) < get_modified_file_time(second));
}

/// Exits SUCCESS if the first file is older than the second file.
fn file_is_older_than(first: &str, second: &str) {
    evaluate_bool(get_modified_file_time(first) < get_modified_file_time(second));
}

/// Obtain the time the file was last modified as a `SystemTime` type.
fn get_modified_file_time(filename: &str) -> std::time::SystemTime {
    match fs::metadata(filename) {
        Ok(file) => match file.modified() {
            Ok(time) => return time,
            Err(_)   => exit(FAILED)
        },
        Err(_) => exit(FAILED)
    }
}

/// Attempt to parse a &str as a usize.
fn parse_integers(left: &str, right: &str, stderr: &mut io::Stderr) -> (usize, usize) {
    let mut parse_integer = |input: &str| -> usize {
        if let Ok(integer) = input.parse::<usize>() {
            integer
        } else {
            stderr.write_all(b"integer expression expected: ").try(stderr);
            stderr.write_all(input.as_bytes()).try(stderr);
            stderr.write_all(&[b'\n']).try(stderr);
            stderr.flush().try(stderr);
            exit(FAILED);
        }
    };
    (parse_integer(left), parse_integer(right))
}

/// Matches flag arguments to their respective functionaity when the `-` character is detected.
fn match_flag_argument(character: Option<char>, arguments: Option<String>) {
    if let Some(second_character) = character {
        // TODO: Implement missing flags
        match second_character {
            'b' => file_is_block_device(arguments),
            'c' => file_is_character_device(arguments),
            'd' => file_is_directory(arguments),
            'e' => file_exists(arguments),
            'f' => file_is_regular(arguments),
            //'g' => file_is_set_group_id(arguments),
            //'G' => file_is_owned_by_effective_group_id(arguments),
            'h' => file_is_symlink(arguments),
            //'k' => file_has_sticky_bit(arguments),
            'L' => file_is_symlink(arguments),
            //'O' => file_is_owned_by_effective_user_id(arguments),
            //'p' => file_is_named_pipe(arguments),
            'r' => file_has_read_permission(arguments),
            's' => file_size_is_greater_than_zero(arguments),
            'S' => file_is_socket(arguments),
            //'t' => file_descriptor_is_opened_on_a_terminal(arguments),
            'w' => file_has_write_permission(arguments),
            'x' => file_has_execute_permission(arguments),
            'n' => string_is_nonzero(arguments),
            'z' => string_is_zero(arguments),
            _ => exit(SUCCESS),
        }
    } else {
        exit(SUCCESS);
    }
}

/// Exits SUCCESS if the file size is greather than zero.
fn file_size_is_greater_than_zero(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.len() > 0),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file has read permissions. This function is rather low level because
/// Rust currently does not have a higher level abstraction for obtaining non-standard file modes.
/// To extract the permissions from the mode, the bitwise AND operator will be used and compared
/// with the respective read bits.
fn file_has_read_permission(file: Option<String>) {
    const USER_BIT:  u32 = 0b100000000;
    const GROUP_BIT: u32 = 0b100000;
    const GUEST_BIT: u32 = 0b100;

    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => {
                let permissions = metadata.permissions().mode();
                if permissions & USER_BIT == USER_BIT { exit(SUCCESS); }
                if permissions & GROUP_BIT == GROUP_BIT { exit(SUCCESS); }
                if permissions & GUEST_BIT == GUEST_BIT { exit(SUCCESS); }
                exit(FAILED);
            },
            Err(_) => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file has write permissions. This function is rather low level because
/// Rust currently does not have a higher level abstraction for obtaining non-standard file modes.
/// To extract the permissions from the mode, the bitwise AND operator will be used and compared
/// with the respective write bits.
fn file_has_write_permission(file: Option<String>) {
    const USER_BIT:  u32 = 0b10000000;
    const GROUP_BIT: u32 = 0b10000;
    const GUEST_BIT: u32 = 0b10;

    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => {
                let permissions = metadata.permissions().mode();
                if permissions & USER_BIT == USER_BIT { exit(SUCCESS); }
                if permissions & GROUP_BIT == GROUP_BIT { exit(SUCCESS); }
                if permissions & GUEST_BIT == GUEST_BIT { exit(SUCCESS); }
                exit(FAILED);
            },
            Err(_) => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file has execute permissions. This function is rather low level because
/// Rust currently does not have a higher level abstraction for obtaining non-standard file modes.
/// To extract the permissions from the mode, the bitwise AND operator will be used and compared
/// with the respective execute bits.
fn file_has_execute_permission(file: Option<String>) {
    const USER_BIT:  u32 = 0b1000000;
    const GROUP_BIT: u32 = 0b1000;
    const GUEST_BIT: u32 = 0b1;

    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => {
                let permissions = metadata.permissions().mode();
                if permissions & USER_BIT == USER_BIT { exit(SUCCESS); }
                if permissions & GROUP_BIT == GROUP_BIT { exit(SUCCESS); }
                if permissions & GUEST_BIT == GUEST_BIT { exit(SUCCESS); }
                exit(FAILED);
            },
            Err(_) => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file argument is a socket
fn file_is_socket(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_socket()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file argument is a block device
fn file_is_block_device(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_block_device()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file argument is a character device
fn file_is_character_device(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_char_device()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file exists
fn file_exists(file: Option<String>) {
    match file {
        Some(filepath) => evaluate_bool(Path::new(&filepath).exists()),
        None           => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file is a regular file
fn file_is_regular(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_file()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file is a directory
fn file_is_directory(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_dir()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the file is a symbolic link
fn file_is_symlink(file: Option<String>) {
    match file {
        Some(filepath) => match fs::metadata(filepath) {
            Ok(metadata) => evaluate_bool(metadata.file_type().is_symlink()),
            Err(_)       => exit(FAILED)
        },
        None => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the string is not empty
fn string_is_nonzero(string: Option<String>) {
    match string {
        Some(string) => evaluate_bool(!string.is_empty()),
        None         => exit(SUCCESS)
    }
}

/// Exits SUCCESS if the string is empty
fn string_is_zero(string: Option<String>) {
    match string {
        Some(string) => evaluate_bool(string.is_empty()),
        None         => exit(SUCCESS)
    }
}

// Convert a boolean to it's respective exit code.
fn evaluate_bool(input: bool) { if input { exit(SUCCESS); } else { exit(FAILED); } }
<|endoftext|>


<commit_before>use hyper::server::Request;
use std::sync::Mutex;
extern crate redis;
extern crate byteorder;

use config::*;
use common::*;
use self::byteorder::{BigEndian, WriteBytesExt};
use std::net::IpAddr;
use std::net::Ipv4Addr;
extern crate time;
use std::error::Error;

pub fn announce(req: &Request, redis_connection: &Mutex<redis::Connection>) -> Result<Vec<u8>, String>
{
    let query_hashmap = query_hashmap(&req.uri);

    // Check we have everything we need
    let info_hash = try!(query_hashmap.get("info_hash")
        .ok_or_else(|| "No info_hash specified".to_owned())
        .and_then(|i| parse_info_hash(i)));
    let peer_id = try!(query_hashmap.get("peer_id")
        .ok_or_else(|| "No peer_id specified".to_owned())
        .and_then(|i| parse_peer_id(i)));
    let port = try!(query_hashmap.get("port")
        .ok_or_else(|| "No port specified".to_owned())
        .and_then(|i| i.parse::<u16>().map_err(|_| "Invalid port specified".to_owned())));
    let left = try!(query_hashmap.get("left")
        .ok_or_else(|| "No left specified".to_owned())
        .and_then(|i| i.parse::<u64>().map_err(|_| "Invalid left specified".to_owned())));
    let compact = try!(query_hashmap.get("compact")
        .ok_or_else(|| "No compact specified".to_owned())
        .and_then(|i| i.parse::<u64>().map_err(|_| "Invalid compact specified".to_owned())));
    if compact != 1 {
        return Err("This tracker only supports compact responses".to_owned());
    }
    let numwant = try!(query_hashmap.get("numwant")
        .unwrap_or(&"50")
        .parse::<u64>().map_err(|_| "Invalid numwant specified".to_owned()));
    let ip = req.remote_addr.ip();

    // Generate compact peer entry
    let ip_v4 = match ip {
        IpAddr::V4(i) => i,
        _ => return Err("Ipv6 not implemented yet".to_owned()),
    };
    let peer_v4 = generate_peer_ipv4(ip_v4, port);

    // Get ready for redis queries
    let key_base = format!("torrent:{}", info_hash);
    let key_seeds = format!("{}:seeds", key_base);
    let key_peers = format!("{}:peers", key_base);
    let time_now = time::get_time().sec*1000;
    let time_drop = time_now - DROP_THRESHOLD*1000;
    let mut pipe = redis::pipe();

    // Prune out old entries
    pipe.cmd("ZREMRANGEBYSCORE").arg(&*key_seeds).arg(0).arg(time_drop).ignore();
    pipe.cmd("ZREMRANGEBYSCORE").arg(&*key_peers).arg(0).arg(time_drop).ignore();

    // Get total count of peers/seeds
    pipe.cmd("ZCARD").arg(&*key_seeds);
    pipe.cmd("ZCARD").arg(&*key_peers);

    // Get peers and seeds
    pipe.cmd("ZRANGE").arg(&*key_seeds).arg(0).arg(numwant);
    pipe.cmd("ZRANGE").arg(&*key_peers).arg(0).arg(numwant);

    // Add
    if left == 0 {
        pipe.cmd("ZADD").arg(&*key_seeds).arg(time_now).arg(peer_v4).ignore();
    }
    else {
        pipe.cmd("ZADD").arg(&*key_peers).arg(time_now).arg(peer_v4).ignore();
    }

    // Unlock mutex and go!
    let results: (u32, u32, Vec<Vec<u8>>, Vec<Vec<u8>>);
    {
        let r = redis_connection.lock().unwrap();
        results = try!(pipe.query(&(*r))
            .map_err(|e| format!("Redis error: {}", e.description())));
    }

    let (total_seeds, total_peers, seeds, peers) = results;
    debug!("  info_hash={}, seeds={}, peers={}", info_hash, total_seeds, total_peers);

    // Begin building output
    let mut response: Vec<u8> = Vec::new();
    response.extend(format!("d8:completei{}e10:incompletei{}e8:intervali{}e5:peers",
        total_seeds, total_peers, ANNOUNCE_INTERVAL).bytes());

    // dont give seeds to seeds
    if left == 0 {
        response.extend(format!("{}:", 6*peers.len()).bytes());
        for i in peers {
            response.extend(i);
        }
    }
    else {
        response.extend(format!("{}:", 6*seeds.len() + 6*peers.len()).bytes());
        for i in seeds {
            response.extend(i);
        }
        for i in peers {
            response.extend(i);
        }
    }
    response.extend(b"e");

    return Ok(response);
}

fn generate_peer_ipv4(ip: Ipv4Addr, port: u16) -> Vec<u8> {
    let mut retval = Vec::with_capacity(6);
    retval.extend(ip.octets().iter());
    retval.write_u16::<BigEndian>(port).unwrap();
    return retval;
}

#[test]
fn generate_peer_ipv4_test() {
    assert_eq!(generate_peer_ipv4(Ipv4Addr::new(127, 0, 0, 1), 0x3039), &[127, 0, 0, 1, 0x30, 0x39]);
}
<commit_msg>announce: add 'ip' parameter<commit_after>use hyper::server::Request;
use std::sync::Mutex;
extern crate redis;
extern crate byteorder;

use config::*;
use common::*;
use self::byteorder::{BigEndian, WriteBytesExt};
use std::net::IpAddr;
use std::net::Ipv4Addr;
extern crate time;
use std::error::Error;
use std::str::FromStr;

pub fn announce(req: &Request, redis_connection: &Mutex<redis::Connection>) -> Result<Vec<u8>, String>
{
    let query_hashmap = query_hashmap(&req.uri);

    // Check we have everything we need
    let info_hash = try!(query_hashmap.get("info_hash")
        .ok_or_else(|| "No info_hash specified".to_owned())
        .and_then(|i| parse_info_hash(i)));
    let peer_id = try!(query_hashmap.get("peer_id")
        .ok_or_else(|| "No peer_id specified".to_owned())
        .and_then(|i| parse_peer_id(i)));
    let port = try!(query_hashmap.get("port")
        .ok_or_else(|| "No port specified".to_owned())
        .and_then(|i| i.parse::<u16>().map_err(|_| "Invalid port specified".to_owned())));
    let left = try!(query_hashmap.get("left")
        .ok_or_else(|| "No left specified".to_owned())
        .and_then(|i| i.parse::<u64>().map_err(|_| "Invalid left specified".to_owned())));
    let compact = try!(query_hashmap.get("compact")
        .ok_or_else(|| "No compact specified".to_owned())
        .and_then(|i| i.parse::<u64>().map_err(|_| "Invalid compact specified".to_owned())));
    if compact != 1 {
        return Err("This tracker only supports compact responses".to_owned());
    }
    let numwant = try!(query_hashmap.get("numwant")
        .unwrap_or(&"50")
        .parse::<u64>().map_err(|_| "Invalid numwant specified".to_owned()));

    // Get ip
    let ip;
    if query_hashmap.contains_key("ip") {
        ip = try!(IpAddr::from_str(query_hashmap.get("ip").unwrap())
            .map_err(|_| "Invalid ip specified".to_owned()));
    }
    else {
        ip = req.remote_addr.ip();
    }

    // Generate compact peer entry
    let ip_v4 = match ip {
        IpAddr::V4(i) => i,
        _ => return Err("Ipv6 not implemented yet".to_owned()),
    };
    let peer_v4 = generate_peer_ipv4(ip_v4, port);

    // Get ready for redis queries
    let key_base = format!("torrent:{}", info_hash);
    let key_seeds = format!("{}:seeds", key_base);
    let key_peers = format!("{}:peers", key_base);
    let time_now = time::get_time().sec*1000;
    let time_drop = time_now - DROP_THRESHOLD*1000;
    let mut pipe = redis::pipe();

    // Prune out old entries
    pipe.cmd("ZREMRANGEBYSCORE").arg(&*key_seeds).arg(0).arg(time_drop).ignore();
    pipe.cmd("ZREMRANGEBYSCORE").arg(&*key_peers).arg(0).arg(time_drop).ignore();

    // Get total count of peers/seeds
    pipe.cmd("ZCARD").arg(&*key_seeds);
    pipe.cmd("ZCARD").arg(&*key_peers);

    // Get peers and seeds
    pipe.cmd("ZRANGE").arg(&*key_seeds).arg(0).arg(numwant);
    pipe.cmd("ZRANGE").arg(&*key_peers).arg(0).arg(numwant);

    // Add
    if left == 0 {
        pipe.cmd("ZADD").arg(&*key_seeds).arg(time_now).arg(peer_v4).ignore();
    }
    else {
        pipe.cmd("ZADD").arg(&*key_peers).arg(time_now).arg(peer_v4).ignore();
    }

    // Unlock mutex and go!
    let results: (u32, u32, Vec<Vec<u8>>, Vec<Vec<u8>>);
    {
        let r = redis_connection.lock().unwrap();
        results = try!(pipe.query(&(*r))
            .map_err(|e| format!("Redis error: {}", e.description())));
    }

    let (total_seeds, total_peers, seeds, peers) = results;
    debug!("  info_hash={}, seeds={}, peers={}", info_hash, total_seeds, total_peers);

    // Begin building output
    let mut response: Vec<u8> = Vec::new();
    response.extend(format!("d8:completei{}e10:incompletei{}e8:intervali{}e5:peers",
        total_seeds, total_peers, ANNOUNCE_INTERVAL).bytes());

    // dont give seeds to seeds
    if left == 0 {
        response.extend(format!("{}:", 6*peers.len()).bytes());
        for i in peers {
            response.extend(i);
        }
    }
    else {
        response.extend(format!("{}:", 6*seeds.len() + 6*peers.len()).bytes());
        for i in seeds {
            response.extend(i);
        }
        for i in peers {
            response.extend(i);
        }
    }
    response.extend(b"e");

    return Ok(response);
}

fn generate_peer_ipv4(ip: Ipv4Addr, port: u16) -> Vec<u8> {
    let mut retval = Vec::with_capacity(6);
    retval.extend(ip.octets().iter());
    retval.write_u16::<BigEndian>(port).unwrap();
    return retval;
}

#[test]
fn generate_peer_ipv4_test() {
    assert_eq!(generate_peer_ipv4(Ipv4Addr::new(127, 0, 0, 1), 0x3039), &[127, 0, 0, 1, 0x30, 0x39]);
}
<|endoftext|>