<commit_before>// .env parsing

extern crate dotenv;
// command line argument parsing

extern crate clap;
// logging macros

#[macro_use]
extern crate log;
// console logging output

extern crate chrono;
extern crate fern;
// sockets

extern crate futures;
extern crate tokio_core;
extern crate websocket;
// Screeps API

extern crate screeps_api;
// json pretty printing

extern crate serde_json;

use std::borrow::Cow;

use futures::{future, stream, Future, Sink, Stream};

use websocket::OwnedMessage;

use screeps_api::websocket::{Channel, ChannelUpdate, ScreepsMessage, SockjsMessage};
use screeps_api::{RoomName, TokenStorage};

/// Set up dotenv and retrieve a specific variable, informatively panicking if it does not exist.
fn env(var: &str) -> String {
    dotenv::dotenv().ok();
    match ::std::env::var(var) {
        Ok(value) => value,
        Err(e) => panic!("must have `{}` defined (err: {:?})", var, e),
    }
}

fn setup_logging(verbosity: u64) {
    let log_level = match verbosity {
        0 => log::LevelFilter::Warn,
        1 => log::LevelFilter::Info,
        _ => log::LevelFilter::Debug,
    };
    fern::Dispatch::new()
        .level(log_level)
        .level_for("rustls", log::LevelFilter::Warn)
        .level_for("hyper", log::LevelFilter::Warn)
        .format(|out, message, record| {
            let now = chrono::Local::now();

            out.finish(format_args!("[{}][{}] {}: {}",
                                    now.format("%H:%M:%S"),
                                    record.level(),
                                    record.target(),
                                    message));
        })
        .chain(std::io::stdout())
        .apply()
        // ignore errors
        .unwrap_or(());
}

#[derive(Clone, Debug)]
struct Config {
    cpu: bool,
    messages: bool,
    credits: bool,
    console: bool,
    shard: Option<Cow<'static, str>>,
    rooms: Vec<RoomName>,
    map_view: Vec<RoomName>,
    url: Cow<'static, str>,
}

impl Config {
    fn new<'a>(
        args: &'a clap::ArgMatches,
    ) -> Result<Self, screeps_api::data::room_name::RoomNameParseError<'a>> {
        #[allow(unused_imports)] // becomes unnecessary for rust 1.23+
        use std::ascii::AsciiExt;

        Ok(Config {
            cpu: args.is_present("cpu"),
            messages: args.is_present("messages"),
            credits: args.is_present("credits"),
            console: args.is_present("console"),
            shard: args
                .value_of("shard")
                .map(|v| {
                    if "none".eq_ignore_ascii_case(v) {
                        None
                    } else {
                        Some(v.to_owned().into())
                    }
                })
                .unwrap_or_else(|| Some("shard0".into())),
            rooms: args
                .values_of("room")
                .map(|it| it.map(|v| RoomName::new(v)).collect::<Result<_, _>>())
                .unwrap_or_else(|| Ok(Vec::new()))?,
            map_view: args
                .values_of("map-view")
                .map(|it| it.map(|v| RoomName::new(v)).collect::<Result<_, _>>())
                .unwrap_or_else(|| Ok(Vec::new()))?,
            url: args
                .value_of("url")
                .map(|v| v.to_owned().into())
                .unwrap_or_else(|| screeps_api::DEFAULT_OFFICIAL_API_URL.into()),
        })
    }

    fn subscribe_with(
        &self,
        id: &str,
    ) -> Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>> {
        use screeps_api::websocket::subscribe;

        let mut messages = Vec::with_capacity(
            1 + self.cpu as usize
                + self.messages as usize
                + self.credits as usize
                + self.console as usize
                + self.rooms.len()
                + self.map_view.len(),
        );

        messages.push(subscribe(&Channel::ServerMessages));

        if self.cpu {
            messages.push(subscribe(&Channel::user_cpu(id)));
        }

        if self.messages {
            messages.push(subscribe(&Channel::user_messages(id)));
            messages.push(subscribe(&Channel::user_conversation(
                id,
                "57fb16b6e4dd183b746435b0",
            )));
        }

        if self.credits {
            messages.push(subscribe(&Channel::user_credits(id)));
        }

        if self.console {
            messages.push(subscribe(&Channel::user_console(id)));
        }

        for room_name in &self.rooms {
            messages.push(subscribe(&Channel::room_detail(
                *room_name,
                self.shard.as_ref().map(AsRef::as_ref),
            )));
        }

        for room_name in &self.map_view {
            messages.push(subscribe(&Channel::room_map_view(
                *room_name,
                self.shard.as_ref().map(AsRef::as_ref),
            )));
        }

        Box::new(stream::iter_ok(
            messages.into_iter().map(OwnedMessage::Text),
        ))
    }
}

fn setup() -> Config {
    let cmd_arguments = clap::App::new("ws-debug")
        .arg(
            clap::Arg::with_name("verbose")
                .short("v")
                .long("verbose")
                .multiple(true)
                .help("Enables verbose logging"),
        )
        .arg(
            clap::Arg::with_name("cpu")
                .short("p")
                .long("cpu")
                .help("Subscribe to user cpu and memory updates"),
        )
        .arg(
            clap::Arg::with_name("credits")
                .short("c")
                .long("credits")
                .help("Subscribe to per-tick user credit updates"),
        )
        .arg(
            clap::Arg::with_name("console")
                .short("o")
                .long("console")
                .help("Subscribe to user console messages"),
        )
        .arg(
            clap::Arg::with_name("messages")
                .short("e")
                .long("messages")
                .help("Subscribe to user message alerts"),
        )
        .arg(
            clap::Arg::with_name("shard")
                .short("s")
                .long("shard")
                .value_name("SHARD_NAME")
                .help("Sets the shard (default shard0, use 'None' for no shard)")
                .takes_value(true),
        )
        .arg(
            clap::Arg::with_name("room")
                .short("r")
                .long("room")
                .value_name("ROOM_NAME")
                .help("Subscribes to a room")
                .takes_value(true)
                .multiple(true),
        )
        .arg(
            clap::Arg::with_name("map-view")
                .short("m")
                .long("map-view")
                .value_name("ROOM_NAME")
                .help("Subscribes to a map-view room")
                .takes_value(true)
                .multiple(true),
        )
        .arg(
            clap::Arg::with_name("url")
                .short("u")
                .long("url")
                .value_name("API_URL")
                .help("Server url to connect to")
                .takes_value(true),
        )
        .get_matches();

    setup_logging(cmd_arguments.occurrences_of("verbose"));

    match Config::new(&cmd_arguments) {
        Ok(v) => v,
        Err(e) => clap::Error {
            message: e.to_string(),
            kind: clap::ErrorKind::InvalidValue,
            info: None,
        }
        .exit(),
    }
}

fn main() {
    let config = setup();

    let token_storage = screeps_api::RcTokenStorage::default();

    let mut client = screeps_api::SyncConfig::new()
        .unwrap()
        .url(&config.url)
        .tokens(token_storage.clone())
        .build()
        .unwrap();

    // Login using the API client - this will storage the auth token in token_storage.
    client
        .login(env("SCREEPS_API_USERNAME"), env("SCREEPS_API_PASSWORD"))
        .expect("failed to login");

    let my_info = client.my_info().unwrap();

    info!(
        "Logged in as {}, attempting to connect to stream.",
        my_info.username
    );

    let mut core =
        tokio_core::reactor::Core::new().expect("expected IO core to start up without issue.");

    let handle = core.handle();

    let ws_url = screeps_api::websocket::connecting::transform_url(&config.url)
        .expect("expected server api url to parse into websocket url.");

    let connection = websocket::ClientBuilder::from_url(&ws_url).async_connect(None, &handle);

    core.run(connection.then(|result| {
        let (client, _) = result.expect("connecting to server failed");

        let (sink, stream) = client.split();

        sink.send(OwnedMessage::Text(screeps_api::websocket::authenticate(
            &token_storage.take_token().unwrap(),
        )))
        .and_then(|sink| {
            let handler = Handler::new(token_storage, my_info, config);

            sink.send_all(
                stream
                    .and_then(move |data| future::ok(handler.handle_data(data)))
                    .or_else(|err| {
                        warn!("error occurred: {}", err);

                        future::ok::<_, websocket::WebSocketError>(Box::new(stream::empty())
                            as Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>>)
                    })
                    .flatten(),
            )
        })
    }))
    .expect("expected websocket connection to complete successfully, but an error occurred");
}

struct Handler<T>
where
    T: screeps_api::TokenStorage,
{
    tokens: T,
    info: screeps_api::MyInfo,
    config: Config,
}

impl<T> Handler<T>
where
    T: screeps_api::TokenStorage,
{
    fn new(tokens: T, info: screeps_api::MyInfo, config: Config) -> Self {
        Handler {
            tokens: tokens,
            info: info,
            config: config,
        }
    }

    fn handle_data(
        &self,
        data: OwnedMessage,
    ) -> Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>> {
        match data {
            OwnedMessage::Text(string) => {
                let data = SockjsMessage::parse(&string)
                    .expect("expected a correct SockJS message, found a parse error.");

                match data {
                    SockjsMessage::Open => debug!("SockJS connection opened"),
                    SockjsMessage::Heartbeat => debug!("SockJS heartbeat."),
                    SockjsMessage::Close { .. } => debug!("SockJS close"),
                    SockjsMessage::Message(message) => {
                        return Box::new(self.handle_parsed_message(message));
                    }
                    SockjsMessage::Messages(messages) => {
                        let results = messages
                            .into_iter()
                            .map(|message| Ok(self.handle_parsed_message(message)))
                            .collect::<Vec<_>>();

                        return Box::new(
                            stream::iter_result::<_, _, websocket::WebSocketError>(results)
                                .flatten(),
                        );
                    }
                }
            }
            OwnedMessage::Binary(data) => warn!("ignoring binary data from websocket: {:?}", data),
            OwnedMessage::Close(data) => warn!("connection closing: {:?}", data),
            OwnedMessage::Ping(data) => return Box::new(stream::once(Ok(OwnedMessage::Pong(data)))),
            OwnedMessage::Pong(_) => (),
        }

        Box::new(stream::empty())
    }

    fn handle_parsed_message(
        &self,
        message: screeps_api::websocket::parsing::ScreepsMessage,
    ) -> Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>> {
        match message {
            ScreepsMessage::AuthFailed => panic!("authentication with stored token failed!"),
            ScreepsMessage::AuthOk { new_token } => {
                info!(
                    "authentication succeeded, now authenticated as {}.",
                    self.info.username
                );
                // return the token to the token storage in case we need it again - we won't in this example
                // program, but this is a good practice
                //
                // TODO: find an efficient way to do this automatically in the handler.
                self.tokens.return_token(new_token);

                return Box::new(
                    self.config.subscribe_with(&self.info.user_id).chain(
                        stream::futures_unordered(vec![future::lazy(|| {
                            warn!("subscribed!");
                            future::ok::<_, websocket::WebSocketError>(stream::empty())
                        })])
                        .flatten(),
                    ),
                )
                    as Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>>;
            }
            ScreepsMessage::ChannelUpdate { update } => {
                self.handle_update(update);
            }
            ScreepsMessage::ServerProtocol { protocol } => {
                info!("server protocol: {}", protocol);
            }
            ScreepsMessage::ServerTime { time } => {
                info!("server time: {}", time);
            }
            ScreepsMessage::ServerPackage { package } => {
                info!("server package: {}", package);
            }
            ScreepsMessage::Other(other) => {
                warn!("ScreepsMessage::Other: {}", other);
            }
        }

        Box::new(stream::empty())
    }

    fn handle_update(&self, update: ChannelUpdate) {
        match update {
            ChannelUpdate::UserCpu { user_id, update } => info!("CPU: [{}] {:#?}", user_id, update),
            ChannelUpdate::RoomMapView {
                room_name,
                shard_name,
                update,
            } => {
                info!(
                    "Map View: [{}/{}] {:?}",
                    shard_name.as_ref().map(AsRef::as_ref).unwrap_or("<None>"),
                    room_name,
                    update
                );
            }
            ChannelUpdate::RoomDetail {
                room_name,
                shard_name,
                update,
            } => {
                debug!(
                    "Room Detail: [{}/{}] {:?}",
                    shard_name.as_ref().map(AsRef::as_ref).unwrap_or("<None>"),
                    room_name,
                    update
                );
                info!(
                    "Room {}/{}: {}",
                    shard_name.as_ref().map(AsRef::as_ref).unwrap_or("<None>"),
                    room_name,
                    serde_json::to_string_pretty(&serde_json::Value::Object(
                        update.objects.iter().cloned().collect()
                    ))
                    .expect("expected to_string to succeed on plain map.")
                );
            }
            ChannelUpdate::NoRoomDetail {
                room_name,
                shard_name,
            } => {
                info!(
                    "Room Skipped: {}/{}",
                    shard_name.as_ref().map(AsRef::as_ref).unwrap_or("<None>"),
                    room_name
                );
            }
            ChannelUpdate::UserConsole { user_id, update } => {
                info!("Console: [{}] {:#?}", user_id, update);
            }
            ChannelUpdate::UserCredits { user_id, update } => {
                info!("Credits: [{}] {}", user_id, update);
            }
            ChannelUpdate::UserMessage { user_id, update } => {
                info!("New message: [{}] {:#?}", user_id, update);
            }
            ChannelUpdate::UserConversation {
                user_id,
                target_user_id,
                update,
            } => {
                info!(
                    "Conversation update: [{}->{}] {:#?}",
                    user_id, target_user_id, update
                );
            }
            ChannelUpdate::Other { channel, update } => {
                warn!(
                    "ChannelUpdate::Other: {}\n{}",
                    channel,
                    serde_json::to_string_pretty(&update).expect("failed to serialize json string")
                );
            }
        }
    }
}
<commit_msg>Clean up unused import in recent rust.<commit_after>// .env parsing

extern crate dotenv;
// command line argument parsing

extern crate clap;
// logging macros

#[macro_use]
extern crate log;
// console logging output

extern crate chrono;
extern crate fern;
// sockets

extern crate futures;
extern crate tokio_core;
extern crate websocket;
// Screeps API

extern crate screeps_api;
// json pretty printing

extern crate serde_json;

use std::borrow::Cow;

use futures::{future, stream, Future, Sink, Stream};

use websocket::OwnedMessage;

use screeps_api::websocket::{Channel, ChannelUpdate, ScreepsMessage, SockjsMessage};
use screeps_api::{RoomName, TokenStorage};

/// Set up dotenv and retrieve a specific variable, informatively panicking if it does not exist.
fn env(var: &str) -> String {
    dotenv::dotenv().ok();
    match ::std::env::var(var) {
        Ok(value) => value,
        Err(e) => panic!("must have `{}` defined (err: {:?})", var, e),
    }
}

fn setup_logging(verbosity: u64) {
    let log_level = match verbosity {
        0 => log::LevelFilter::Warn,
        1 => log::LevelFilter::Info,
        _ => log::LevelFilter::Debug,
    };
    fern::Dispatch::new()
        .level(log_level)
        .level_for("rustls", log::LevelFilter::Warn)
        .level_for("hyper", log::LevelFilter::Warn)
        .format(|out, message, record| {
            let now = chrono::Local::now();

            out.finish(format_args!("[{}][{}] {}: {}",
                                    now.format("%H:%M:%S"),
                                    record.level(),
                                    record.target(),
                                    message));
        })
        .chain(std::io::stdout())
        .apply()
        // ignore errors
        .unwrap_or(());
}

#[derive(Clone, Debug)]
struct Config {
    cpu: bool,
    messages: bool,
    credits: bool,
    console: bool,
    shard: Option<Cow<'static, str>>,
    rooms: Vec<RoomName>,
    map_view: Vec<RoomName>,
    url: Cow<'static, str>,
}

impl Config {
    fn new<'a>(
        args: &'a clap::ArgMatches,
    ) -> Result<Self, screeps_api::data::room_name::RoomNameParseError<'a>> {
        Ok(Config {
            cpu: args.is_present("cpu"),
            messages: args.is_present("messages"),
            credits: args.is_present("credits"),
            console: args.is_present("console"),
            shard: args
                .value_of("shard")
                .map(|v| {
                    if "none".eq_ignore_ascii_case(v) {
                        None
                    } else {
                        Some(v.to_owned().into())
                    }
                })
                .unwrap_or_else(|| Some("shard0".into())),
            rooms: args
                .values_of("room")
                .map(|it| it.map(|v| RoomName::new(v)).collect::<Result<_, _>>())
                .unwrap_or_else(|| Ok(Vec::new()))?,
            map_view: args
                .values_of("map-view")
                .map(|it| it.map(|v| RoomName::new(v)).collect::<Result<_, _>>())
                .unwrap_or_else(|| Ok(Vec::new()))?,
            url: args
                .value_of("url")
                .map(|v| v.to_owned().into())
                .unwrap_or_else(|| screeps_api::DEFAULT_OFFICIAL_API_URL.into()),
        })
    }

    fn subscribe_with(
        &self,
        id: &str,
    ) -> Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>> {
        use screeps_api::websocket::subscribe;

        let mut messages = Vec::with_capacity(
            1 + self.cpu as usize
                + self.messages as usize
                + self.credits as usize
                + self.console as usize
                + self.rooms.len()
                + self.map_view.len(),
        );

        messages.push(subscribe(&Channel::ServerMessages));

        if self.cpu {
            messages.push(subscribe(&Channel::user_cpu(id)));
        }

        if self.messages {
            messages.push(subscribe(&Channel::user_messages(id)));
            messages.push(subscribe(&Channel::user_conversation(
                id,
                "57fb16b6e4dd183b746435b0",
            )));
        }

        if self.credits {
            messages.push(subscribe(&Channel::user_credits(id)));
        }

        if self.console {
            messages.push(subscribe(&Channel::user_console(id)));
        }

        for room_name in &self.rooms {
            messages.push(subscribe(&Channel::room_detail(
                *room_name,
                self.shard.as_ref().map(AsRef::as_ref),
            )));
        }

        for room_name in &self.map_view {
            messages.push(subscribe(&Channel::room_map_view(
                *room_name,
                self.shard.as_ref().map(AsRef::as_ref),
            )));
        }

        Box::new(stream::iter_ok(
            messages.into_iter().map(OwnedMessage::Text),
        ))
    }
}

fn setup() -> Config {
    let cmd_arguments = clap::App::new("ws-debug")
        .arg(
            clap::Arg::with_name("verbose")
                .short("v")
                .long("verbose")
                .multiple(true)
                .help("Enables verbose logging"),
        )
        .arg(
            clap::Arg::with_name("cpu")
                .short("p")
                .long("cpu")
                .help("Subscribe to user cpu and memory updates"),
        )
        .arg(
            clap::Arg::with_name("credits")
                .short("c")
                .long("credits")
                .help("Subscribe to per-tick user credit updates"),
        )
        .arg(
            clap::Arg::with_name("console")
                .short("o")
                .long("console")
                .help("Subscribe to user console messages"),
        )
        .arg(
            clap::Arg::with_name("messages")
                .short("e")
                .long("messages")
                .help("Subscribe to user message alerts"),
        )
        .arg(
            clap::Arg::with_name("shard")
                .short("s")
                .long("shard")
                .value_name("SHARD_NAME")
                .help("Sets the shard (default shard0, use 'None' for no shard)")
                .takes_value(true),
        )
        .arg(
            clap::Arg::with_name("room")
                .short("r")
                .long("room")
                .value_name("ROOM_NAME")
                .help("Subscribes to a room")
                .takes_value(true)
                .multiple(true),
        )
        .arg(
            clap::Arg::with_name("map-view")
                .short("m")
                .long("map-view")
                .value_name("ROOM_NAME")
                .help("Subscribes to a map-view room")
                .takes_value(true)
                .multiple(true),
        )
        .arg(
            clap::Arg::with_name("url")
                .short("u")
                .long("url")
                .value_name("API_URL")
                .help("Server url to connect to")
                .takes_value(true),
        )
        .get_matches();

    setup_logging(cmd_arguments.occurrences_of("verbose"));

    match Config::new(&cmd_arguments) {
        Ok(v) => v,
        Err(e) => clap::Error {
            message: e.to_string(),
            kind: clap::ErrorKind::InvalidValue,
            info: None,
        }
        .exit(),
    }
}

fn main() {
    let config = setup();

    let token_storage = screeps_api::RcTokenStorage::default();

    let mut client = screeps_api::SyncConfig::new()
        .unwrap()
        .url(&config.url)
        .tokens(token_storage.clone())
        .build()
        .unwrap();

    // Login using the API client - this will storage the auth token in token_storage.
    client
        .login(env("SCREEPS_API_USERNAME"), env("SCREEPS_API_PASSWORD"))
        .expect("failed to login");

    let my_info = client.my_info().unwrap();

    info!(
        "Logged in as {}, attempting to connect to stream.",
        my_info.username
    );

    let mut core =
        tokio_core::reactor::Core::new().expect("expected IO core to start up without issue.");

    let handle = core.handle();

    let ws_url = screeps_api::websocket::connecting::transform_url(&config.url)
        .expect("expected server api url to parse into websocket url.");

    let connection = websocket::ClientBuilder::from_url(&ws_url).async_connect(None, &handle);

    core.run(connection.then(|result| {
        let (client, _) = result.expect("connecting to server failed");

        let (sink, stream) = client.split();

        sink.send(OwnedMessage::Text(screeps_api::websocket::authenticate(
            &token_storage.take_token().unwrap(),
        )))
        .and_then(|sink| {
            let handler = Handler::new(token_storage, my_info, config);

            sink.send_all(
                stream
                    .and_then(move |data| future::ok(handler.handle_data(data)))
                    .or_else(|err| {
                        warn!("error occurred: {}", err);

                        future::ok::<_, websocket::WebSocketError>(Box::new(stream::empty())
                            as Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>>)
                    })
                    .flatten(),
            )
        })
    }))
    .expect("expected websocket connection to complete successfully, but an error occurred");
}

struct Handler<T>
where
    T: screeps_api::TokenStorage,
{
    tokens: T,
    info: screeps_api::MyInfo,
    config: Config,
}

impl<T> Handler<T>
where
    T: screeps_api::TokenStorage,
{
    fn new(tokens: T, info: screeps_api::MyInfo, config: Config) -> Self {
        Handler {
            tokens: tokens,
            info: info,
            config: config,
        }
    }

    fn handle_data(
        &self,
        data: OwnedMessage,
    ) -> Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>> {
        match data {
            OwnedMessage::Text(string) => {
                let data = SockjsMessage::parse(&string)
                    .expect("expected a correct SockJS message, found a parse error.");

                match data {
                    SockjsMessage::Open => debug!("SockJS connection opened"),
                    SockjsMessage::Heartbeat => debug!("SockJS heartbeat."),
                    SockjsMessage::Close { .. } => debug!("SockJS close"),
                    SockjsMessage::Message(message) => {
                        return Box::new(self.handle_parsed_message(message));
                    }
                    SockjsMessage::Messages(messages) => {
                        let results = messages
                            .into_iter()
                            .map(|message| Ok(self.handle_parsed_message(message)))
                            .collect::<Vec<_>>();

                        return Box::new(
                            stream::iter_result::<_, _, websocket::WebSocketError>(results)
                                .flatten(),
                        );
                    }
                }
            }
            OwnedMessage::Binary(data) => warn!("ignoring binary data from websocket: {:?}", data),
            OwnedMessage::Close(data) => warn!("connection closing: {:?}", data),
            OwnedMessage::Ping(data) => return Box::new(stream::once(Ok(OwnedMessage::Pong(data)))),
            OwnedMessage::Pong(_) => (),
        }

        Box::new(stream::empty())
    }

    fn handle_parsed_message(
        &self,
        message: screeps_api::websocket::parsing::ScreepsMessage,
    ) -> Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>> {
        match message {
            ScreepsMessage::AuthFailed => panic!("authentication with stored token failed!"),
            ScreepsMessage::AuthOk { new_token } => {
                info!(
                    "authentication succeeded, now authenticated as {}.",
                    self.info.username
                );
                // return the token to the token storage in case we need it again - we won't in this example
                // program, but this is a good practice
                //
                // TODO: find an efficient way to do this automatically in the handler.
                self.tokens.return_token(new_token);

                return Box::new(
                    self.config.subscribe_with(&self.info.user_id).chain(
                        stream::futures_unordered(vec![future::lazy(|| {
                            warn!("subscribed!");
                            future::ok::<_, websocket::WebSocketError>(stream::empty())
                        })])
                        .flatten(),
                    ),
                )
                    as Box<Stream<Item = OwnedMessage, Error = websocket::WebSocketError>>;
            }
            ScreepsMessage::ChannelUpdate { update } => {
                self.handle_update(update);
            }
            ScreepsMessage::ServerProtocol { protocol } => {
                info!("server protocol: {}", protocol);
            }
            ScreepsMessage::ServerTime { time } => {
                info!("server time: {}", time);
            }
            ScreepsMessage::ServerPackage { package } => {
                info!("server package: {}", package);
            }
            ScreepsMessage::Other(other) => {
                warn!("ScreepsMessage::Other: {}", other);
            }
        }

        Box::new(stream::empty())
    }

    fn handle_update(&self, update: ChannelUpdate) {
        match update {
            ChannelUpdate::UserCpu { user_id, update } => info!("CPU: [{}] {:#?}", user_id, update),
            ChannelUpdate::RoomMapView {
                room_name,
                shard_name,
                update,
            } => {
                info!(
                    "Map View: [{}/{}] {:?}",
                    shard_name.as_ref().map(AsRef::as_ref).unwrap_or("<None>"),
                    room_name,
                    update
                );
            }
            ChannelUpdate::RoomDetail {
                room_name,
                shard_name,
                update,
            } => {
                debug!(
                    "Room Detail: [{}/{}] {:?}",
                    shard_name.as_ref().map(AsRef::as_ref).unwrap_or("<None>"),
                    room_name,
                    update
                );
                info!(
                    "Room {}/{}: {}",
                    shard_name.as_ref().map(AsRef::as_ref).unwrap_or("<None>"),
                    room_name,
                    serde_json::to_string_pretty(&serde_json::Value::Object(
                        update.objects.iter().cloned().collect()
                    ))
                    .expect("expected to_string to succeed on plain map.")
                );
            }
            ChannelUpdate::NoRoomDetail {
                room_name,
                shard_name,
            } => {
                info!(
                    "Room Skipped: {}/{}",
                    shard_name.as_ref().map(AsRef::as_ref).unwrap_or("<None>"),
                    room_name
                );
            }
            ChannelUpdate::UserConsole { user_id, update } => {
                info!("Console: [{}] {:#?}", user_id, update);
            }
            ChannelUpdate::UserCredits { user_id, update } => {
                info!("Credits: [{}] {}", user_id, update);
            }
            ChannelUpdate::UserMessage { user_id, update } => {
                info!("New message: [{}] {:#?}", user_id, update);
            }
            ChannelUpdate::UserConversation {
                user_id,
                target_user_id,
                update,
            } => {
                info!(
                    "Conversation update: [{}->{}] {:#?}",
                    user_id, target_user_id, update
                );
            }
            ChannelUpdate::Other { channel, update } => {
                warn!(
                    "ChannelUpdate::Other: {}\n{}",
                    channel,
                    serde_json::to_string_pretty(&update).expect("failed to serialize json string")
                );
            }
        }
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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.

use std::collections::HashMap;
use std::fmt;
use std::ops::Deref;
use std::path::PathBuf;
use std::result;
use std::str::FromStr;
use std::string::ToString;

use glob;
use hab_core::channel::UNSTABLE_CHANNEL;
use hab_core::config::ConfigFile;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use toml;

use error::{Error, Result};

/// Postprocessing config file name
pub const BLDR_CFG: &'static str = ".bldr.toml";
pub const DEFAULT_CHANNEL: &'static str = UNSTABLE_CHANNEL;

#[derive(Debug, Deserialize, Serialize)]
pub struct BuildCfg(HashMap<String, ProjectCfg>);

impl BuildCfg {
    pub fn from_slice(bytes: &[u8]) -> Result<Self> {
        let inner = toml::from_slice::<HashMap<String, ProjectCfg>>(bytes)
            .map_err(|e| Error::DecryptError(e.to_string()))?;
        Ok(BuildCfg(inner))
    }

    /// List of all registered projects for this `BuildCfg`.
    pub fn projects(&self) -> Vec<&ProjectCfg> {
        self.0.values().collect()
    }

    /// Returns true if the given branch & file path combination should result in a new build
    /// being automatically triggered by a GitHub Push notification.
    pub fn triggered_by<T>(&self, branch: &str, paths: &[T]) -> Vec<&ProjectCfg>
    where
        T: AsRef<str>,
    {
        self.0
            .values()
            .filter(|p| p.triggered_by(branch, paths))
            .collect()
    }
}

impl ConfigFile for BuildCfg {
    type Error = Error;
}

impl Default for BuildCfg {
    fn default() -> Self {
        let mut cfg = HashMap::default();
        cfg.insert("default".into(), ProjectCfg::default());
        BuildCfg(cfg)
    }
}

impl Deref for BuildCfg {
    type Target = HashMap<String, ProjectCfg>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

pub struct Pattern {
    inner: glob::Pattern,
    options: glob::MatchOptions,
}

impl Pattern {
    fn default_options() -> glob::MatchOptions {
        glob::MatchOptions {
            case_sensitive: false,
            require_literal_separator: false,
            require_literal_leading_dot: false,
        }
    }

    pub fn matches<T>(&self, value: T) -> bool
    where
        T: AsRef<str>,
    {
        self.inner.matches_with(value.as_ref(), &self.options)
    }
}

impl<'de> Deserialize<'de> for Pattern {
    fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Pattern::from_str(&s).map_err(de::Error::custom)
    }
}

impl fmt::Debug for Pattern {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.inner)
    }
}

impl FromStr for Pattern {
    type Err = glob::PatternError;

    fn from_str(value: &str) -> result::Result<Self, Self::Err> {
        let inner: glob::Pattern = FromStr::from_str(value)?;
        Ok(Pattern {
            inner: inner,
            options: Pattern::default_options(),
        })
    }
}

impl Serialize for Pattern {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.inner.to_string())
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ProjectCfg {
    /// Branches which trigger an automatic rebuild on push notification from a GitHub push
    /// notification (default: ["master"]).
    #[serde(default = "ProjectCfg::default_branches")]
    pub branches: Vec<String>,
    /// Additional Release Channel to promote built packages into.
    #[serde(default)]
    pub channels: Vec<String>,
    /// Unix style file globs which are matched against changed files from a GitHub push
    /// notification to determine if an automatic rebuild should occur.
    #[serde(default)]
    pub paths: Vec<Pattern>,
    /// Relative filepath to the project's Habitat Plan (default: "habitat").
    #[serde(default = "ProjectCfg::default_plan_path")]
    plan_path: PathBuf,
}

impl ProjectCfg {
    fn default_branches() -> Vec<String> {
        vec!["master".to_string()]
    }

    fn default_plan_path() -> PathBuf {
        PathBuf::from("habitat")
    }

    fn default_plan_pattern() -> Pattern {
        Pattern::from_str("habitat/*").unwrap()
    }

    pub fn plan_file(&self) -> PathBuf {
        self.plan_path.join("plan.sh")
    }

    /// Returns true if the given branch & file path combination should result in a new build
    /// being automatically triggered by a GitHub Push notification
    fn triggered_by<T>(&self, branch: &str, paths: &[T]) -> bool
    where
        T: AsRef<str>,
    {
        if !self.branches.iter().any(|b| b == branch) {
            return false;
        }
        let plan_pattern = Pattern::from_str(&self.plan_path.join("*").to_string_lossy())
            .unwrap_or(Self::default_plan_pattern());
        paths.iter().any(|p| {
            plan_pattern.matches(p.as_ref()) || self.paths.iter().any(|i| i.matches(p.as_ref()))
        })
    }
}

impl Default for ProjectCfg {
    fn default() -> Self {
        ProjectCfg {
            branches: ProjectCfg::default_branches(),
            channels: vec![],
            paths: vec![],
            plan_path: ProjectCfg::default_plan_path(),
        }
    }
}

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

    const CONFIG: &'static str = r#"
    [hab-sup]
    plan_path = "components/hab-sup"
    branches = [
      "master",
      "dev",
    ]
    channels = [
      "stable"
    ]
    paths = [
      "components/net/*"
    ]

    [builder-api]
    plan_path = "components/builder-api/habitat"
    paths = [
      "components/net/*"
    ]

    [default]
    "#;

    #[test]
    fn triggered_by() {
        let cfg = BuildCfg::from_slice(CONFIG.as_bytes()).unwrap();
        let hab_sup = cfg.get("hab-sup").unwrap();
        let bldr_api = cfg.get("builder-api").unwrap();
        let default = cfg.get("default").unwrap();

        assert!(hab_sup.triggered_by(
            "master",
            &["components/hab-sup/Cargo.toml"],
        ));
        assert!(hab_sup.triggered_by(
            "master",
            &["components/hAb-Sup/Cargo.toml"],
        ));
        assert!(hab_sup.triggered_by(
            "dev",
            &["components/hab-sup/Cargo.toml"],
        ));
        assert_eq!(hab_sup.triggered_by("master", &["components"]), false);

        assert!(bldr_api.triggered_by(
            "master",
            &["components/builder-api/habitat/plan.sh"],
        ));
        assert!(bldr_api.triggered_by(
            "master",
            &["components/net/Cargo.toml"],
        ));

        assert!(default.triggered_by("master", &["habitat/plan.sh"]));
        assert!(default.triggered_by("master", &["habitat/hooks/init"]));
        assert_eq!(default.triggered_by("dev", &["habitat/plan.sh"]), false);
        assert_eq!(default.triggered_by("master", &["components"]), false);
    }
}
<commit_msg>Handle case where plan file is at the root<commit_after>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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.

use std::collections::HashMap;
use std::fmt;
use std::ops::Deref;
use std::path::PathBuf;
use std::result;
use std::str::FromStr;
use std::string::ToString;

use glob;
use hab_core::channel::UNSTABLE_CHANNEL;
use hab_core::config::ConfigFile;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use toml;

use error::{Error, Result};

/// Postprocessing config file name
pub const BLDR_CFG: &'static str = ".bldr.toml";
pub const DEFAULT_CHANNEL: &'static str = UNSTABLE_CHANNEL;

#[derive(Debug, Deserialize, Serialize)]
pub struct BuildCfg(HashMap<String, ProjectCfg>);

impl BuildCfg {
    pub fn from_slice(bytes: &[u8]) -> Result<Self> {
        let inner = toml::from_slice::<HashMap<String, ProjectCfg>>(bytes)
            .map_err(|e| Error::DecryptError(e.to_string()))?;
        Ok(BuildCfg(inner))
    }

    /// List of all registered projects for this `BuildCfg`.
    pub fn projects(&self) -> Vec<&ProjectCfg> {
        self.0.values().collect()
    }

    /// Returns true if the given branch & file path combination should result in a new build
    /// being automatically triggered by a GitHub Push notification.
    pub fn triggered_by<T>(&self, branch: &str, paths: &[T]) -> Vec<&ProjectCfg>
    where
        T: AsRef<str>,
    {
        self.0
            .values()
            .filter(|p| p.triggered_by(branch, paths))
            .collect()
    }
}

impl ConfigFile for BuildCfg {
    type Error = Error;
}

impl Default for BuildCfg {
    fn default() -> Self {
        let mut cfg = HashMap::default();
        cfg.insert("default".into(), ProjectCfg::default());

        // Handle the case where the plan.sh is at the root
        let mut root_proj_cfg = ProjectCfg::default();
        root_proj_cfg.plan_path = PathBuf::from("");
        cfg.insert("root_default".into(), root_proj_cfg);

        BuildCfg(cfg)
    }
}

impl Deref for BuildCfg {
    type Target = HashMap<String, ProjectCfg>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

pub struct Pattern {
    inner: glob::Pattern,
    options: glob::MatchOptions,
}

impl Pattern {
    fn default_options() -> glob::MatchOptions {
        glob::MatchOptions {
            case_sensitive: false,
            require_literal_separator: false,
            require_literal_leading_dot: false,
        }
    }

    pub fn matches<T>(&self, value: T) -> bool
    where
        T: AsRef<str>,
    {
        self.inner.matches_with(value.as_ref(), &self.options)
    }
}

impl<'de> Deserialize<'de> for Pattern {
    fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Pattern::from_str(&s).map_err(de::Error::custom)
    }
}

impl fmt::Debug for Pattern {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.inner)
    }
}

impl FromStr for Pattern {
    type Err = glob::PatternError;

    fn from_str(value: &str) -> result::Result<Self, Self::Err> {
        let inner: glob::Pattern = FromStr::from_str(value)?;
        Ok(Pattern {
            inner: inner,
            options: Pattern::default_options(),
        })
    }
}

impl Serialize for Pattern {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.inner.to_string())
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ProjectCfg {
    /// Branches which trigger an automatic rebuild on push notification from a GitHub push
    /// notification (default: ["master"]).
    #[serde(default = "ProjectCfg::default_branches")]
    pub branches: Vec<String>,
    /// Additional Release Channel to promote built packages into.
    #[serde(default)]
    pub channels: Vec<String>,
    /// Unix style file globs which are matched against changed files from a GitHub push
    /// notification to determine if an automatic rebuild should occur.
    #[serde(default)]
    pub paths: Vec<Pattern>,
    /// Relative filepath to the project's Habitat Plan (default: "habitat").
    #[serde(default = "ProjectCfg::default_plan_path")]
    plan_path: PathBuf,
}

impl ProjectCfg {
    fn default_branches() -> Vec<String> {
        vec!["master".to_string()]
    }

    fn default_path() -> Pattern {
        Pattern::from_str("*").unwrap()
    }

    fn default_plan_path() -> PathBuf {
        PathBuf::from("habitat")
    }

    fn default_plan_pattern() -> Pattern {
        Pattern::from_str("habitat/*").unwrap()
    }

    pub fn plan_file(&self) -> PathBuf {
        self.plan_path.join("plan.sh")
    }

    /// Returns true if the given branch & file path combination should result in a new build
    /// being automatically triggered by a GitHub Push notification
    fn triggered_by<T>(&self, branch: &str, paths: &[T]) -> bool
    where
        T: AsRef<str>,
    {
        if !self.branches.iter().any(|b| b == branch) {
            return false;
        }
        let plan_pattern = Pattern::from_str(&self.plan_path.join("*").to_string_lossy())
            .unwrap_or(Self::default_plan_pattern());
        paths.iter().any(|p| {
            plan_pattern.matches(p.as_ref()) || self.paths.iter().any(|i| i.matches(p.as_ref()))
        })
    }
}

impl Default for ProjectCfg {
    fn default() -> Self {
        ProjectCfg {
            branches: ProjectCfg::default_branches(),
            channels: vec![],
            paths: vec![ProjectCfg::default_path()],
            plan_path: ProjectCfg::default_plan_path(),
        }
    }
}

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

    const CONFIG: &'static str = r#"
    [hab-sup]
    plan_path = "components/hab-sup"
    branches = [
      "master",
      "dev",
    ]
    channels = [
      "stable"
    ]
    paths = [
      "components/net/*"
    ]

    [builder-api]
    plan_path = "components/builder-api/habitat"
    paths = [
      "components/net/*"
    ]

    [default]
    "#;

    #[test]
    fn triggered_by() {
        let cfg = BuildCfg::from_slice(CONFIG.as_bytes()).unwrap();
        let hab_sup = cfg.get("hab-sup").unwrap();
        let bldr_api = cfg.get("builder-api").unwrap();
        let default = cfg.get("default").unwrap();

        assert!(hab_sup.triggered_by(
            "master",
            &["components/hab-sup/Cargo.toml"],
        ));
        assert!(hab_sup.triggered_by(
            "master",
            &["components/hAb-Sup/Cargo.toml"],
        ));
        assert!(hab_sup.triggered_by(
            "dev",
            &["components/hab-sup/Cargo.toml"],
        ));
        assert_eq!(hab_sup.triggered_by("master", &["components"]), false);

        assert!(bldr_api.triggered_by(
            "master",
            &["components/builder-api/habitat/plan.sh"],
        ));
        assert!(bldr_api.triggered_by(
            "master",
            &["components/net/Cargo.toml"],
        ));

        assert!(default.triggered_by("master", &["habitat/plan.sh"]));
        assert!(default.triggered_by("master", &["habitat/hooks/init"]));
        assert_eq!(default.triggered_by("dev", &["habitat/plan.sh"]), false);
        assert_eq!(default.triggered_by("master", &["components"]), false);
    }

    #[test]
    fn triggered_by_default() {
        let cfg = BuildCfg::default();

        assert_eq!(cfg.triggered_by("dev", &["habitat/plan.sh"]).len(), 0);
        assert_eq!(cfg.triggered_by("master", &["habitat/plan.sh"]).len(), 2);
        assert_eq!(cfg.triggered_by("master", &["plan.sh"]).len(), 2);
    }
}
<|endoftext|>


<commit_before>#![feature(test)]

extern crate test;
extern crate exonum;

#[cfg(test)]
mod tests {
    use test::Bencher;
    use exonum::crypto::{gen_keypair, sign, verify, hash};

    #[bench]
    fn bench_sign_64(b: &mut Bencher) {
        let (_, secret_key) = gen_keypair();
        let data = (0..64).collect::<Vec<u8>>();
        b.iter(|| sign(&data, &secret_key))
    }

    #[bench]
    fn bench_sign_128(b: &mut Bencher) {
        let (_, secret_key) = gen_keypair();
        let data = (0..128).collect::<Vec<u8>>();
        b.iter(|| sign(&data, &secret_key))
    }

    #[bench]
    fn bench_sign_1024(b: &mut Bencher) {
        let (_, secret_key) = gen_keypair();
        let data = (0..128).collect::<Vec<u8>>();
        b.iter(|| sign(&data, &secret_key))
    }

    #[bench]
    fn bench_verify_64(b: &mut Bencher) {
        let (public_key, secret_key) = gen_keypair();
        let data = (0..64).collect::<Vec<u8>>();
        let signature = sign(&data, &secret_key);
        b.iter(|| verify(&signature, &data, &public_key))
    }

    #[bench]
    fn bench_verify_128(b: &mut Bencher) {
        let (public_key, secret_key) = gen_keypair();
        let data = (0..128).collect::<Vec<u8>>();
        let signature = sign(&data, &secret_key);
        b.iter(|| verify(&signature, &data, &public_key))
    }
    #[bench]
    fn bench_verify_1024(b: &mut Bencher) {
        let (public_key, secret_key) = gen_keypair();
        let data = (0..1024).collect::<Vec<u8>>();
        let signature = sign(&data, &secret_key);
        b.iter(|| verify(&signature, &data, &public_key))
    }

    #[bench]
    fn bench_hash_64(b: &mut Bencher) {
        let data = (0..64).collect::<Vec<u8>>();
        b.iter(|| hash(&data))
    }

    #[bench]
    fn bench_hash_128(b: &mut Bencher) {
        let data = (0..128).collect::<Vec<u8>>();
        b.iter(|| hash(&data))
    }
    #[bench]
    fn bench_hash_1024(b: &mut Bencher) {
        let data = (0..1024).collect::<Vec<u8>>();
        b.iter(|| hash(&data))
    }
}
<commit_msg>Fix crypto benchmarks<commit_after>#![feature(test)]

extern crate test;
extern crate exonum;

#[cfg(test)]
mod tests {
    use test::Bencher;
    use exonum::crypto::{gen_keypair, sign, verify, hash};

    #[bench]
    fn bench_sign_64(b: &mut Bencher) {
        let (_, secret_key) = gen_keypair();
        let data = (0..64).collect::<Vec<u8>>();
        b.iter(|| sign(&data, &secret_key))
    }

    #[bench]
    fn bench_sign_128(b: &mut Bencher) {
        let (_, secret_key) = gen_keypair();
        let data = (0..128).collect::<Vec<u8>>();
        b.iter(|| sign(&data, &secret_key))
    }

    #[bench]
    fn bench_sign_1024(b: &mut Bencher) {
        let (_, secret_key) = gen_keypair();
        let data = (0..1024)
            .map(|x| (x % 255) as u8)
            .collect::<Vec<_>>();
        b.iter(|| sign(&data, &secret_key))
    }

    #[bench]
    fn bench_sign_1024_inited_sodium(b: &mut Bencher) {
        ::exonum::crypto::init();
        let (_, secret_key) = gen_keypair();
        let data = (0..1024)
            .map(|x| (x % 255) as u8)
            .collect::<Vec<_>>();
        b.iter(|| sign(&data, &secret_key))
    }

    #[bench]
    fn bench_verify_64(b: &mut Bencher) {
        let (public_key, secret_key) = gen_keypair();
        let data = (0..64).collect::<Vec<u8>>();
        let signature = sign(&data, &secret_key);
        b.iter(|| verify(&signature, &data, &public_key))
    }

    #[bench]
    fn bench_verify_128(b: &mut Bencher) {
        let (public_key, secret_key) = gen_keypair();
        let data = (0..128).collect::<Vec<u8>>();
        let signature = sign(&data, &secret_key);
        b.iter(|| verify(&signature, &data, &public_key))
    }

    #[bench]
    fn bench_verify_1024(b: &mut Bencher) {
        let (public_key, secret_key) = gen_keypair();
        let data = (0..1024)
            .map(|x| (x % 255) as u8)
            .collect::<Vec<_>>();
        let signature = sign(&data, &secret_key);
        b.iter(|| verify(&signature, &data, &public_key))
    }

    #[bench]
    fn bench_verify_1024_inited_sodium(b: &mut Bencher) {
        ::exonum::crypto::init();
        let (public_key, secret_key) = gen_keypair();
        let data = (0..1024)
            .map(|x| (x % 255) as u8)
            .collect::<Vec<_>>();
        let signature = sign(&data, &secret_key);
        b.iter(|| verify(&signature, &data, &public_key))
    }

    #[bench]
    fn bench_hash_64(b: &mut Bencher) {
        let data = (0..64).collect::<Vec<u8>>();
        b.iter(|| hash(&data))
    }

    #[bench]
    fn bench_hash_128(b: &mut Bencher) {
        let data = (0..128).collect::<Vec<u8>>();
        b.iter(|| hash(&data))
    }

    #[bench]
    fn bench_hash_1024(b: &mut Bencher) {
        let data = (0..1024)
            .map(|x| (x % 255) as u8)
            .collect::<Vec<_>>();
        b.iter(|| hash(&data))
    }

    #[bench]
    fn bench_hash_1024_inited_sodium(b: &mut Bencher) {
        ::exonum::crypto::init();
        let data = (0..1024)
            .map(|x| (x % 255) as u8)
            .collect::<Vec<_>>();
        b.iter(|| hash(&data))
    }
}
<|endoftext|>


<commit_before>#[doc = "

Pulls a brief description out of a long description.

If the first paragraph of a long description is short enough then it
is interpreted as the brief description.

"];

export mk_pass;

fn mk_pass() -> pass {
    {
        name: "desc_to_brief",
        f: run
    }
}

fn run(
    _srv: astsrv::srv,
    doc: doc::doc
) -> doc::doc {
    let fold = fold::fold({
        fold_item: fold_item,
        fold_iface: fold_iface,
        fold_impl: fold_impl
        with *fold::default_any_fold(())
    });
    fold.fold_doc(fold, doc)
}

fn fold_item(fold: fold::fold<()>, doc: doc::itemdoc) -> doc::itemdoc {
    let doc = fold::default_seq_fold_item(fold, doc);

    {
        brief: extract(doc.desc)
        with doc
    }
}

fn fold_iface(fold: fold::fold<()>, doc: doc::ifacedoc) -> doc::ifacedoc {
    let doc =fold::default_seq_fold_iface(fold, doc);

    {
        methods: par::anymap(doc.methods) {|doc|
            {
                brief: extract(doc.desc)
                with doc
            }
        }
        with doc
    }
}

fn fold_impl(fold: fold::fold<()>, doc: doc::impldoc) -> doc::impldoc {
    let doc =fold::default_seq_fold_impl(fold, doc);

    {
        methods: par::anymap(doc.methods) {|doc|
            {
                brief: extract(doc.desc)
                with doc
            }
        }
        with doc
    }
}

#[test]
fn should_promote_desc() {
    let doc = test::mk_doc("#[doc = \"desc\"] mod m { }");
    assert doc.cratemod().mods()[0].brief() == some("desc");
}

#[test]
fn should_promote_iface_method_desc() {
    let doc = test::mk_doc("iface i { #[doc = \"desc\"] fn a(); }");
    assert doc.cratemod().ifaces()[0].methods[0].brief == some("desc");
}

#[test]
fn should_promote_impl_method_desc() {
    let doc = test::mk_doc(
        "impl i for int { #[doc = \"desc\"] fn a() { } }");
    assert doc.cratemod().impls()[0].methods[0].brief == some("desc");
}

#[cfg(test)]
mod test {
    fn mk_doc(source: str) -> doc::doc {
        astsrv::from_str(source) {|srv|
            let doc = extract::from_srv(srv, "");
            let doc = attr_pass::mk_pass().f(srv, doc);
            run(srv, doc)
        }
    }
}

fn extract(desc: option<str>) -> option<str> {
    if option::is_none(desc) {
        ret none
    }

    parse_desc(option::get(desc))
}

fn parse_desc(desc: str) -> option<str> {

    const max_brief_len: uint = 120u;

    alt first_sentence(desc) {
      some(first_sentence) {
        if str::len(first_sentence) <= max_brief_len {
            some(first_sentence)
        } else {
            none
        }
      }
      none { none }
    }
}

fn first_sentence(s: str) -> option<str> {
    let paras = paragraphs(s);
    if vec::is_not_empty(paras) {
        let first = vec::head(sentences(vec::head(paras)));
        some(str::replace(first, "\n", " "))
    } else {
        none
    }
}

fn sentences(s: str) -> [str] {
    str::split_char(s, '.')
}

fn paragraphs(s: str) -> [str] {
    let lines = str::lines_any(s);
    let whitespace_lines = 0;
    let accum = "";
    let paras = vec::foldl([], lines) {|paras, line|
        let res = paras;

        if str::is_whitespace(line) {
            whitespace_lines += 1;
        } else {
            if whitespace_lines > 0 {
                if str::is_not_empty(accum) {
                    res += [accum];
                    accum = "";
                }
            }

            whitespace_lines = 0;

            accum = if str::is_empty(accum) {
                line
            } else {
                accum + "\n" + line
            }
        }

        res
    };

    if str::is_not_empty(accum) {
        paras + [accum]
    } else {
        paras
    }
}

#[test]
fn test_paragraphs_1() {
    let paras = paragraphs("1\n\n2");
    assert paras == ["1", "2"];
}

#[test]
fn test_paragraphs_2() {
    let paras = paragraphs("\n\n1\n1\n\n2\n\n");
    assert paras == ["1\n1", "2"];
}

#[test]
fn should_promote_short_descs() {
    let desc = some("desc");
    let brief = extract(desc);
    assert brief == desc;
}

#[test]
fn should_not_promote_long_descs() {
    let desc = some("Warkworth Castle is a ruined medieval building
in the town of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
    let brief = extract(desc);
    assert brief == none;
}

#[test]
fn should_promote_first_sentence() {
    let desc = some("Warkworth Castle is a ruined medieval building
in the town. of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
    let brief = extract(desc);
    assert brief == some(
        "Warkworth Castle is a ruined medieval building in the town");
}<commit_msg>rustdoc: Allow elipses to appear in brief descriptions. Closes #2003<commit_after>#[doc = "

Pulls a brief description out of a long description.

If the first paragraph of a long description is short enough then it
is interpreted as the brief description.

"];

export mk_pass;

fn mk_pass() -> pass {
    {
        name: "desc_to_brief",
        f: run
    }
}

fn run(
    _srv: astsrv::srv,
    doc: doc::doc
) -> doc::doc {
    let fold = fold::fold({
        fold_item: fold_item,
        fold_iface: fold_iface,
        fold_impl: fold_impl
        with *fold::default_any_fold(())
    });
    fold.fold_doc(fold, doc)
}

fn fold_item(fold: fold::fold<()>, doc: doc::itemdoc) -> doc::itemdoc {
    let doc = fold::default_seq_fold_item(fold, doc);

    {
        brief: extract(doc.desc)
        with doc
    }
}

fn fold_iface(fold: fold::fold<()>, doc: doc::ifacedoc) -> doc::ifacedoc {
    let doc =fold::default_seq_fold_iface(fold, doc);

    {
        methods: par::anymap(doc.methods) {|doc|
            {
                brief: extract(doc.desc)
                with doc
            }
        }
        with doc
    }
}

fn fold_impl(fold: fold::fold<()>, doc: doc::impldoc) -> doc::impldoc {
    let doc =fold::default_seq_fold_impl(fold, doc);

    {
        methods: par::anymap(doc.methods) {|doc|
            {
                brief: extract(doc.desc)
                with doc
            }
        }
        with doc
    }
}

#[test]
fn should_promote_desc() {
    let doc = test::mk_doc("#[doc = \"desc\"] mod m { }");
    assert doc.cratemod().mods()[0].brief() == some("desc");
}

#[test]
fn should_promote_iface_method_desc() {
    let doc = test::mk_doc("iface i { #[doc = \"desc\"] fn a(); }");
    assert doc.cratemod().ifaces()[0].methods[0].brief == some("desc");
}

#[test]
fn should_promote_impl_method_desc() {
    let doc = test::mk_doc(
        "impl i for int { #[doc = \"desc\"] fn a() { } }");
    assert doc.cratemod().impls()[0].methods[0].brief == some("desc");
}

#[cfg(test)]
mod test {
    fn mk_doc(source: str) -> doc::doc {
        astsrv::from_str(source) {|srv|
            let doc = extract::from_srv(srv, "");
            let doc = attr_pass::mk_pass().f(srv, doc);
            run(srv, doc)
        }
    }
}

fn extract(desc: option<str>) -> option<str> {
    if option::is_none(desc) {
        ret none
    }

    parse_desc(option::get(desc))
}

fn parse_desc(desc: str) -> option<str> {

    const max_brief_len: uint = 120u;

    alt first_sentence(desc) {
      some(first_sentence) {
        if str::len(first_sentence) <= max_brief_len {
            some(first_sentence)
        } else {
            none
        }
      }
      none { none }
    }
}

fn first_sentence(s: str) -> option<str> {
    let paras = paragraphs(s);
    if vec::is_not_empty(paras) {
        let first_para = vec::head(paras);
        some(str::replace(first_sentence_(first_para), "\n", " "))
    } else {
        none
    }
}

fn first_sentence_(s: str) -> str {
    let dotcount = 0;
    // The index of the character following a single dot. This allows
    // Things like [0..1) to appear in the brief description
    let idx = str::find(s) {|ch|
        if ch == '.' {
            dotcount += 1;
            false
        } else {
            if dotcount == 1 {
                true
            } else {
                dotcount = 0;
                false
            }
        }
    };
    alt idx {
      some(idx) if idx > 2u {
        str::slice(s, 0u, idx - 1u)
      }
      _ {
        if str::ends_with(s, ".") {
            str::slice(s, 0u, str::len(s))
        } else {
            s
        }
      }
    }
}

fn paragraphs(s: str) -> [str] {
    let lines = str::lines_any(s);
    let whitespace_lines = 0;
    let accum = "";
    let paras = vec::foldl([], lines) {|paras, line|
        let res = paras;

        if str::is_whitespace(line) {
            whitespace_lines += 1;
        } else {
            if whitespace_lines > 0 {
                if str::is_not_empty(accum) {
                    res += [accum];
                    accum = "";
                }
            }

            whitespace_lines = 0;

            accum = if str::is_empty(accum) {
                line
            } else {
                accum + "\n" + line
            }
        }

        res
    };

    if str::is_not_empty(accum) {
        paras + [accum]
    } else {
        paras
    }
}

#[test]
fn test_paragraphs_1() {
    let paras = paragraphs("1\n\n2");
    assert paras == ["1", "2"];
}

#[test]
fn test_paragraphs_2() {
    let paras = paragraphs("\n\n1\n1\n\n2\n\n");
    assert paras == ["1\n1", "2"];
}

#[test]
fn should_promote_short_descs() {
    let desc = some("desc");
    let brief = extract(desc);
    assert brief == desc;
}

#[test]
fn should_not_promote_long_descs() {
    let desc = some("Warkworth Castle is a ruined medieval building
in the town of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
    let brief = extract(desc);
    assert brief == none;
}

#[test]
fn should_promote_first_sentence() {
    let desc = some("Warkworth Castle is a ruined medieval building
in the town. of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
    let brief = extract(desc);
    assert brief == some(
        "Warkworth Castle is a ruined medieval building in the town");
}

#[test]
fn should_not_consider_double_period_to_end_sentence() {
    let desc = some("Warkworth..Castle is a ruined medieval building
in the town. of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
    let brief = extract(desc);
    assert brief == some(
        "Warkworth..Castle is a ruined medieval building in the town");
}

#[test]
fn should_not_consider_triple_period_to_end_sentence() {
    let desc = some("Warkworth... Castle is a ruined medieval building
in the town. of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
    let brief = extract(desc);
    assert brief == some(
        "Warkworth... Castle is a ruined medieval building in the town");
}
<|endoftext|>


<commit_before>use crate::error::{Error,
                   Result};
use anyhow::Context;
use habitat_common::ui::UI;
use habitat_core::fs::cache_build_path;
use log::debug;
use std::{env,
          ffi::OsString,
          fs::{self,
               File},
          io::Write,
          process::Command};

const HAB_PLAN_BUILD_SOURCE_FILES: [(&str, &[u8]); 4] =
    [("environment.bash", include_bytes!("../../../../plan-build/bin/environment.bash")),
     ("shared.bash", include_bytes!("../../../../plan-build/bin/shared.bash")),
     ("public.bash", include_bytes!("../../../../plan-build/bin/public.bash")),
     ("hab-plan-build.sh", include_bytes!("../../../../plan-build/bin/hab-plan-build.sh"))];

pub fn start_native_studio(ui: &mut UI, args: &[OsString]) -> Result<()> {
    start_native_studio_impl(ui, args).map_err(Error::NativeStudioError)
}

fn start_native_studio_impl(_ui: &mut UI, args: &[OsString]) -> anyhow::Result<(), anyhow::Error> {
    let build_dir = cache_build_path(None::<&str>);
    fs::create_dir_all(&build_dir).context("Failed to create cache build directory for the \
                                            habitat plan build script")?;
    for (source_file_name, source_file_data) in HAB_PLAN_BUILD_SOURCE_FILES {
        let source_file_path = build_dir.join(source_file_name);
        File::create(&source_file_path).with_context(|| {
                                           format!("Failed to create plan build source file '{}'",
                                                   source_file_path.display())
                                       })?
                                       .write_all(source_file_data)
                                       .with_context(|| {
                                           format!("Failed to write data to plan build source \
                                                    file '{}'",
                                                   source_file_path.display())
                                       })?;
    }
    let mut cmd = Command::new("bash");

    cmd.arg(build_dir.join("hab-plan-build.sh"))
       .arg(args.last().unwrap())
       .env("HAB_NATIVE_PACKAGE", "true");

    if let Some(position) = args.iter().position(|x| x == "-s") {
        cmd.env("HAB_SRC_PATH", &args[position + 1]);
    } else {
        cmd.env("HAB_SRC_PATH", env::current_dir().unwrap());
    }

    debug!("Executing habitat plan build script with command: [{:?}]",
           cmd);

    let mut child = cmd.spawn().context("Failed to run hab plan build script")?;
    let exit_status =
        child.wait()
             .context("Failed to wait for hab plan build script to run to completion")?;
    debug!("Habitat plan build script {}", exit_status);
    Ok(())
}
<commit_msg>fix: make failed native builds exit with error<commit_after>use crate::error::{Error,
                   Result};
use anyhow::{anyhow,
             Context};
use habitat_common::ui::UI;
use habitat_core::fs::cache_build_path;
use log::debug;
use std::{env,
          ffi::OsString,
          fs::{self,
               File},
          io::Write,
          process::Command};

const HAB_PLAN_BUILD_SOURCE_FILES: [(&str, &[u8]); 4] =
    [("environment.bash", include_bytes!("../../../../plan-build/bin/environment.bash")),
     ("shared.bash", include_bytes!("../../../../plan-build/bin/shared.bash")),
     ("public.bash", include_bytes!("../../../../plan-build/bin/public.bash")),
     ("hab-plan-build.sh", include_bytes!("../../../../plan-build/bin/hab-plan-build.sh"))];

pub fn start_native_studio(ui: &mut UI, args: &[OsString]) -> Result<()> {
    start_native_studio_impl(ui, args).map_err(Error::NativeStudioError)
}

fn start_native_studio_impl(_ui: &mut UI, args: &[OsString]) -> anyhow::Result<(), anyhow::Error> {
    let build_dir = cache_build_path(None::<&str>);
    fs::create_dir_all(&build_dir).context("Failed to create cache build directory for the \
                                            habitat plan build script")?;
    for (source_file_name, source_file_data) in HAB_PLAN_BUILD_SOURCE_FILES {
        let source_file_path = build_dir.join(source_file_name);
        File::create(&source_file_path).with_context(|| {
                                           format!("Failed to create plan build source file '{}'",
                                                   source_file_path.display())
                                       })?
                                       .write_all(source_file_data)
                                       .with_context(|| {
                                           format!("Failed to write data to plan build source \
                                                    file '{}'",
                                                   source_file_path.display())
                                       })?;
    }
    let mut cmd = Command::new("bash");

    cmd.arg(build_dir.join("hab-plan-build.sh"))
       .arg(args.last().unwrap())
       .env("HAB_NATIVE_PACKAGE", "true");

    if let Some(position) = args.iter().position(|x| x == "-s") {
        cmd.env("HAB_SRC_PATH", &args[position + 1]);
    } else {
        cmd.env("HAB_SRC_PATH", env::current_dir().unwrap());
    }

    debug!("Executing habitat plan build script with command: [{:?}]",
           cmd);

    let mut child = cmd.spawn().context("Failed to run hab plan build script")?;
    let exit_status =
        child.wait()
             .context("Failed to wait for hab plan build script to run to completion")?;
    debug!("Habitat plan build script {}", exit_status);
    if exit_status.success() {
        Ok(())
    } else {
        Err(anyhow!("Failed to build native habitat plan"))
    }
}
<|endoftext|>


<commit_before>// Thread parker implementation for Windows.
//
// This uses WaitOnAddress and WakeByAddressSingle if available (Windows 8+).
// This modern API is exactly the same as the futex syscalls the Linux thread
// parker uses. When These APIs are available, the implementation of this
// thread parker matches the Linux thread parker exactly.
//
// However, when the modern API is not available, this implementation falls
// back to NT Keyed Events, which are similar, but have some important
// differences. These are available since Windows XP.
//
// WaitOnAddress first checks the state of the thread parker to make sure it no
// WakeByAddressSingle calls can be missed between updating the parker state
// and calling the function.
//
// NtWaitForKeyedEvent does not have this option, and unconditionally blocks
// without checking the parker state first. Instead, NtReleaseKeyedEvent
// (unlike WakeByAddressSingle) *blocks* until it woke up a thread waiting for
// it by NtWaitForKeyedEvent. This way, we can be sure no events are missed,
// but we need to be careful not to block unpark() if park_timeout() was woken
// up by a timeout instead of unpark().
//
// Unlike WaitOnAddress, NtWaitForKeyedEvent/NtReleaseKeyedEvent operate on a
// HANDLE (created with NtCreateKeyedEvent). This means that we can be sure
// a succesfully awoken park() was awoken by unpark() and not a
// NtReleaseKeyedEvent call from some other code, as these events are not only
// matched by the key (address of the parker (state)), but also by this HANDLE.
// We lazily allocate this handle the first time it is needed.
//
// The fast path (calling park() after unpark() was already called) and the
// possible states are the same for both implementations. This is used here to
// make sure the fast path does not even check which API to use, but can return
// right away, independent of the used API. Only the slow paths (which will
// actually block/wake a thread) check which API is available and have
// different implementations.
//
// Unfortunately, NT Keyed Events are an undocumented Windows API. However:
// - This API is relatively simple with obvious behaviour, and there are
//   several (unofficial) articles documenting the details. [1]
// - `parking_lot` has been using this API for years (on Windows versions
//   before Windows 8). [2] Many big projects extensively use parking_lot,
//   such as servo and the Rust compiler itself.
// - It is the underlying API used by Windows SRW locks and Windows critical
//   sections. [3] [4]
// - The source code of the implementations of Wine, ReactOs, and Windows XP
//   are available and match the expected behaviour.
// - The main risk with an undocumented API is that it might change in the
//   future. But since we only use it for older versions of Windows, that's not
//   a problem.
// - Even if these functions do not block or wake as we expect (which is
//   unlikely, see all previous points), this implementation would still be
//   memory safe. The NT Keyed Events API is only used to sleep/block in the
//   right place.
//
// [1]: http://www.locklessinc.com/articles/keyed_events/
// [2]: https://github.com/Amanieu/parking_lot/commit/43abbc964e
// [3]: https://docs.microsoft.com/en-us/archive/msdn-magazine/2012/november/windows-with-c-the-evolution-of-synchronization-in-windows-and-c
// [4]: Windows Internals, Part 1, ISBN 9780735671300

use crate::convert::TryFrom;
use crate::ptr;
use crate::sync::atomic::{
    AtomicI8, AtomicUsize,
    Ordering::{Acquire, Relaxed, Release},
};
use crate::sys::{c, dur2timeout};
use crate::time::Duration;

pub struct Parker {
    state: AtomicI8,
}

const PARKED: i8 = -1;
const EMPTY: i8 = 0;
const NOTIFIED: i8 = 1;

// Notes about memory ordering:
//
// Memory ordering is only relevant for the relative ordering of operations
// between different variables. Even Ordering::Relaxed guarantees a
// monotonic/consistent order when looking at just a single atomic variable.
//
// So, since this parker is just a single atomic variable, we only need to look
// at the ordering guarantees we need to provide to the 'outside world'.
//
// The only memory ordering guarantee that parking and unparking provide, is
// that things which happened before unpark() are visible on the thread
// returning from park() afterwards. Otherwise, it was effectively unparked
// before unpark() was called while still consuming the 'token'.
//
// In other words, unpark() needs to synchronize with the part of park() that
// consumes the token and returns.
//
// This is done with a release-acquire synchronization, by using
// Ordering::Release when writing NOTIFIED (the 'token') in unpark(), and using
// Ordering::Acquire when reading this state in park() after waking up.
impl Parker {
    pub fn new() -> Self {
        Self { state: AtomicI8::new(EMPTY) }
    }

    // Assumes this is only called by the thread that owns the Parker,
    // which means that `self.state != PARKED`.
    pub unsafe fn park(&self) {
        // Change NOTIFIED=>EMPTY or EMPTY=>PARKED, and directly return in the
        // first case.
        if self.state.fetch_sub(1, Acquire) == NOTIFIED {
            return;
        }

        if c::WaitOnAddress::is_available() {
            loop {
                // Wait for something to happen, assuming it's still set to PARKED.
                c::WaitOnAddress(self.ptr(), &PARKED as *const _ as c::LPVOID, 1, c::INFINITE);
                // Change NOTIFIED=>EMPTY but leave PARKED alone.
                if self.state.compare_and_swap(NOTIFIED, EMPTY, Acquire) == NOTIFIED {
                    // Actually woken up by unpark().
                    return;
                } else {
                    // Spurious wake up. We loop to try again.
                }
            }
        } else {
            // Wait for unpark() to produce this event.
            c::NtWaitForKeyedEvent(keyed_event_handle(), self.ptr(), 0, ptr::null_mut());
            // Set the state back to EMPTY (from either PARKED or NOTIFIED).
            // Note that we don't just write EMPTY, but use swap() to also
            // include an acquire-ordered read to synchronize with unpark()'s
            // release-ordered write.
            self.state.swap(EMPTY, Acquire);
        }
    }

    // Assumes this is only called by the thread that owns the Parker,
    // which means that `self.state != PARKED`.
    pub unsafe fn park_timeout(&self, timeout: Duration) {
        // Change NOTIFIED=>EMPTY or EMPTY=>PARKED, and directly return in the
        // first case.
        if self.state.fetch_sub(1, Acquire) == NOTIFIED {
            return;
        }

        if c::WaitOnAddress::is_available() {
            // Wait for something to happen, assuming it's still set to PARKED.
            c::WaitOnAddress(self.ptr(), &PARKED as *const _ as c::LPVOID, 1, dur2timeout(timeout));
            // Set the state back to EMPTY (from either PARKED or NOTIFIED).
            // Note that we don't just write EMPTY, but use swap() to also
            // include an acquire-ordered read to synchronize with unpark()'s
            // release-ordered write.
            if self.state.swap(EMPTY, Acquire) == NOTIFIED {
                // Actually woken up by unpark().
            } else {
                // Timeout or spurious wake up.
                // We return either way, because we can't easily tell if it was the
                // timeout or not.
            }
        } else {
            // Need to wait for unpark() using NtWaitForKeyedEvent.
            let handle = keyed_event_handle();

            // NtWaitForKeyedEvent uses a unit of 100ns, and uses negative values
            // to indicate the monotonic clock.
            let mut timeout = match i64::try_from((timeout.as_nanos() + 99) / 100) {
                Ok(t) => -t,
                Err(_) => i64::MIN,
            };

            // Wait for unpark() to produce this event.
            let unparked = c::NtWaitForKeyedEvent(handle, self.ptr(), 0, &mut timeout) == c::STATUS_SUCCESS;

            // Set the state back to EMPTY (from either PARKED or NOTIFIED).
            let prev_state = self.state.swap(EMPTY, Acquire);

            if !unparked && prev_state == NOTIFIED {
                // We were awoken by a timeout, not by unpark(), but the state
                // was set to NOTIFIED, which means we *just* missed an
                // unpark(), which is now blocked on us to wait for it.
                // Wait for it to consume the event and unblock that thread.
                c::NtWaitForKeyedEvent(handle, self.ptr(), 0, ptr::null_mut());
            }
        }
    }

    pub fn unpark(&self) {
        // Change PARKED=>NOTIFIED, EMPTY=>NOTIFIED, or NOTIFIED=>NOTIFIED, and
        // wake the thread in the first case.
        //
        // Note that even NOTIFIED=>NOTIFIED results in a write. This is on
        // purpose, to make sure every unpark() has a release-acquire ordering
        // with park().
        if self.state.swap(NOTIFIED, Release) == PARKED {
            if c::WakeByAddressSingle::is_available() {
                unsafe {
                    c::WakeByAddressSingle(self.ptr());
                }
            } else {
                // If we run NtReleaseKeyedEvent before the waiting thread runs
                // NtWaitForKeyedEvent, this (shortly) blocks until we can wake it up.
                // If the waiting thread wakes up before we run NtReleaseKeyedEvent
                // (e.g. due to a timeout), this blocks until we do wake up a thread.
                // To prevent this thread from blocking indefinitely in that case,
                // park_impl() will, after seeing the state set to NOTIFIED after
                // waking up, call NtWaitForKeyedEvent again to unblock us.
                unsafe {
                    c::NtReleaseKeyedEvent(keyed_event_handle(), self.ptr(), 0, ptr::null_mut());
                }
            }
        }
    }

    fn ptr(&self) -> c::LPVOID {
        &self.state as *const _ as c::LPVOID
    }
}

fn keyed_event_handle() -> c::HANDLE {
    const INVALID: usize = !0;
    static HANDLE: AtomicUsize = AtomicUsize::new(INVALID);
    match HANDLE.load(Relaxed) {
        INVALID => {
            let mut handle = c::INVALID_HANDLE_VALUE;
            unsafe {
                match c::NtCreateKeyedEvent(
                    &mut handle,
                    c::GENERIC_READ | c::GENERIC_WRITE,
                    ptr::null_mut(),
                    0,
                ) {
                    c::STATUS_SUCCESS => {}
                    r => panic!("Unable to create keyed event handle: error {}", r),
                }
            }
            match HANDLE.compare_exchange(INVALID, handle as usize, Relaxed, Relaxed) {
                Ok(_) => handle,
                Err(h) => {
                    // Lost the race to another thread initializing HANDLE before we did.
                    // Closing our handle and using theirs instead.
                    unsafe {
                        c::CloseHandle(handle);
                    }
                    h as c::HANDLE
                }
            }
        }
        handle => handle as c::HANDLE,
    }
}
<commit_msg>Add comment documenting NtWaitForKeyedEvent's timeout interpretation.<commit_after>// Thread parker implementation for Windows.
//
// This uses WaitOnAddress and WakeByAddressSingle if available (Windows 8+).
// This modern API is exactly the same as the futex syscalls the Linux thread
// parker uses. When These APIs are available, the implementation of this
// thread parker matches the Linux thread parker exactly.
//
// However, when the modern API is not available, this implementation falls
// back to NT Keyed Events, which are similar, but have some important
// differences. These are available since Windows XP.
//
// WaitOnAddress first checks the state of the thread parker to make sure it no
// WakeByAddressSingle calls can be missed between updating the parker state
// and calling the function.
//
// NtWaitForKeyedEvent does not have this option, and unconditionally blocks
// without checking the parker state first. Instead, NtReleaseKeyedEvent
// (unlike WakeByAddressSingle) *blocks* until it woke up a thread waiting for
// it by NtWaitForKeyedEvent. This way, we can be sure no events are missed,
// but we need to be careful not to block unpark() if park_timeout() was woken
// up by a timeout instead of unpark().
//
// Unlike WaitOnAddress, NtWaitForKeyedEvent/NtReleaseKeyedEvent operate on a
// HANDLE (created with NtCreateKeyedEvent). This means that we can be sure
// a succesfully awoken park() was awoken by unpark() and not a
// NtReleaseKeyedEvent call from some other code, as these events are not only
// matched by the key (address of the parker (state)), but also by this HANDLE.
// We lazily allocate this handle the first time it is needed.
//
// The fast path (calling park() after unpark() was already called) and the
// possible states are the same for both implementations. This is used here to
// make sure the fast path does not even check which API to use, but can return
// right away, independent of the used API. Only the slow paths (which will
// actually block/wake a thread) check which API is available and have
// different implementations.
//
// Unfortunately, NT Keyed Events are an undocumented Windows API. However:
// - This API is relatively simple with obvious behaviour, and there are
//   several (unofficial) articles documenting the details. [1]
// - `parking_lot` has been using this API for years (on Windows versions
//   before Windows 8). [2] Many big projects extensively use parking_lot,
//   such as servo and the Rust compiler itself.
// - It is the underlying API used by Windows SRW locks and Windows critical
//   sections. [3] [4]
// - The source code of the implementations of Wine, ReactOs, and Windows XP
//   are available and match the expected behaviour.
// - The main risk with an undocumented API is that it might change in the
//   future. But since we only use it for older versions of Windows, that's not
//   a problem.
// - Even if these functions do not block or wake as we expect (which is
//   unlikely, see all previous points), this implementation would still be
//   memory safe. The NT Keyed Events API is only used to sleep/block in the
//   right place.
//
// [1]: http://www.locklessinc.com/articles/keyed_events/
// [2]: https://github.com/Amanieu/parking_lot/commit/43abbc964e
// [3]: https://docs.microsoft.com/en-us/archive/msdn-magazine/2012/november/windows-with-c-the-evolution-of-synchronization-in-windows-and-c
// [4]: Windows Internals, Part 1, ISBN 9780735671300

use crate::convert::TryFrom;
use crate::ptr;
use crate::sync::atomic::{
    AtomicI8, AtomicUsize,
    Ordering::{Acquire, Relaxed, Release},
};
use crate::sys::{c, dur2timeout};
use crate::time::Duration;

pub struct Parker {
    state: AtomicI8,
}

const PARKED: i8 = -1;
const EMPTY: i8 = 0;
const NOTIFIED: i8 = 1;

// Notes about memory ordering:
//
// Memory ordering is only relevant for the relative ordering of operations
// between different variables. Even Ordering::Relaxed guarantees a
// monotonic/consistent order when looking at just a single atomic variable.
//
// So, since this parker is just a single atomic variable, we only need to look
// at the ordering guarantees we need to provide to the 'outside world'.
//
// The only memory ordering guarantee that parking and unparking provide, is
// that things which happened before unpark() are visible on the thread
// returning from park() afterwards. Otherwise, it was effectively unparked
// before unpark() was called while still consuming the 'token'.
//
// In other words, unpark() needs to synchronize with the part of park() that
// consumes the token and returns.
//
// This is done with a release-acquire synchronization, by using
// Ordering::Release when writing NOTIFIED (the 'token') in unpark(), and using
// Ordering::Acquire when reading this state in park() after waking up.
impl Parker {
    pub fn new() -> Self {
        Self { state: AtomicI8::new(EMPTY) }
    }

    // Assumes this is only called by the thread that owns the Parker,
    // which means that `self.state != PARKED`.
    pub unsafe fn park(&self) {
        // Change NOTIFIED=>EMPTY or EMPTY=>PARKED, and directly return in the
        // first case.
        if self.state.fetch_sub(1, Acquire) == NOTIFIED {
            return;
        }

        if c::WaitOnAddress::is_available() {
            loop {
                // Wait for something to happen, assuming it's still set to PARKED.
                c::WaitOnAddress(self.ptr(), &PARKED as *const _ as c::LPVOID, 1, c::INFINITE);
                // Change NOTIFIED=>EMPTY but leave PARKED alone.
                if self.state.compare_and_swap(NOTIFIED, EMPTY, Acquire) == NOTIFIED {
                    // Actually woken up by unpark().
                    return;
                } else {
                    // Spurious wake up. We loop to try again.
                }
            }
        } else {
            // Wait for unpark() to produce this event.
            c::NtWaitForKeyedEvent(keyed_event_handle(), self.ptr(), 0, ptr::null_mut());
            // Set the state back to EMPTY (from either PARKED or NOTIFIED).
            // Note that we don't just write EMPTY, but use swap() to also
            // include an acquire-ordered read to synchronize with unpark()'s
            // release-ordered write.
            self.state.swap(EMPTY, Acquire);
        }
    }

    // Assumes this is only called by the thread that owns the Parker,
    // which means that `self.state != PARKED`.
    pub unsafe fn park_timeout(&self, timeout: Duration) {
        // Change NOTIFIED=>EMPTY or EMPTY=>PARKED, and directly return in the
        // first case.
        if self.state.fetch_sub(1, Acquire) == NOTIFIED {
            return;
        }

        if c::WaitOnAddress::is_available() {
            // Wait for something to happen, assuming it's still set to PARKED.
            c::WaitOnAddress(self.ptr(), &PARKED as *const _ as c::LPVOID, 1, dur2timeout(timeout));
            // Set the state back to EMPTY (from either PARKED or NOTIFIED).
            // Note that we don't just write EMPTY, but use swap() to also
            // include an acquire-ordered read to synchronize with unpark()'s
            // release-ordered write.
            if self.state.swap(EMPTY, Acquire) == NOTIFIED {
                // Actually woken up by unpark().
            } else {
                // Timeout or spurious wake up.
                // We return either way, because we can't easily tell if it was the
                // timeout or not.
            }
        } else {
            // Need to wait for unpark() using NtWaitForKeyedEvent.
            let handle = keyed_event_handle();

            // NtWaitForKeyedEvent uses a unit of 100ns, and uses negative
            // values to indicate a relative time on the monotonic clock.
            // This is documented here for the underlying KeWaitForSingleObject function:
            // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-kewaitforsingleobject
            let mut timeout = match i64::try_from((timeout.as_nanos() + 99) / 100) {
                Ok(t) => -t,
                Err(_) => i64::MIN,
            };

            // Wait for unpark() to produce this event.
            let unparked = c::NtWaitForKeyedEvent(handle, self.ptr(), 0, &mut timeout) == c::STATUS_SUCCESS;

            // Set the state back to EMPTY (from either PARKED or NOTIFIED).
            let prev_state = self.state.swap(EMPTY, Acquire);

            if !unparked && prev_state == NOTIFIED {
                // We were awoken by a timeout, not by unpark(), but the state
                // was set to NOTIFIED, which means we *just* missed an
                // unpark(), which is now blocked on us to wait for it.
                // Wait for it to consume the event and unblock that thread.
                c::NtWaitForKeyedEvent(handle, self.ptr(), 0, ptr::null_mut());
            }
        }
    }

    pub fn unpark(&self) {
        // Change PARKED=>NOTIFIED, EMPTY=>NOTIFIED, or NOTIFIED=>NOTIFIED, and
        // wake the thread in the first case.
        //
        // Note that even NOTIFIED=>NOTIFIED results in a write. This is on
        // purpose, to make sure every unpark() has a release-acquire ordering
        // with park().
        if self.state.swap(NOTIFIED, Release) == PARKED {
            if c::WakeByAddressSingle::is_available() {
                unsafe {
                    c::WakeByAddressSingle(self.ptr());
                }
            } else {
                // If we run NtReleaseKeyedEvent before the waiting thread runs
                // NtWaitForKeyedEvent, this (shortly) blocks until we can wake it up.
                // If the waiting thread wakes up before we run NtReleaseKeyedEvent
                // (e.g. due to a timeout), this blocks until we do wake up a thread.
                // To prevent this thread from blocking indefinitely in that case,
                // park_impl() will, after seeing the state set to NOTIFIED after
                // waking up, call NtWaitForKeyedEvent again to unblock us.
                unsafe {
                    c::NtReleaseKeyedEvent(keyed_event_handle(), self.ptr(), 0, ptr::null_mut());
                }
            }
        }
    }

    fn ptr(&self) -> c::LPVOID {
        &self.state as *const _ as c::LPVOID
    }
}

fn keyed_event_handle() -> c::HANDLE {
    const INVALID: usize = !0;
    static HANDLE: AtomicUsize = AtomicUsize::new(INVALID);
    match HANDLE.load(Relaxed) {
        INVALID => {
            let mut handle = c::INVALID_HANDLE_VALUE;
            unsafe {
                match c::NtCreateKeyedEvent(
                    &mut handle,
                    c::GENERIC_READ | c::GENERIC_WRITE,
                    ptr::null_mut(),
                    0,
                ) {
                    c::STATUS_SUCCESS => {}
                    r => panic!("Unable to create keyed event handle: error {}", r),
                }
            }
            match HANDLE.compare_exchange(INVALID, handle as usize, Relaxed, Relaxed) {
                Ok(_) => handle,
                Err(h) => {
                    // Lost the race to another thread initializing HANDLE before we did.
                    // Closing our handle and using theirs instead.
                    unsafe {
                        c::CloseHandle(handle);
                    }
                    h as c::HANDLE
                }
            }
        }
        handle => handle as c::HANDLE,
    }
}
<|endoftext|>