<commit_before>use parser;

struct Builtin {
    name: &'static str,
    func: fn(cmd: parser::Command) -> Result<(),&'static str>
}

pub struct BuiltinManager {
    builtins: Vec<Builtin>
}

impl BuiltinManager {
    pub fn new() -> BuiltinManager {
        BuiltinManager {
            builtins: vec!(
                Builtin{
                    name: "test",
                    func: builtin_test
                }
            )
        }
    }

    pub fn is_builtin(&self, cmd: String) -> bool {
        for builtin in self.builtins.iter() {
            if builtin.name == cmd {
                return true
            }
        }
        return false
    }
    pub fn run(&self, cmd: parser::Command) -> Result<(), &'static str> {
        for builtin in self.builtins.iter() {
            if builtin.name == cmd.cmd {
                let func = builtin.func;
                return func(cmd);
            }
        }
        return Err("Command not found")
    }
}

fn builtin_test (cmd: parser::Command) -> Result<(),&'static str> {
    println!("TEST: {:?}", cmd.argv);
    Ok(())
}
<commit_msg>Added cd builtin so that you can move about<commit_after>use parser;

use std::env;
use std::path;

struct Builtin {
    name: &'static str,
    func: fn(cmd: parser::Command) -> Result<(),&'static str>
}

pub struct BuiltinManager {
    builtins: Vec<Builtin>
}

impl BuiltinManager {
    pub fn new() -> BuiltinManager {
        BuiltinManager {
            builtins: vec!(
                Builtin{
                    name: "test",
                    func: builtin_test
                },
                Builtin{
                    name: "cd",
                    func: builtin_cd
                }
            )
        }
    }

    pub fn is_builtin(&self, cmd: String) -> bool {
        for builtin in self.builtins.iter() {
            if builtin.name == cmd {
                return true
            }
        }
        return false
    }
    pub fn run(&self, cmd: parser::Command) -> Result<(), &'static str> {
        for builtin in self.builtins.iter() {
            if builtin.name == cmd.cmd {
                let func = builtin.func;
                return func(cmd);
            }
        }
        return Err("Command not found")
    }
}

fn builtin_test (cmd: parser::Command) -> Result<(),&'static str> {
    println!("TEST: {:?}", cmd.argv);
    Ok(())
}

fn builtin_cd (cmd: parser::Command) -> Result<(), &'static str> {
    match cmd.argv.len() {
        0 => {
            let home = env::home_dir().expect("Home dir not found");
            match env::set_current_dir(&home) {
                Ok(_) => Ok(()),
                Err(_) => Err("Unable to cd to home")
            }
        }
        1 => {
            let path = path::Path::new(&cmd.argv[0]);
            if path.is_relative() {
                let current = env::current_dir().expect("Failed to get PWD");
				match env::set_current_dir(&current.join(path)) {
					Ok(_) => Ok(()),
					Err(_) => Err("Unable to cd to relative path")
				}
            } else {
				match env::set_current_dir(&path) {
					Ok(_) => Ok(()),
					Err(_) => Err("Unable to cd to relative path")
				}
            }
        }
        _ => Err("Too many arguments")
    }
}
<|endoftext|>


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

//! Default authentication when running within GCP
//!
//! Inspired by golang.org/x/oauth2/google and cloud.google.com/go/compute/metadata

use chrono::{DateTime, Utc};
use futures::join;
use lazy_static::lazy_static;
use percent_encoding::percent_encode;
use serde::{Deserialize, Serialize};
use std::{
    cell::RefCell,
    collections::HashMap,
    net::{SocketAddr, ToSocketAddrs},
    sync::Mutex,
};
use tink_core::{utils::wrap_err, TinkError};
use tokio::time::timeout;

/// Metadata server IP address when running within GCP.
const METADATA_IP_STR: &str = "169.254.169.254";
const METADATA_IP: [u8; 4] = [169, 254, 169, 254];

/// Environment variable specifying the GCE metadata hostname. If the environment variable is empty,
/// the default [`METADATA_IP`] value will be used. Name chosen to match the one used by Go package
/// cloud.google.com/go/compute/metadata.
const METADATA_HOST_ENV: &str = "GCE_METADATA_HOST";

/// User agent to report on requests to GCE metadata server.
const USER_AGENT: &str = "tink-gcpkms/Rust";

lazy_static! {
    /// Cached indication of whether code is running on GCE.
    static ref ON_GCE: Mutex<Option<bool>> = Mutex::new(None);
}

/// Indicate whether code is running on GCE.  First use of this function may be slow; subsequent
/// invocations return cached result.
async fn on_gce() -> bool {
    if let Some(v) = *ON_GCE.lock().unwrap(/* safe: lock */) {
        return v;
    }
    let result = on_gce_test().await;
    *ON_GCE.lock().unwrap() = Some(result); // safe: lock
    result
}

/// Determine whether code is running on GCE.
async fn on_gce_test() -> bool {
    // If `GCE_METADATA_HOST` is set in the environment, assume we're on GCE.
    if let Ok(val) = std::env::var(METADATA_HOST_ENV) {
        if !val.is_empty() {
            return true;
        }
    }

    // Method 1: check header returned by metadata server
    let http_result = async {
        let client = hyper::Client::new();
        let uri = match format!("http://{}", METADATA_IP_STR).parse() {
            Ok(v) => v,
            Err(_) => return false,
        };
        let rsp = match client.get(uri).await {
            Ok(v) => v,
            Err(_) => return false,
        };
        return rsp.headers().get("Metadata-Flavor")
            == Some(&http::HeaderValue::from_static("Google"));
    };
    // Ensure the HTTP request times out in a reasonable period.
    let timed_http_result = async {
        timeout(std::time::Duration::from_secs(2), http_result)
            .await
            .unwrap_or(false)
    };

    // Method 2: check DNS resolution of metadata server name
    let dns_result = async {
        if let Ok(iter) = "metadata.google.internal:80".to_socket_addrs() {
            let needle = SocketAddr::from((METADATA_IP, 80));
            for addr in iter {
                if addr == needle {
                    return true;
                }
            }
        }
        false
    };

    // Run both methods in parallel and combine.
    let results = join!(timed_http_result, dns_result);
    return results.0 || results.1;
}

/// Retrieve a specified piece of metadata from the metadata server.
async fn get_gce_metadata(name: &str) -> Result<String, TinkError> {
    // Allow the metadata server location to be overridden by environment (to allow testing).
    let host = std::env::var(METADATA_HOST_ENV).unwrap_or_else(|_e| METADATA_IP_STR.to_string());
    let authority: http::uri::Authority = host
        .parse()
        .map_err(|e| wrap_err("failed to parse host", e))?;
    let uri = hyper::Uri::builder()
        .scheme("http")
        .authority(authority)
        .path_and_query(format!("/computeMetadata/v1/{}", name))
        .build()
        .map_err(|e| wrap_err("failed to build Uri", e))?;
    let client = hyper::Client::new();

    let req = hyper::Request::builder()
        .method(http::method::Method::GET)
        .uri(uri)
        .header(http::header::USER_AGENT, USER_AGENT)
        .header("Metadata-Flavor", "Google")
        .body(hyper::Body::empty())
        .map_err(|e| wrap_err("failed to build request", e))?;
    let rsp = client
        .request(req)
        .await
        .map_err(|e| wrap_err("failed to execute request", e))?;
    if rsp.status() != http::StatusCode::OK {
        return Err("failed HTTP request".into());
    }
    let bytes = hyper::body::to_bytes(rsp.into_body())
        .await
        .map_err(|e| wrap_err("failed to retrieve response body", e))?;
    String::from_utf8(bytes.to_vec()).map_err(|e| wrap_err("failed to convert body to string", e))
}

/// Token structure for JSON returned by metadata server
#[derive(Deserialize)]
struct Token {
    pub access_token: String,
    pub expires_in: i64,
    // Also has `token_type: String` which we ignore.
}

/// Local copy of [`yup_oauth2::AccessToken`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
struct AccessTokenClone {
    pub value: String,
    pub expires_at: Option<DateTime<Utc>>,
}

pub struct DefaultServiceAccountAuthenticator {
    // Map from scopelist to access tokens.
    // We don't attempt to canonicalize the scopelist (so a,b is different than b,a).
    tokens: RefCell<HashMap<String, yup_oauth2::AccessToken>>,
}

impl DefaultServiceAccountAuthenticator {
    pub async fn new() -> Result<Self, TinkError> {
        if !on_gce().await {
            return Err("not running on GCE".into());
        }
        Ok(Self {
            tokens: RefCell::new(HashMap::new()),
        })
    }

    pub async fn token(&self, scopes: &[&str]) -> Result<yup_oauth2::AccessToken, TinkError> {
        let scopelist = scopes.join(",");

        if let Some(token) = self.tokens.borrow().get(&scopelist) {
            if !token.is_expired() {
                return Ok(token.clone());
            }
        }

        let token = self.refresh_token(&scopelist).await?;

        // Cache the token until expiry time.
        self.tokens
            .borrow_mut()
            .insert(scopelist.to_string(), token.clone());
        Ok(token)
    }
    pub async fn refresh_token(
        &self,
        scopelist: &str,
    ) -> Result<yup_oauth2::AccessToken, TinkError> {
        if !on_gce().await {
            return Err("not running on GCE".into());
        }

        // Retrieve a token from the local metadata server.
        let token_json = get_gce_metadata(&format!(
            "instance/service-accounts/default/token?{}",
            percent_encode(scopelist.as_bytes(), crate::DEFAULT_URL_ENCODE_SET),
        ))
        .await?;

        let token: Token = serde_json::from_str(&token_json)
            .map_err(|e| wrap_err("failed to parse token JSON", e))?;
        if token.access_token.is_empty() || token.expires_in == 0 {
            return Err("invalid token contents".into());
        }
        let token_expiry = Utc::now()
            .checked_add_signed(chrono::Duration::seconds(token.expires_in))
            .ok_or_else(|| TinkError::new("failed to calculate expiry time"))?;

        // The internals of [`yup_oauth2::TokenInfo`] and [`yup_oauth2::AccessToken`] are
        // private, but deserialization is accessible, so round-trip via JSON using a clone
        // of the structure.
        let token_clone = AccessTokenClone {
            value: token.access_token,
            expires_at: Some(token_expiry),
        };
        let token_json = serde_json::to_string(&token_clone)
            .map_err(|e| wrap_err("failed to JSON encode", e))?;
        let token: yup_oauth2::AccessToken = serde_json::from_str(&token_json)
            .map_err(|e| wrap_err("failed to parse internal JSON", e))?;

        Ok(token)
    }
}

impl super::Authenticator for DefaultServiceAccountAuthenticator {
    fn get_token(
        &self,
        runtime: &mut tokio::runtime::Runtime,
        scopes: &[&str],
    ) -> Result<yup_oauth2::AccessToken, TinkError> {
        runtime.block_on(self.token(scopes))
    }
}
<commit_msg>tink-gcpkms: fix newer clippy warning<commit_after>// Copyright 2021 The Tink-Rust Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

//! Default authentication when running within GCP
//!
//! Inspired by golang.org/x/oauth2/google and cloud.google.com/go/compute/metadata

use chrono::{DateTime, Utc};
use futures::join;
use lazy_static::lazy_static;
use percent_encoding::percent_encode;
use serde::{Deserialize, Serialize};
use std::{
    cell::RefCell,
    collections::HashMap,
    net::{SocketAddr, ToSocketAddrs},
    sync::Mutex,
};
use tink_core::{utils::wrap_err, TinkError};
use tokio::time::timeout;

/// Metadata server IP address when running within GCP.
const METADATA_IP_STR: &str = "169.254.169.254";
const METADATA_IP: [u8; 4] = [169, 254, 169, 254];

/// Environment variable specifying the GCE metadata hostname. If the environment variable is empty,
/// the default [`METADATA_IP`] value will be used. Name chosen to match the one used by Go package
/// cloud.google.com/go/compute/metadata.
const METADATA_HOST_ENV: &str = "GCE_METADATA_HOST";

/// User agent to report on requests to GCE metadata server.
const USER_AGENT: &str = "tink-gcpkms/Rust";

lazy_static! {
    /// Cached indication of whether code is running on GCE.
    static ref ON_GCE: Mutex<Option<bool>> = Mutex::new(None);
}

/// Indicate whether code is running on GCE.  First use of this function may be slow; subsequent
/// invocations return cached result.
async fn on_gce() -> bool {
    if let Some(v) = *ON_GCE.lock().unwrap(/* safe: lock */) {
        return v;
    }
    let result = on_gce_test().await;
    *ON_GCE.lock().unwrap() = Some(result); // safe: lock
    result
}

/// Determine whether code is running on GCE.
async fn on_gce_test() -> bool {
    // If `GCE_METADATA_HOST` is set in the environment, assume we're on GCE.
    if let Ok(val) = std::env::var(METADATA_HOST_ENV) {
        if !val.is_empty() {
            return true;
        }
    }

    // Method 1: check header returned by metadata server
    let http_result = async {
        let client = hyper::Client::new();
        let uri = match format!("http://{}", METADATA_IP_STR).parse() {
            Ok(v) => v,
            Err(_) => return false,
        };
        let rsp = match client.get(uri).await {
            Ok(v) => v,
            Err(_) => return false,
        };
        return rsp.headers().get("Metadata-Flavor")
            == Some(&http::HeaderValue::from_static("Google"));
    };
    // Ensure the HTTP request times out in a reasonable period.
    let timed_http_result = async {
        timeout(std::time::Duration::from_secs(2), http_result)
            .await
            .unwrap_or(false)
    };

    // Method 2: check DNS resolution of metadata server name
    let dns_result = async {
        if let Ok(iter) = "metadata.google.internal:80".to_socket_addrs() {
            let needle = SocketAddr::from((METADATA_IP, 80));
            for addr in iter {
                if addr == needle {
                    return true;
                }
            }
        }
        false
    };

    // Run both methods in parallel and combine.
    let results = join!(timed_http_result, dns_result);
    results.0 || results.1
}

/// Retrieve a specified piece of metadata from the metadata server.
async fn get_gce_metadata(name: &str) -> Result<String, TinkError> {
    // Allow the metadata server location to be overridden by environment (to allow testing).
    let host = std::env::var(METADATA_HOST_ENV).unwrap_or_else(|_e| METADATA_IP_STR.to_string());
    let authority: http::uri::Authority = host
        .parse()
        .map_err(|e| wrap_err("failed to parse host", e))?;
    let uri = hyper::Uri::builder()
        .scheme("http")
        .authority(authority)
        .path_and_query(format!("/computeMetadata/v1/{}", name))
        .build()
        .map_err(|e| wrap_err("failed to build Uri", e))?;
    let client = hyper::Client::new();

    let req = hyper::Request::builder()
        .method(http::method::Method::GET)
        .uri(uri)
        .header(http::header::USER_AGENT, USER_AGENT)
        .header("Metadata-Flavor", "Google")
        .body(hyper::Body::empty())
        .map_err(|e| wrap_err("failed to build request", e))?;
    let rsp = client
        .request(req)
        .await
        .map_err(|e| wrap_err("failed to execute request", e))?;
    if rsp.status() != http::StatusCode::OK {
        return Err("failed HTTP request".into());
    }
    let bytes = hyper::body::to_bytes(rsp.into_body())
        .await
        .map_err(|e| wrap_err("failed to retrieve response body", e))?;
    String::from_utf8(bytes.to_vec()).map_err(|e| wrap_err("failed to convert body to string", e))
}

/// Token structure for JSON returned by metadata server
#[derive(Deserialize)]
struct Token {
    pub access_token: String,
    pub expires_in: i64,
    // Also has `token_type: String` which we ignore.
}

/// Local copy of [`yup_oauth2::AccessToken`].
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
struct AccessTokenClone {
    pub value: String,
    pub expires_at: Option<DateTime<Utc>>,
}

pub struct DefaultServiceAccountAuthenticator {
    // Map from scopelist to access tokens.
    // We don't attempt to canonicalize the scopelist (so a,b is different than b,a).
    tokens: RefCell<HashMap<String, yup_oauth2::AccessToken>>,
}

impl DefaultServiceAccountAuthenticator {
    pub async fn new() -> Result<Self, TinkError> {
        if !on_gce().await {
            return Err("not running on GCE".into());
        }
        Ok(Self {
            tokens: RefCell::new(HashMap::new()),
        })
    }

    pub async fn token(&self, scopes: &[&str]) -> Result<yup_oauth2::AccessToken, TinkError> {
        let scopelist = scopes.join(",");

        if let Some(token) = self.tokens.borrow().get(&scopelist) {
            if !token.is_expired() {
                return Ok(token.clone());
            }
        }

        let token = self.refresh_token(&scopelist).await?;

        // Cache the token until expiry time.
        self.tokens
            .borrow_mut()
            .insert(scopelist.to_string(), token.clone());
        Ok(token)
    }
    pub async fn refresh_token(
        &self,
        scopelist: &str,
    ) -> Result<yup_oauth2::AccessToken, TinkError> {
        if !on_gce().await {
            return Err("not running on GCE".into());
        }

        // Retrieve a token from the local metadata server.
        let token_json = get_gce_metadata(&format!(
            "instance/service-accounts/default/token?{}",
            percent_encode(scopelist.as_bytes(), crate::DEFAULT_URL_ENCODE_SET),
        ))
        .await?;

        let token: Token = serde_json::from_str(&token_json)
            .map_err(|e| wrap_err("failed to parse token JSON", e))?;
        if token.access_token.is_empty() || token.expires_in == 0 {
            return Err("invalid token contents".into());
        }
        let token_expiry = Utc::now()
            .checked_add_signed(chrono::Duration::seconds(token.expires_in))
            .ok_or_else(|| TinkError::new("failed to calculate expiry time"))?;

        // The internals of [`yup_oauth2::TokenInfo`] and [`yup_oauth2::AccessToken`] are
        // private, but deserialization is accessible, so round-trip via JSON using a clone
        // of the structure.
        let token_clone = AccessTokenClone {
            value: token.access_token,
            expires_at: Some(token_expiry),
        };
        let token_json = serde_json::to_string(&token_clone)
            .map_err(|e| wrap_err("failed to JSON encode", e))?;
        let token: yup_oauth2::AccessToken = serde_json::from_str(&token_json)
            .map_err(|e| wrap_err("failed to parse internal JSON", e))?;

        Ok(token)
    }
}

impl super::Authenticator for DefaultServiceAccountAuthenticator {
    fn get_token(
        &self,
        runtime: &mut tokio::runtime::Runtime,
        scopes: &[&str],
    ) -> Result<yup_oauth2::AccessToken, TinkError> {
        runtime.block_on(self.token(scopes))
    }
}
<|endoftext|>


<commit_before><commit_msg>Remove unused imports<commit_after><|endoftext|>


<commit_before>//!
//! The API for communicating with devices.
//!
//! This API is provided as Traits to be implemented:
//!
//! - by the low-level layers of the FoxBox, including the adapters;
//! - by test suites and tools that need to simulate connected devices.
//!
//! In turn, this API is used to implement:
//!
//! - the public-facing REST and WebSocket API;
//! - the rules API (ThinkerBell).
//!
//!

use services::*;
use selector::*;
use values::{ Value, Range, TypeError };
use util::{ Exactly, Id };

use transformable_channels::mpsc::*;

/// An error that arose during interaction with either a device, an adapter or the
/// adapter manager
#[derive(Serialize, Debug, Clone)]
pub enum Error {
    /// Attempting to fetch a value from a Channel<Getter> that doesn't support this operation.
    GetterDoesNotSupportPolling(Id<Getter>),

    /// Attempting to watch a value from a Channel<Getter> that doesn't support this operation.
    GetterDoesNotSupportWatching(Id<Getter>),

    /// Attempting to watch all values from a Channel<Getter> that requires a filter.
    /// For instance, some Channel<Getter> may be updated 60 times per second. Attempting to
    /// watch all values could easily exceed the capacity of the network or exhaust the battery.
    /// In such a case, the adapter should return this error.
    GetterRequiresThresholdForWatching(Id<Getter>),

    /// Attempting to send a value with a wrong type.
    TypeError(TypeError),

    /// Attempting to use an inconsistent range. For instance, one with `min > max`.
    RangeError(Range),

    /// Attempting to send an invalid value. For instance, a time of day larger than 24h.
    InvalidValue(Value),

    /// An error internal to the foxbox or an adapter. Normally, these errors should never
    /// arise from the high-level API.
    InternalError(InternalError),
}

#[derive(Serialize, Debug, Clone)]
pub enum InternalError {
    /// Attempting to fetch or watch a getter that isn't registered.
    NoSuchGetter(Id<Getter>),
    /// Attempting to send values to a setter that isn't registered.
    NoSuchSetter(Id<Setter>),
    /// Attempting to access a service that isn't registered.
    NoSuchService(Id<ServiceId>),
    /// Attempting to access an adapter that isn't registered.
    NoSuchAdapter(Id<AdapterId>),

    /// Attempting to register a getter with an id that is already used.
    DuplicateGetter(Id<Getter>),
    /// Attempting to register a setter with an id that is already used.
    DuplicateSetter(Id<Setter>),
    /// Attempting to register a service with an id that is already used.
    DuplicateService(Id<ServiceId>),
    /// Attempting to register an adapter with an id that is already used.
    DuplicateAdapter(Id<AdapterId>),

    /// Attempting to register a channel with an adapter that doesn't match that of its service.
    ConflictingAdapter(Id<AdapterId>, Id<AdapterId>),

    /// Attempting to register a service in an invalid initial state. Typically, a service that
    /// pretends that it already has channels.
    InvalidInitialService,
}

/// An event during watching.
#[derive(Serialize, Debug, Clone)]
pub enum WatchEvent {
    /// If a range was specified when we registered for watching, `EnterRange` is fired whenever
    /// we enter this range. If `Always` was specified, `EnterRange` is fired whenever a new value
    /// is available. Otherwise, never fired.
    EnterRange {
        /// The channel that sent the value.
        from: Id<Getter>,

        /// The actual value.
        value: Value
    },

    /// If a range was specified when we registered for watching, `ExitRange` is fired whenever
    /// we exit this range. Otherwise, never fired.
    ExitRange {
        /// The channel that sent the value.
        from: Id<Getter>,

        /// The actual value.
        value: Value
    },

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// removed. Payload is the id of the device that was removed.
    GetterRemoved(Id<Getter>),

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// added. Payload is the id of the device that was added.
    GetterAdded(Id<Getter>),

    /// One of the channels encountered an error during initialization.
    /// This channel will not be watched, but other channels will remain
    /// watched.
    InitializationError {
        channel: Id<Getter>,
        error: Error
    },
}

/// A bunch of results coming from different sources.
pub type ResultMap<K, T, E> = Vec<(K, Result<T, E>)>;

/// A bunch of instructions, going to different targets.
pub type TargetMap<K, T> = Vec<(Vec<K>, T)>;

/// A handle to the public API.
pub trait API {
    /// Get the metadata on services matching some conditions.
    ///
    /// A call to `API::get_services(vec![req1, req2, ...])` will return
    /// the metadata on all services matching _either_ `req1` or `req2`
    /// or ...
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services`
    ///
    /// ## Requests
    ///
    /// Any JSON that can be deserialized to a `Vec<ServiceSelector>`. See
    /// the implementation of `ServiceSelector` for details.
    ///
    /// ### Example
    ///
    /// Selector all doors in the entrance (tags `door`, `entrance`)
    /// that support setter channel `OpenClose`
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door"],
    ///   "getters": [
    ///     {
    ///       "kind": {
    ///         "Exactly": {
    ///           "OpenClose": []
    ///         }
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    ///
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing an array of `Service`. See the implementation
    /// of `Service` for details.
    ///
    /// ### Example
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door", "somevendor"],
    ///   "id: "some-service-id",
    ///   "getters": [],
    ///   "setters": [
    ///     "tags": [...],
    ///     "id": "some-channel-id",
    ///     "service": "some-service-id",
    ///     "last_seen": "some-date",
    ///     "mechanism": {
    ///       "Setter":  {
    ///         "kind": {
    ///           "OnOff": []
    ///         },
    ///         "push": [5000],
    ///         "updated": "some-date",
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    fn get_services(&self, &[ServiceSelector]) -> Vec<Service>;

    /// Label a set of services with a set of tags.
    ///
    /// A call to `API::put_service_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the services matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of services
    /// matching any of the selectors.
    ///
    /// Some of the services may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/tag`
    ///
    /// ## Getters
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<ServiceSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON string representing a number.
    fn add_service_tags(&self, selectors: &[ServiceSelector], tags: &[Id<TagId>]) -> usize;

    /// Remove a set of tags from a set of services.
    ///
    /// A call to `API::delete_service_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the services matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of services
    /// matching any of the selectors.
    ///
    /// Some of the services may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/services/tag`
    ///
    /// ## Getters
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<ServiceSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn remove_service_tags(&self, selectors: &[ServiceSelector], tags: &[Id<TagId>]) -> usize;

    /// Get a list of getters matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/channels`
    fn get_getter_channels(&self, selectors: &[GetterSelector]) -> Vec<Channel<Getter>>;
    fn get_setter_channels(&self, selectors: &[SetterSelector]) -> Vec<Channel<Setter>>;

    /// Label a set of channels with a set of tags.
    ///
    /// A call to `API::put_{getter, setter}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the channels matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of channels
    /// matching any of the selectors.
    ///
    /// Some of the channels may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if channels
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/channels/tag`
    ///
    /// ## Requests
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<GetterSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<SetterSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn add_getter_tags(&self, selectors: &[GetterSelector], tags: &[Id<TagId>]) -> usize;
    fn add_setter_tags(&self, selectors: &[SetterSelector], tags: &[Id<TagId>]) -> usize;

    /// Remove a set of tags from a set of channels.
    ///
    /// A call to `API::delete_{getter, setter}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the channels matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of channels
    /// matching any of the selectors.
    ///
    /// Some of the channels may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if channels
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/channels/tag`
    ///
    /// ## Requests
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<GetterSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<SetterSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn remove_getter_tags(&self, selectors: &[GetterSelector], tags: &[Id<TagId>]) -> usize;
    fn remove_setter_tags(&self, selectors: &[SetterSelector], tags: &[Id<TagId>]) -> usize;

    /// Read the latest value from a set of channels
    ///
    /// # REST API
    ///
    /// `GET /api/v1/channels/value`
    fn fetch_values(&self, &[GetterSelector]) -> ResultMap<Id<Getter>, Option<Value>, Error>;

    /// Send a bunch of values to a set of channels
    ///
    /// # REST API
    ///
    /// `POST /api/v1/channels/value`
    fn send_values(&self, TargetMap<SetterSelector, Value>) -> ResultMap<Id<Setter>, (), Error>;

    /// Watch for changes from channels.
    ///
    /// This method registers a closure to watch over events on a set of channels. Argument `watch`
    /// specifies which channels to watch and which events are of interest.
    ///
    /// - If argument `Exactly<Range>` is `Exactly::Exactly(range)`, the watch is interested in
    /// values coming from these channels, if they fall within `range`. This is the most common
    /// case. In this case, `on_event` receives `WatcherEvent::GetterAdded`,
    /// `WatcherEvent::GetterRemoved` and `WatcherEvent::Value`, whenever a new value is available
    /// in the range. Values that do not have the same type as `range` are dropped silently.
    ///
    /// - If argument `Exactly<Range>` is `Exactly::Never`, the watch is not interested in the
    /// values coming from these channels, only in connection/disconnection events. Argument
    /// `on_event` receives `WatchEvent::GetterAdded` and `WatchEvent::GetterRemoved`.
    ///
    /// - If the `Exactly<Range>` argument is `Exactly::Always`, the watch is interested in
    /// receiving *every single value coming from the channels*. This is very rarely a good idea.
    /// Many devices may reject such requests.
    ///
    /// The watcher is disconnected once the `WatchGuard` returned by this method is dropped.
    ///
    /// # WebSocket API
    ///
    /// `/api/v1/channels/watch`
    fn register_channel_watch(&self, watch: TargetMap<GetterSelector, Exactly<Range>>,
            on_event: Box<ExtSender<WatchEvent>>) -> Self::WatchGuard;

    /// A value that causes a disconnection once it is dropped.
    type WatchGuard;
}
<commit_msg>[fxbox/taxonomy] API is now Send<commit_after>//!
//! The API for communicating with devices.
//!
//! This API is provided as Traits to be implemented:
//!
//! - by the low-level layers of the FoxBox, including the adapters;
//! - by test suites and tools that need to simulate connected devices.
//!
//! In turn, this API is used to implement:
//!
//! - the public-facing REST and WebSocket API;
//! - the rules API (ThinkerBell).
//!
//!

use services::*;
use selector::*;
use values::{ Value, Range, TypeError };
use util::{ Exactly, Id };

use transformable_channels::mpsc::*;

/// An error that arose during interaction with either a device, an adapter or the
/// adapter manager
#[derive(Serialize, Debug, Clone)]
pub enum Error {
    /// Attempting to fetch a value from a Channel<Getter> that doesn't support this operation.
    GetterDoesNotSupportPolling(Id<Getter>),

    /// Attempting to watch a value from a Channel<Getter> that doesn't support this operation.
    GetterDoesNotSupportWatching(Id<Getter>),

    /// Attempting to watch all values from a Channel<Getter> that requires a filter.
    /// For instance, some Channel<Getter> may be updated 60 times per second. Attempting to
    /// watch all values could easily exceed the capacity of the network or exhaust the battery.
    /// In such a case, the adapter should return this error.
    GetterRequiresThresholdForWatching(Id<Getter>),

    /// Attempting to send a value with a wrong type.
    TypeError(TypeError),

    /// Attempting to use an inconsistent range. For instance, one with `min > max`.
    RangeError(Range),

    /// Attempting to send an invalid value. For instance, a time of day larger than 24h.
    InvalidValue(Value),

    /// An error internal to the foxbox or an adapter. Normally, these errors should never
    /// arise from the high-level API.
    InternalError(InternalError),
}

#[derive(Serialize, Debug, Clone)]
pub enum InternalError {
    /// Attempting to fetch or watch a getter that isn't registered.
    NoSuchGetter(Id<Getter>),
    /// Attempting to send values to a setter that isn't registered.
    NoSuchSetter(Id<Setter>),
    /// Attempting to access a service that isn't registered.
    NoSuchService(Id<ServiceId>),
    /// Attempting to access an adapter that isn't registered.
    NoSuchAdapter(Id<AdapterId>),

    /// Attempting to register a getter with an id that is already used.
    DuplicateGetter(Id<Getter>),
    /// Attempting to register a setter with an id that is already used.
    DuplicateSetter(Id<Setter>),
    /// Attempting to register a service with an id that is already used.
    DuplicateService(Id<ServiceId>),
    /// Attempting to register an adapter with an id that is already used.
    DuplicateAdapter(Id<AdapterId>),

    /// Attempting to register a channel with an adapter that doesn't match that of its service.
    ConflictingAdapter(Id<AdapterId>, Id<AdapterId>),

    /// Attempting to register a service in an invalid initial state. Typically, a service that
    /// pretends that it already has channels.
    InvalidInitialService,
}

/// An event during watching.
#[derive(Serialize, Debug, Clone)]
pub enum WatchEvent {
    /// If a range was specified when we registered for watching, `EnterRange` is fired whenever
    /// we enter this range. If `Always` was specified, `EnterRange` is fired whenever a new value
    /// is available. Otherwise, never fired.
    EnterRange {
        /// The channel that sent the value.
        from: Id<Getter>,

        /// The actual value.
        value: Value
    },

    /// If a range was specified when we registered for watching, `ExitRange` is fired whenever
    /// we exit this range. Otherwise, never fired.
    ExitRange {
        /// The channel that sent the value.
        from: Id<Getter>,

        /// The actual value.
        value: Value
    },

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// removed. Payload is the id of the device that was removed.
    GetterRemoved(Id<Getter>),

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// added. Payload is the id of the device that was added.
    GetterAdded(Id<Getter>),

    /// One of the channels encountered an error during initialization.
    /// This channel will not be watched, but other channels will remain
    /// watched.
    InitializationError {
        channel: Id<Getter>,
        error: Error
    },
}

/// A bunch of results coming from different sources.
pub type ResultMap<K, T, E> = Vec<(K, Result<T, E>)>;

/// A bunch of instructions, going to different targets.
pub type TargetMap<K, T> = Vec<(Vec<K>, T)>;

/// A handle to the public API.
pub trait API: Send {
    /// Get the metadata on services matching some conditions.
    ///
    /// A call to `API::get_services(vec![req1, req2, ...])` will return
    /// the metadata on all services matching _either_ `req1` or `req2`
    /// or ...
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services`
    ///
    /// ## Requests
    ///
    /// Any JSON that can be deserialized to a `Vec<ServiceSelector>`. See
    /// the implementation of `ServiceSelector` for details.
    ///
    /// ### Example
    ///
    /// Selector all doors in the entrance (tags `door`, `entrance`)
    /// that support setter channel `OpenClose`
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door"],
    ///   "getters": [
    ///     {
    ///       "kind": {
    ///         "Exactly": {
    ///           "OpenClose": []
    ///         }
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    ///
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing an array of `Service`. See the implementation
    /// of `Service` for details.
    ///
    /// ### Example
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door", "somevendor"],
    ///   "id: "some-service-id",
    ///   "getters": [],
    ///   "setters": [
    ///     "tags": [...],
    ///     "id": "some-channel-id",
    ///     "service": "some-service-id",
    ///     "last_seen": "some-date",
    ///     "mechanism": {
    ///       "Setter":  {
    ///         "kind": {
    ///           "OnOff": []
    ///         },
    ///         "push": [5000],
    ///         "updated": "some-date",
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    fn get_services(&self, Vec<ServiceSelector>) -> Vec<Service>;

    /// Label a set of services with a set of tags.
    ///
    /// A call to `API::put_service_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the services matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of services
    /// matching any of the selectors.
    ///
    /// Some of the services may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/tag`
    ///
    /// ## Getters
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<ServiceSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON string representing a number.
    fn add_service_tags(&self, selectors: Vec<ServiceSelector>, tags: Vec<Id<TagId>>) -> usize;

    /// Remove a set of tags from a set of services.
    ///
    /// A call to `API::delete_service_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the services matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of services
    /// matching any of the selectors.
    ///
    /// Some of the services may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/services/tag`
    ///
    /// ## Getters
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<ServiceSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn remove_service_tags(&self, selectors: Vec<ServiceSelector>, tags: Vec<Id<TagId>>) -> usize;

    /// Get a list of getters matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/channels`
    fn get_getter_channels(&self, selectors: Vec<GetterSelector>) -> Vec<Channel<Getter>>;
    fn get_setter_channels(&self, selectors: Vec<SetterSelector>) -> Vec<Channel<Setter>>;

    /// Label a set of channels with a set of tags.
    ///
    /// A call to `API::put_{getter, setter}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the channels matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of channels
    /// matching any of the selectors.
    ///
    /// Some of the channels may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if channels
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/channels/tag`
    ///
    /// ## Requests
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<GetterSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<SetterSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn add_getter_tags(&self, selectors: Vec<GetterSelector>, tags: Vec<Id<TagId>>) -> usize;
    fn add_setter_tags(&self, selectors: Vec<SetterSelector>, tags: Vec<Id<TagId>>) -> usize;

    /// Remove a set of tags from a set of channels.
    ///
    /// A call to `API::delete_{getter, setter}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the channels matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of channels
    /// matching any of the selectors.
    ///
    /// Some of the channels may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if channels
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/channels/tag`
    ///
    /// ## Requests
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<GetterSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<SetterSelector>,
    ///   tags: Vec<Id<TagId>>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn remove_getter_tags(&self, selectors: Vec<GetterSelector>, tags: Vec<Id<TagId>>) -> usize;
    fn remove_setter_tags(&self, selectors: Vec<SetterSelector>, tags: Vec<Id<TagId>>) -> usize;

    /// Read the latest value from a set of channels
    ///
    /// # REST API
    ///
    /// `GET /api/v1/channels/value`
    fn fetch_values(&self, Vec<GetterSelector>) -> ResultMap<Id<Getter>, Option<Value>, Error>;

    /// Send a bunch of values to a set of channels
    ///
    /// # REST API
    ///
    /// `POST /api/v1/channels/value`
    fn send_values(&self, TargetMap<SetterSelector, Value>) -> ResultMap<Id<Setter>, (), Error>;

    /// Watch for changes from channels.
    ///
    /// This method registers a closure to watch over events on a set of channels. Argument `watch`
    /// specifies which channels to watch and which events are of interest.
    ///
    /// - If argument `Exactly<Range>` is `Exactly::Exactly(range)`, the watch is interested in
    /// values coming from these channels, if they fall within `range`. This is the most common
    /// case. In this case, `on_event` receives `WatcherEvent::GetterAdded`,
    /// `WatcherEvent::GetterRemoved` and `WatcherEvent::Value`, whenever a new value is available
    /// in the range. Values that do not have the same type as `range` are dropped silently.
    ///
    /// - If argument `Exactly<Range>` is `Exactly::Never`, the watch is not interested in the
    /// values coming from these channels, only in connection/disconnection events. Argument
    /// `on_event` receives `WatchEvent::GetterAdded` and `WatchEvent::GetterRemoved`.
    ///
    /// - If the `Exactly<Range>` argument is `Exactly::Always`, the watch is interested in
    /// receiving *every single value coming from the channels*. This is very rarely a good idea.
    /// Many devices may reject such requests.
    ///
    /// The watcher is disconnected once the `WatchGuard` returned by this method is dropped.
    ///
    /// # WebSocket API
    ///
    /// `/api/v1/channels/watch`
    fn register_channel_watch(&self, watch: TargetMap<GetterSelector, Exactly<Range>>,
            on_event: Box<ExtSender<WatchEvent>>) -> Self::WatchGuard;

    /// A value that causes a disconnection once it is dropped.
    type WatchGuard;
}
<|endoftext|>


<commit_before>//!
//! Defines the default Intercom type model.
//!

use model::{ ComInterface, ComStruct, ComImpl };

pub struct BuiltinTypeInfo {
    pub interface: ComInterface,
    pub class: ComStruct,
    pub implementation: ComImpl,
    pub ctor : ::quote::Tokens,
}

pub fn builtin_intercom_types( lib_name: &str ) -> Vec<BuiltinTypeInfo> {
    vec![
        BuiltinTypeInfo {
            interface: allocator_interface( lib_name ),
            class: allocator_class( lib_name ),
            implementation: allocator_impl(),
            ctor: quote!( ::intercom::alloc::Allocator::default() ),
        },
    ]
}

fn allocator_interface( lib_name: &str ) -> ComInterface {
    ComInterface::parse(
            lib_name,
            "\"18EE22B3-B0C6-44A5-A94A-7A417676FB66\"",
            allocator_impl_code() ).unwrap()
}

fn allocator_impl() -> ComImpl {
    ComImpl::parse( allocator_impl_code() ).unwrap()
}

fn allocator_class( lib_name: &str ) -> ComStruct {
    ComStruct::parse(
            lib_name,
            "AUTO_GUID, Allocator",
            "pub struct Allocator;" ).unwrap()
}

fn allocator_impl_code() -> &'static str {
    r#"
    impl Allocator {
        unsafe fn alloc_bstr( &self, text : &BStr, len : u32 ) -> BString {
            os::alloc_bstr( text, len )
        }

        unsafe fn free_bstr( &self, bstr : &BStr ) {
            os::free_bstr( bstr )
        }

        unsafe fn alloc( &self, len : usize ) -> *mut raw::c_void {
            os::alloc( len )
        }

        unsafe fn free( &self, ptr : *mut raw::c_void ) {
            os::free( ptr )
        }
    }
    "#
}

<commit_msg>Use u16 string instead of BSTR as AllocBstr parameter<commit_after>//!
//! Defines the default Intercom type model.
//!

use model::{ ComInterface, ComStruct, ComImpl };

pub struct BuiltinTypeInfo {
    pub interface: ComInterface,
    pub class: ComStruct,
    pub implementation: ComImpl,
    pub ctor : ::quote::Tokens,
}

pub fn builtin_intercom_types( lib_name: &str ) -> Vec<BuiltinTypeInfo> {
    vec![
        BuiltinTypeInfo {
            interface: allocator_interface( lib_name ),
            class: allocator_class( lib_name ),
            implementation: allocator_impl(),
            ctor: quote!( ::intercom::alloc::Allocator::default() ),
        },
    ]
}

fn allocator_interface( lib_name: &str ) -> ComInterface {
    ComInterface::parse(
            lib_name,
            "\"18EE22B3-B0C6-44A5-A94A-7A417676FB66\"",
            allocator_impl_code() ).unwrap()
}

fn allocator_impl() -> ComImpl {
    ComImpl::parse( allocator_impl_code() ).unwrap()
}

fn allocator_class( lib_name: &str ) -> ComStruct {
    ComStruct::parse(
            lib_name,
            "AUTO_GUID, Allocator",
            "pub struct Allocator;" ).unwrap()
}

fn allocator_impl_code() -> &'static str {
    r#"
    impl Allocator {
        unsafe fn alloc_bstr( &self, text : *const u16, len : u32 ) -> BString {
            os::alloc_bstr( text, len )
        }

        unsafe fn free_bstr( &self, bstr : &BStr ) {
            os::free_bstr( bstr )
        }

        unsafe fn alloc( &self, len : usize ) -> *mut raw::c_void {
            os::alloc( len )
        }

        unsafe fn free( &self, ptr : *mut raw::c_void ) {
            os::free( ptr )
        }
    }
    "#
}

<|endoftext|>


<commit_before>use clap::ArgMatches;
use eth2::types::*;
use eth2::{BeaconNodeHttpClient, Timeouts};
use log::{error, info};
use sensitive_url::SensitiveUrl;
use std::collections::{HashMap, HashSet};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use std::time::Duration;

type CommitteePosition = usize;
type Committee = u64;
type InclusionDistance = u64;
type ValidatorIndex = u64;

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct UniqueAttestation {
    slot: Slot,
    committee_index: Committee,
    committee_position: CommitteePosition,
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct ProposerInfo {
    proposer_index: ValidatorIndex,
    graffiti: String,
}

#[derive(Debug)]
struct CommitteeInfo {
    number_of_committees: usize,
    validators_per_committee: usize,
}

async fn get_validator_set_len<T: EthSpec>(
    node: &BeaconNodeHttpClient,
    slot: Slot,
) -> Result<usize, String> {
    let active_validator_set = node
        .get_beacon_states_validators(StateId::Slot(slot), None, None)
        .await
        .map_err(|e| format!("{:?}", e))?
        .ok_or_else(|| "No validators found".to_string())?
        .data;
    Ok(active_validator_set
        .iter()
        .filter(|x| x.status.superstatus() == ValidatorStatus::Active)
        .count())
}

async fn get_block_attestations_set<'a, T: EthSpec>(
    node: &BeaconNodeHttpClient,
    slot: Slot,
) -> Result<Option<(HashMap<UniqueAttestation, InclusionDistance>, ProposerInfo)>, String> {
    let mut unique_attestations_set: HashMap<UniqueAttestation, InclusionDistance> = HashMap::new();

    let option_block: Option<ForkVersionedResponse<SignedBeaconBlock<T>>> = node
        .get_beacon_blocks(BlockId::Slot(slot))
        .await
        .map_err(|e| format!("{:?}", e))?;

    let block = match option_block {
        Some(block) => block.data,
        // No block was proposed for this slot.
        None => return Ok(None),
    };

    let proposer = ProposerInfo {
        proposer_index: block.message().proposer_index(),
        graffiti: block
            .message()
            .body()
            .graffiti()
            .as_utf8_lossy()
            // Remove commas and apostropes from graffiti to ensure correct CSV format.
            .replace(",", "")
            .replace("\"", "")
            .replace("'", ""),
    };

    let attestations = block.message().body().attestations();

    for attestation in attestations.iter() {
        for (position, voted) in attestation.aggregation_bits.iter().enumerate() {
            if voted {
                let unique_attestation = UniqueAttestation {
                    slot: attestation.data.slot,
                    committee_index: attestation.data.index,
                    committee_position: position,
                };
                let inclusion_distance: u64 = slot
                    .as_u64()
                    .checked_sub(attestation.data.slot.as_u64())
                    .ok_or_else(|| "Attestation slot is larger than the block slot".to_string())?;
                unique_attestations_set.insert(unique_attestation, inclusion_distance);
            }
        }
    }

    Ok(Some((unique_attestations_set, proposer)))
}

async fn get_epoch_committee_data<T: EthSpec>(
    node: &BeaconNodeHttpClient,
    epoch: Epoch,
) -> Result<(Vec<CommitteeData>, CommitteeInfo), String> {
    let committee_data = node
        .get_beacon_states_committees(
            StateId::Slot(Epoch::start_slot(epoch, T::slots_per_epoch())),
            None,
            None,
            Some(epoch),
        )
        .await
        .map_err(|e| format!("{:?}", e))?
        .ok_or_else(|| "No committees found".to_string())?
        .data;

    let committee_info = CommitteeInfo {
        number_of_committees: committee_data.len(),
        // FIXME: validators.len() isn't consistent between different committees in the
        // same epoch.
        validators_per_committee: committee_data[0].validators.len(),
    };

    Ok((committee_data, committee_info))
}

pub async fn run<T: EthSpec>(matches: &ArgMatches<'_>) -> Result<(), String> {
    const SECONDS_PER_SLOT: Duration = Duration::from_secs(12);
    let output_path: PathBuf = clap_utils::parse_required(matches, "output")?;
    let start_epoch: Epoch = clap_utils::parse_required(matches, "start-epoch")?;
    let offline_window: u64 = matches
        .value_of("offline-window")
        .unwrap_or("3")
        .parse()
        .map_err(|e| format!("{:?}", e))?;
    let calculate_offline_vals = offline_window != 0;

    if start_epoch == 0 {
        return Err("start_epoch cannot be 0.".to_string());
    }
    let initialization_epoch: Epoch = start_epoch - 1;
    let end_epoch: Epoch = clap_utils::parse_required(matches, "end-epoch")?;

    if end_epoch < start_epoch {
        return Err("start_epoch must be smaller than end_epoch".to_string());
    }

    let mut available_attestations_set: HashSet<UniqueAttestation> = HashSet::new();
    let mut included_attestations_set: HashMap<UniqueAttestation, InclusionDistance> =
        HashMap::new();

    // Build validator set HashMap<Index, EpochOfMostRecentAttestation)>
    // This allows a 'best effort' attempt to normalize block efficiencies.
    let mut online_validator_set: HashMap<ValidatorIndex, Epoch> = HashMap::new();

    let mut proposer_map: HashMap<Slot, ProposerInfo> = HashMap::new();

    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(output_path)
        .map_err(|e| format!("Unable to open file: {}", e))?;

    write!(file, "slot,proposer,available,included,offline,graffiti").unwrap();

    // Initialize API.
    let endpoint = matches
        .value_of("endpoint")
        .unwrap_or("http://localhost:5052/");
    let node = BeaconNodeHttpClient::new(
        SensitiveUrl::parse(endpoint).map_err(|_| "Unable to parse endpoint.".to_string())?,
        Timeouts::set_all(SECONDS_PER_SLOT),
    );

    // Check we can connect to the API.
    let version =
        match node.get_node_version().await {
            Ok(version_response) => version_response.data.version,
            Err(_) => return Err(
                "Error: A working HTTP API server is required. Ensure one is synced and available."
                    .to_string(),
            ),
        };

    // Check we are synced past the required epoch range.
    let head_slot_synced =
        match node.get_node_syncing().await {
            Ok(synced_response) => synced_response.data.head_slot,
            Err(_) => return Err(
                "Error: A working HTTP API server is required. Ensure one is synced and available."
                    .to_string(),
            ),
        };

    if head_slot_synced < end_epoch.end_slot(T::slots_per_epoch()) {
        return Err(
            "Error: The beacon node is not sufficiently synced. Make sure your node is synced \
            past the desired `end-epoch` and that you aren't requesting future epochs."
                .to_string(),
        );
    }

    // Whether the beacon node is responding to requests. This is helpful for logging.
    let mut connected: bool = true;
    info!("Connected to endpoint at: {:?} - {:?}", endpoint, version);

    // Loop over epochs.
    for epoch in (initialization_epoch.as_u64()..=end_epoch.as_u64()).map(Epoch::new) {
        if epoch != initialization_epoch {
            info!("Analysing epoch {}...", epoch);
        } else {
            info!("Initializing...");
        }
        let mut epoch_data: Vec<(Slot, Option<ProposerInfo>, usize, usize)> = Vec::new();

        // Current epochs available attestations set
        let (committee_data, committee_info) = loop {
            if let Ok(committee_result) = get_epoch_committee_data::<T>(&node, epoch).await {
                if !connected {
                    info!("Connected to endpoint at: {:?} - {:?}", endpoint, version);
                    connected = true;
                }
                break committee_result;
            }

            if connected {
                connected = false;
                error!("A request to the Beacon Node API failed. Check connectivity.");
            }
        };

        // Ensure available attestations don't exceed the possible amount of attestations
        // as determined by the committee size/number.
        // This is unlikely to happen, but not impossible.
        let max_possible_attesations =
            committee_info.validators_per_committee * committee_info.number_of_committees;

        // Get number of active validators.
        let active_validators =
            get_validator_set_len::<T>(&node, epoch.start_slot(T::slots_per_epoch())).await?;

        for slot in epoch.slot_iter(T::slots_per_epoch()) {
            // Get all included attestations.
            let block_result = loop {
                if let Ok(block_result) = get_block_attestations_set::<T>(&node, slot).await {
                    if !connected {
                        info!("Connected to endpoint at: {:?} - {:?}", endpoint, version);
                        connected = true;
                    }
                    break block_result;
                };
                if connected {
                    connected = false;
                    error!("A request to the Beacon Node API failed. Check connectivity.");
                }
            };
            let (mut attestations_in_block, proposer) = match block_result {
                Some(output) => (output.0, Some(output.1)),
                None => (HashMap::new(), None),
            };

            // Insert block proposer into proposer_map.
            if let Some(proposer_info) = proposer {
                proposer_map.insert(slot, proposer_info.clone());
            }

            // Remove duplicate attestations.
            attestations_in_block.retain(|x, _| included_attestations_set.get(x).is_none());

            // Add them to the set.
            included_attestations_set.extend(attestations_in_block.clone());

            // Don't write data from the initialization epoch.
            if epoch != initialization_epoch {
                let included = attestations_in_block.len();

                let available = if max_possible_attesations < available_attestations_set.len() {
                    max_possible_attesations
                } else {
                    available_attestations_set.len()
                };

                // Get proposer information.
                let proposer = proposer_map.get(&slot).cloned();

                // Store slot data.
                epoch_data.push((slot, proposer, available, included));
            }

            // Included attestations are no longer available.
            for new_attestation in &attestations_in_block {
                available_attestations_set.remove(new_attestation.0);
            }

            // Get all available attestations.
            for committee in &committee_data {
                if committee.slot == slot {
                    for position in 0..committee.validators.len() {
                        let unique_attestation = UniqueAttestation {
                            slot: committee.slot,
                            committee_index: committee.index,
                            committee_position: position,
                        };
                        available_attestations_set.insert(unique_attestation.clone());
                    }
                }
            }

            // Remove expired available attestations.
            available_attestations_set.retain(|x| x.slot >= (slot.as_u64().saturating_sub(32)));
        }

        let mut offline = "None".to_string();
        if calculate_offline_vals {
            // Get all online validators for the epoch.
            for committee in &committee_data {
                for position in 0..committee.validators.len() {
                    let unique_attestation = UniqueAttestation {
                        slot: committee.slot,
                        committee_index: committee.index,
                        committee_position: position,
                    };
                    let index = committee.validators.get(position).ok_or_else(|| {
                        "Error parsing validator indices from committee data".to_string()
                    })?;

                    if included_attestations_set.get(&unique_attestation).is_some() {
                        online_validator_set.insert(*index, epoch);
                    }
                }
            }

            // Calculate offline validators.
            offline = if epoch >= start_epoch + offline_window {
                active_validators
                    .checked_sub(online_validator_set.len())
                    .ok_or_else(|| "Online set is greater than active set".to_string())?
                    .to_string()
            } else {
                "None".to_string()
            };
        }

        // Write epoch data.
        for (slot, proposer, available, included) in epoch_data {
            let proposer_index = proposer
                .clone()
                .map_or("None".to_string(), |x| x.proposer_index.to_string());
            let graffiti = proposer.map_or("None".to_string(), |x| x.graffiti);
            write!(
                file,
                "\n{},{},{},{},{},{}",
                slot, proposer_index, available, included, offline, graffiti
            )
            .unwrap();
        }

        // Free some memory by removing included attestations older than 1 epoch.
        included_attestations_set.retain(|x, _| {
            x.slot >= Epoch::new(epoch.as_u64().saturating_sub(1)).start_slot(T::slots_per_epoch())
        });

        if calculate_offline_vals {
            // Remove old validators from the validator set which are outside the offline window.
            online_validator_set.retain(|_, x| {
                *x >= Epoch::new(
                    epoch
                        .as_u64()
                        .saturating_sub(offline_window.saturating_sub(1)),
                )
            });
        }
    }
    Ok(())
}
<commit_msg>Fix off-by-one in block packing lcli (#2878)<commit_after>use clap::ArgMatches;
use eth2::types::*;
use eth2::{BeaconNodeHttpClient, Timeouts};
use log::{error, info};
use sensitive_url::SensitiveUrl;
use std::collections::{HashMap, HashSet};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use std::time::Duration;

type CommitteePosition = usize;
type Committee = u64;
type InclusionDistance = u64;
type ValidatorIndex = u64;

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct UniqueAttestation {
    slot: Slot,
    committee_index: Committee,
    committee_position: CommitteePosition,
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct ProposerInfo {
    proposer_index: ValidatorIndex,
    graffiti: String,
}

#[derive(Debug)]
struct CommitteeInfo {
    number_of_committees: usize,
    validators_per_committee: usize,
}

async fn get_validator_set_len<T: EthSpec>(
    node: &BeaconNodeHttpClient,
    slot: Slot,
) -> Result<usize, String> {
    let active_validator_set = node
        .get_beacon_states_validators(StateId::Slot(slot), None, None)
        .await
        .map_err(|e| format!("{:?}", e))?
        .ok_or_else(|| "No validators found".to_string())?
        .data;
    Ok(active_validator_set
        .iter()
        .filter(|x| x.status.superstatus() == ValidatorStatus::Active)
        .count())
}

async fn get_block_attestations_set<'a, T: EthSpec>(
    node: &BeaconNodeHttpClient,
    slot: Slot,
) -> Result<Option<(HashMap<UniqueAttestation, InclusionDistance>, ProposerInfo)>, String> {
    let mut unique_attestations_set: HashMap<UniqueAttestation, InclusionDistance> = HashMap::new();

    let option_block: Option<ForkVersionedResponse<SignedBeaconBlock<T>>> = node
        .get_beacon_blocks(BlockId::Slot(slot))
        .await
        .map_err(|e| format!("{:?}", e))?;

    let block = match option_block {
        Some(block) => block.data,
        // No block was proposed for this slot.
        None => return Ok(None),
    };

    let proposer = ProposerInfo {
        proposer_index: block.message().proposer_index(),
        graffiti: block
            .message()
            .body()
            .graffiti()
            .as_utf8_lossy()
            // Remove commas and apostropes from graffiti to ensure correct CSV format.
            .replace(",", "")
            .replace("\"", "")
            .replace("'", ""),
    };

    let attestations = block.message().body().attestations();

    for attestation in attestations.iter() {
        for (position, voted) in attestation.aggregation_bits.iter().enumerate() {
            if voted {
                let unique_attestation = UniqueAttestation {
                    slot: attestation.data.slot,
                    committee_index: attestation.data.index,
                    committee_position: position,
                };
                let inclusion_distance: u64 = slot
                    .as_u64()
                    .checked_sub(attestation.data.slot.as_u64())
                    .ok_or_else(|| "Attestation slot is larger than the block slot".to_string())?;
                unique_attestations_set.insert(unique_attestation, inclusion_distance);
            }
        }
    }

    Ok(Some((unique_attestations_set, proposer)))
}

async fn get_epoch_committee_data<T: EthSpec>(
    node: &BeaconNodeHttpClient,
    epoch: Epoch,
) -> Result<(Vec<CommitteeData>, CommitteeInfo), String> {
    let committee_data = node
        .get_beacon_states_committees(
            StateId::Slot(Epoch::start_slot(epoch, T::slots_per_epoch())),
            None,
            None,
            Some(epoch),
        )
        .await
        .map_err(|e| format!("{:?}", e))?
        .ok_or_else(|| "No committees found".to_string())?
        .data;

    let committee_info = CommitteeInfo {
        number_of_committees: committee_data.len(),
        // FIXME: validators.len() isn't consistent between different committees in the
        // same epoch.
        validators_per_committee: committee_data[0].validators.len(),
    };

    Ok((committee_data, committee_info))
}

pub async fn run<T: EthSpec>(matches: &ArgMatches<'_>) -> Result<(), String> {
    const SECONDS_PER_SLOT: Duration = Duration::from_secs(12);
    let output_path: PathBuf = clap_utils::parse_required(matches, "output")?;
    let start_epoch: Epoch = clap_utils::parse_required(matches, "start-epoch")?;
    let offline_window: u64 = matches
        .value_of("offline-window")
        .unwrap_or("3")
        .parse()
        .map_err(|e| format!("{:?}", e))?;
    let calculate_offline_vals = offline_window != 0;

    if start_epoch == 0 {
        return Err("start_epoch cannot be 0.".to_string());
    }
    let initialization_epoch: Epoch = start_epoch - 1;
    let end_epoch: Epoch = clap_utils::parse_required(matches, "end-epoch")?;

    if end_epoch < start_epoch {
        return Err("start_epoch must be smaller than end_epoch".to_string());
    }

    let mut available_attestations_set: HashSet<UniqueAttestation> = HashSet::new();
    let mut included_attestations_set: HashMap<UniqueAttestation, InclusionDistance> =
        HashMap::new();

    // Build validator set HashMap<Index, EpochOfMostRecentAttestation)>
    // This allows a 'best effort' attempt to normalize block efficiencies.
    let mut online_validator_set: HashMap<ValidatorIndex, Epoch> = HashMap::new();

    let mut proposer_map: HashMap<Slot, ProposerInfo> = HashMap::new();

    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(output_path)
        .map_err(|e| format!("Unable to open file: {}", e))?;

    write!(file, "slot,proposer,available,included,offline,graffiti").unwrap();

    // Initialize API.
    let endpoint = matches
        .value_of("endpoint")
        .unwrap_or("http://localhost:5052/");
    let node = BeaconNodeHttpClient::new(
        SensitiveUrl::parse(endpoint).map_err(|_| "Unable to parse endpoint.".to_string())?,
        Timeouts::set_all(SECONDS_PER_SLOT),
    );

    // Check we can connect to the API.
    let version =
        match node.get_node_version().await {
            Ok(version_response) => version_response.data.version,
            Err(_) => return Err(
                "Error: A working HTTP API server is required. Ensure one is synced and available."
                    .to_string(),
            ),
        };

    // Check we are synced past the required epoch range.
    let head_slot_synced =
        match node.get_node_syncing().await {
            Ok(synced_response) => synced_response.data.head_slot,
            Err(_) => return Err(
                "Error: A working HTTP API server is required. Ensure one is synced and available."
                    .to_string(),
            ),
        };

    if head_slot_synced < end_epoch.end_slot(T::slots_per_epoch()) {
        return Err(
            "Error: The beacon node is not sufficiently synced. Make sure your node is synced \
            past the desired `end-epoch` and that you aren't requesting future epochs."
                .to_string(),
        );
    }

    // Whether the beacon node is responding to requests. This is helpful for logging.
    let mut connected: bool = true;
    info!("Connected to endpoint at: {:?} - {:?}", endpoint, version);

    // Loop over epochs.
    for epoch in (initialization_epoch.as_u64()..=end_epoch.as_u64()).map(Epoch::new) {
        if epoch != initialization_epoch {
            info!("Analysing epoch {}...", epoch);
        } else {
            info!("Initializing...");
        }
        let mut epoch_data: Vec<(Slot, Option<ProposerInfo>, usize, usize)> = Vec::new();

        // Current epochs available attestations set
        let (committee_data, committee_info) = loop {
            if let Ok(committee_result) = get_epoch_committee_data::<T>(&node, epoch).await {
                if !connected {
                    info!("Connected to endpoint at: {:?} - {:?}", endpoint, version);
                    connected = true;
                }
                break committee_result;
            }

            if connected {
                connected = false;
                error!("A request to the Beacon Node API failed. Check connectivity.");
            }
        };

        // Ensure available attestations don't exceed the possible amount of attestations
        // as determined by the committee size/number.
        // This is unlikely to happen, but not impossible.
        let max_possible_attesations =
            committee_info.validators_per_committee * committee_info.number_of_committees;

        // Get number of active validators.
        let active_validators =
            get_validator_set_len::<T>(&node, epoch.start_slot(T::slots_per_epoch())).await?;

        for slot in epoch.slot_iter(T::slots_per_epoch()) {
            // Get all included attestations.
            let block_result = loop {
                if let Ok(block_result) = get_block_attestations_set::<T>(&node, slot).await {
                    if !connected {
                        info!("Connected to endpoint at: {:?} - {:?}", endpoint, version);
                        connected = true;
                    }
                    break block_result;
                };
                if connected {
                    connected = false;
                    error!("A request to the Beacon Node API failed. Check connectivity.");
                }
            };
            let (mut attestations_in_block, proposer) = match block_result {
                Some(output) => (output.0, Some(output.1)),
                None => (HashMap::new(), None),
            };

            // Insert block proposer into proposer_map.
            if let Some(proposer_info) = proposer {
                proposer_map.insert(slot, proposer_info.clone());
            }

            // Remove duplicate attestations.
            attestations_in_block.retain(|x, _| included_attestations_set.get(x).is_none());

            // Add them to the set.
            included_attestations_set.extend(attestations_in_block.clone());

            // Remove expired available attestations.
            available_attestations_set.retain(|x| x.slot >= (slot.as_u64().saturating_sub(32)));

            // Don't write data from the initialization epoch.
            if epoch != initialization_epoch {
                let included = attestations_in_block.len();

                let available = if max_possible_attesations < available_attestations_set.len() {
                    max_possible_attesations
                } else {
                    available_attestations_set.len()
                };

                // Get proposer information.
                let proposer = proposer_map.get(&slot).cloned();

                // Store slot data.
                epoch_data.push((slot, proposer, available, included));
            }

            // Included attestations are no longer available.
            for new_attestation in &attestations_in_block {
                available_attestations_set.remove(new_attestation.0);
            }

            // Get all available attestations.
            for committee in &committee_data {
                if committee.slot == slot {
                    for position in 0..committee.validators.len() {
                        let unique_attestation = UniqueAttestation {
                            slot: committee.slot,
                            committee_index: committee.index,
                            committee_position: position,
                        };
                        available_attestations_set.insert(unique_attestation.clone());
                    }
                }
            }
        }

        let mut offline = "None".to_string();
        if calculate_offline_vals {
            // Get all online validators for the epoch.
            for committee in &committee_data {
                for position in 0..committee.validators.len() {
                    let unique_attestation = UniqueAttestation {
                        slot: committee.slot,
                        committee_index: committee.index,
                        committee_position: position,
                    };
                    let index = committee.validators.get(position).ok_or_else(|| {
                        "Error parsing validator indices from committee data".to_string()
                    })?;

                    if included_attestations_set.get(&unique_attestation).is_some() {
                        online_validator_set.insert(*index, epoch);
                    }
                }
            }

            // Calculate offline validators.
            offline = if epoch >= start_epoch + offline_window {
                active_validators
                    .checked_sub(online_validator_set.len())
                    .ok_or_else(|| "Online set is greater than active set".to_string())?
                    .to_string()
            } else {
                "None".to_string()
            };
        }

        // Write epoch data.
        for (slot, proposer, available, included) in epoch_data {
            let proposer_index = proposer
                .clone()
                .map_or("None".to_string(), |x| x.proposer_index.to_string());
            let graffiti = proposer.map_or("None".to_string(), |x| x.graffiti);
            write!(
                file,
                "\n{},{},{},{},{},{}",
                slot, proposer_index, available, included, offline, graffiti
            )
            .unwrap();
        }

        // Free some memory by removing included attestations older than 1 epoch.
        included_attestations_set.retain(|x, _| {
            x.slot >= Epoch::new(epoch.as_u64().saturating_sub(1)).start_slot(T::slots_per_epoch())
        });

        if calculate_offline_vals {
            // Remove old validators from the validator set which are outside the offline window.
            online_validator_set.retain(|_, x| {
                *x >= Epoch::new(
                    epoch
                        .as_u64()
                        .saturating_sub(offline_window.saturating_sub(1)),
                )
            });
        }
    }
    Ok(())
}
<|endoftext|>


<commit_before>/// Look for a directive in a comment string.
/// The directive is of the form "foo:" and should follow the leading `;` in the comment:
///
/// ; dominates: ebb3 ebb4
///
/// Return the comment text following the directive.
pub fn match_directive<'a>(comment: &'a str, directive: &str) -> Option<&'a str> {
    assert!(
        directive.ends_with(':'),
        "Directive must include trailing colon"
    );
    let text = comment.trim_left_matches(';').trim_start();
    if text.starts_with(directive) {
        Some(text[directive.len()..].trim())
    } else {
        None
    }
}

#[test]
fn test_match_directive() {
    assert_eq!(match_directive("; foo: bar  ", "foo:"), Some("bar"));
    assert_eq!(match_directive(" foo:bar", "foo:"), Some("bar"));
    assert_eq!(match_directive("foo:bar", "foo:"), Some("bar"));
    assert_eq!(match_directive(";x foo: bar", "foo:"), None);
    assert_eq!(match_directive(";;; foo: bar", "foo:"), Some("bar"));
}
<commit_msg>Replace deprecated trim_left_matches by trim_start_matches;<commit_after>/// Look for a directive in a comment string.
/// The directive is of the form "foo:" and should follow the leading `;` in the comment:
///
/// ; dominates: ebb3 ebb4
///
/// Return the comment text following the directive.
pub fn match_directive<'a>(comment: &'a str, directive: &str) -> Option<&'a str> {
    assert!(
        directive.ends_with(':'),
        "Directive must include trailing colon"
    );
    let text = comment.trim_start_matches(';').trim_start();
    if text.starts_with(directive) {
        Some(text[directive.len()..].trim())
    } else {
        None
    }
}

#[test]
fn test_match_directive() {
    assert_eq!(match_directive("; foo: bar  ", "foo:"), Some("bar"));
    assert_eq!(match_directive(" foo:bar", "foo:"), Some("bar"));
    assert_eq!(match_directive("foo:bar", "foo:"), Some("bar"));
    assert_eq!(match_directive(";x foo: bar", "foo:"), None);
    assert_eq!(match_directive(";;; foo: bar", "foo:"), Some("bar"));
}
<|endoftext|>


<commit_before>// Copyright 2016-2018 Matthew D. Michelotti
//
// 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.

mod collider;
mod dur_hitbox;
mod events;
mod grid;

pub use self::collider::*;

use std::f64;

use self::dur_hitbox::{DurHbVel, DurHitbox};
use geom::shape::PlacedBounds;
use geom::*;

const HIGH_TIME: f64 = 1e50;

/// Type used as a handle for referencing hitboxes in a `Collider` instance.
pub type HbId = u64;

/// Velocity information describing how a hitbox shape is changing over time.
#[derive(PartialEq, Clone, Debug)]
pub struct HbVel {
    /// The movement velocity of the hitbox.
    pub value: Vec2,

    /// A velocity describing how the hitbox dims are changing over time.
    ///
    /// Since the width and height of the shape is greater than `padding` at all
    /// times, if a resize velocity is set that decreases the dimensions of the
    /// shape over time, then the user is responsible for ensuring that the
    /// shape will not decrease below this threshold. Collider may panic if this
    /// is violated.
    pub resize: Vec2,

    /// An upper-bound on the time at which the hitbox will be updated by the
    /// user.
    ///
    /// This is an advanced feature for efficiency and does not impact the
    /// results. Infinity is used as the default, but using a lower value may
    /// improve performance
    ///
    /// Collider will panic if the end time is exceeded without update, at least
    /// in unoptimized builds.  It is ultimately the user's responsibility to
    /// ensure that end times are not exceeded.
    pub end_time: f64,
}

impl HbVel {
    /// Creates an `HbVel` with the given `value`.
    #[inline]
    pub fn moving(value: Vec2) -> HbVel {
        HbVel {
            value,
            resize: Vec2::zero(),
            end_time: f64::INFINITY,
        }
    }

    /// Creates an `HbVel` with the given `value` and `end_time`.
    #[inline]
    pub fn moving_until(value: Vec2, end_time: f64) -> HbVel {
        HbVel {
            value,
            resize: Vec2::zero(),
            end_time,
        }
    }

    /// Creates a stationary `HbVel`.
    #[inline]
    pub fn still() -> HbVel {
        HbVel {
            value: Vec2::zero(),
            resize: Vec2::zero(),
            end_time: f64::INFINITY,
        }
    }

    /// Creates a stationary `HbVel` with the given `end_time`.
    #[inline]
    pub fn still_until(end_time: f64) -> HbVel {
        HbVel {
            value: Vec2::zero(),
            resize: Vec2::zero(),
            end_time,
        }
    }
}

impl From<Vec2> for HbVel {
    fn from(value: Vec2) -> HbVel {
        HbVel::moving(value)
    }
}

impl PlacedBounds for HbVel {
    fn bounds_center(&self) -> &Vec2 {
        &self.value
    }
    fn bounds_dims(&self) -> &Vec2 {
        &self.resize
    }
}

/// Represents a moving shape for continuous collision testing.
#[derive(PartialEq, Clone, Debug)]
pub struct Hitbox {
    /// The placed shape at the given point in time.
    ///
    /// The width and height of the shape must be greater than `padding` at all
    /// times.
    pub value: PlacedShape,

    /// Velocity information describing how the hitbox shape is changing over
    /// time.
    pub vel: HbVel,
}

//TODO invoke hitbox.validate() in more places so that inconsistencies are still found in optimized builds, just found later

impl Hitbox {
    /// Constructs a new hitbox with the given `value` and `vel`.
    #[inline]
    pub fn new(value: PlacedShape, vel: HbVel) -> Hitbox {
        Hitbox { value, vel }
    }

    fn advanced_shape(&self, time: f64) -> PlacedShape {
        assert!(time < HIGH_TIME, "requires time < {}", HIGH_TIME);
        self.value.advance(self.vel.value, self.vel.resize, time)
    }

    fn validate(&self, min_size: f64, present_time: f64) {
        assert!(
            !self.vel.end_time.is_nan() && self.vel.end_time >= present_time,
            "end time must exceed present time"
        );
        if self.value.kind() == ShapeKind::Circle {
            assert_eq!(
                self.vel.resize.x, self.vel.resize.y,
                "circle resize velocity must maintain aspect ratio"
            );
        }
        assert!(
            self.value.dims().x >= min_size && self.value.dims().y >= min_size,
            "shape width/height must be at least {}",
            min_size
        );
    }

    fn time_until_too_small(&self, min_size: f64) -> f64 {
        let min_size = min_size * 0.9;
        assert!(self.value.dims().x > min_size && self.value.dims().y > min_size);
        let mut time = f64::INFINITY;
        if self.vel.resize.x < 0.0 {
            time = time.min((min_size - self.value.dims().x) / self.vel.value.x);
        }
        if self.vel.resize.y < 0.0 {
            time = time.min((min_size - self.value.dims().y) / self.vel.value.y);
        }
        time
    }

    fn to_dur_hitbox(&self, time: f64) -> DurHitbox {
        assert!(time <= self.vel.end_time);
        DurHitbox {
            value: self.value,
            vel: DurHbVel {
                value: self.vel.value,
                resize: self.vel.resize,
                duration: self.vel.end_time - time,
            },
        }
    }
}

/// A group id that may be used as a first measure to efficiently filter out
/// hitboxes that don't interact.
///
/// The total number of groups used should in general be very small. Often 1 is
/// enough, and 10 is excessive. As an example, in a
/// [danmaku](https://en.wikipedia.org/wiki/Shoot_%27em_up#Bullet_hell_and_niche_appeal)
/// game (which has many bullets on screen that do not interact with each
/// other), we may use one group for bullets and one group for everything else,
/// to avoid the quadratic cost of comparing all nearby bullets with each other.
pub type HbGroup = u32;

static DEFAULT_GROUPS: [HbGroup; 1] = [0];

/// A trait that holds metadata for describing a hitbox.
///
/// A user of `Collider` will need to implement an HbProfile that best suites
/// their needs in a game. The most basic HbProfile will just contain an integer
/// ID for the hitbox, but a user may define additional metadata for identfying
/// the hitbox and describing interactivity. An HbProfile must implement the
/// `Copy` trait and should not take up much memory.
pub trait HbProfile: Copy {
    /// A unique identifier for the hitbox.
    ///
    /// Trying to have multiple hitboxes with the same `id` to a `Collider`
    /// instance simultaneously will result in a panic.
    fn id(&self) -> HbId;

    /// Returns the group id associated with the hitbox. Default is `Some(0)`.
    ///
    /// If `None` is returned, then no collisions will be reported for this
    /// hitbox at all.
    fn group(&self) -> Option<HbGroup> {
        Some(0)
    }

    /// Returns a list of groups that this hitbox can interact with. Default is
    /// `[0]`.
    ///
    /// Using large lists of groups may be inefficient.
    fn interact_groups(&self) -> &'static [HbGroup] {
        &DEFAULT_GROUPS
    }

    /// Returns true if the pair of hitboxes should be checked for collisions.
    ///
    /// This method should be commutative. This method should be consistent with
    /// `group` and `interact_groups`, although possibly more restrictive.
    fn can_interact(&self, other: &Self) -> bool;
}
<commit_msg>Fix Hitbox::time_until_too_small(): was dividing by wrong velocities<commit_after>// Copyright 2016-2018 Matthew D. Michelotti
//
// 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.

mod collider;
mod dur_hitbox;
mod events;
mod grid;

pub use self::collider::*;

use std::f64;

use self::dur_hitbox::{DurHbVel, DurHitbox};
use geom::shape::PlacedBounds;
use geom::*;

const HIGH_TIME: f64 = 1e50;

/// Type used as a handle for referencing hitboxes in a `Collider` instance.
pub type HbId = u64;

/// Velocity information describing how a hitbox shape is changing over time.
#[derive(PartialEq, Clone, Debug)]
pub struct HbVel {
    /// The movement velocity of the hitbox.
    pub value: Vec2,

    /// A velocity describing how the hitbox dims are changing over time.
    ///
    /// Since the width and height of the shape is greater than `padding` at all
    /// times, if a resize velocity is set that decreases the dimensions of the
    /// shape over time, then the user is responsible for ensuring that the
    /// shape will not decrease below this threshold. Collider may panic if this
    /// is violated.
    pub resize: Vec2,

    /// An upper-bound on the time at which the hitbox will be updated by the
    /// user.
    ///
    /// This is an advanced feature for efficiency and does not impact the
    /// results. Infinity is used as the default, but using a lower value may
    /// improve performance
    ///
    /// Collider will panic if the end time is exceeded without update, at least
    /// in unoptimized builds.  It is ultimately the user's responsibility to
    /// ensure that end times are not exceeded.
    pub end_time: f64,
}

impl HbVel {
    /// Creates an `HbVel` with the given `value`.
    #[inline]
    pub fn moving(value: Vec2) -> HbVel {
        HbVel {
            value,
            resize: Vec2::zero(),
            end_time: f64::INFINITY,
        }
    }

    /// Creates an `HbVel` with the given `value` and `end_time`.
    #[inline]
    pub fn moving_until(value: Vec2, end_time: f64) -> HbVel {
        HbVel {
            value,
            resize: Vec2::zero(),
            end_time,
        }
    }

    /// Creates a stationary `HbVel`.
    #[inline]
    pub fn still() -> HbVel {
        HbVel {
            value: Vec2::zero(),
            resize: Vec2::zero(),
            end_time: f64::INFINITY,
        }
    }

    /// Creates a stationary `HbVel` with the given `end_time`.
    #[inline]
    pub fn still_until(end_time: f64) -> HbVel {
        HbVel {
            value: Vec2::zero(),
            resize: Vec2::zero(),
            end_time,
        }
    }
}

impl From<Vec2> for HbVel {
    fn from(value: Vec2) -> HbVel {
        HbVel::moving(value)
    }
}

impl PlacedBounds for HbVel {
    fn bounds_center(&self) -> &Vec2 {
        &self.value
    }
    fn bounds_dims(&self) -> &Vec2 {
        &self.resize
    }
}

/// Represents a moving shape for continuous collision testing.
#[derive(PartialEq, Clone, Debug)]
pub struct Hitbox {
    /// The placed shape at the given point in time.
    ///
    /// The width and height of the shape must be greater than `padding` at all
    /// times.
    pub value: PlacedShape,

    /// Velocity information describing how the hitbox shape is changing over
    /// time.
    pub vel: HbVel,
}

//TODO invoke hitbox.validate() in more places so that inconsistencies are still found in optimized builds, just found later

impl Hitbox {
    /// Constructs a new hitbox with the given `value` and `vel`.
    #[inline]
    pub fn new(value: PlacedShape, vel: HbVel) -> Hitbox {
        Hitbox { value, vel }
    }

    fn advanced_shape(&self, time: f64) -> PlacedShape {
        assert!(time < HIGH_TIME, "requires time < {}", HIGH_TIME);
        self.value.advance(self.vel.value, self.vel.resize, time)
    }

    fn validate(&self, min_size: f64, present_time: f64) {
        assert!(
            !self.vel.end_time.is_nan() && self.vel.end_time >= present_time,
            "end time must exceed present time"
        );
        if self.value.kind() == ShapeKind::Circle {
            assert_eq!(
                self.vel.resize.x, self.vel.resize.y,
                "circle resize velocity must maintain aspect ratio"
            );
        }
        assert!(
            self.value.dims().x >= min_size && self.value.dims().y >= min_size,
            "shape width/height must be at least {}",
            min_size
        );
    }

    fn time_until_too_small(&self, min_size: f64) -> f64 {
        let min_size = min_size * 0.9;
        assert!(self.value.dims().x > min_size && self.value.dims().y > min_size);
        let mut time = f64::INFINITY;
        if self.vel.resize.x < 0.0 {
            time = time.min((min_size - self.value.dims().x) / self.vel.resize.x);
        }
        if self.vel.resize.y < 0.0 {
            time = time.min((min_size - self.value.dims().y) / self.vel.resize.y);
        }
        time
    }

    fn to_dur_hitbox(&self, time: f64) -> DurHitbox {
        assert!(time <= self.vel.end_time);
        DurHitbox {
            value: self.value,
            vel: DurHbVel {
                value: self.vel.value,
                resize: self.vel.resize,
                duration: self.vel.end_time - time,
            },
        }
    }
}

/// A group id that may be used as a first measure to efficiently filter out
/// hitboxes that don't interact.
///
/// The total number of groups used should in general be very small. Often 1 is
/// enough, and 10 is excessive. As an example, in a
/// [danmaku](https://en.wikipedia.org/wiki/Shoot_%27em_up#Bullet_hell_and_niche_appeal)
/// game (which has many bullets on screen that do not interact with each
/// other), we may use one group for bullets and one group for everything else,
/// to avoid the quadratic cost of comparing all nearby bullets with each other.
pub type HbGroup = u32;

static DEFAULT_GROUPS: [HbGroup; 1] = [0];

/// A trait that holds metadata for describing a hitbox.
///
/// A user of `Collider` will need to implement an HbProfile that best suites
/// their needs in a game. The most basic HbProfile will just contain an integer
/// ID for the hitbox, but a user may define additional metadata for identfying
/// the hitbox and describing interactivity. An HbProfile must implement the
/// `Copy` trait and should not take up much memory.
pub trait HbProfile: Copy {
    /// A unique identifier for the hitbox.
    ///
    /// Trying to have multiple hitboxes with the same `id` to a `Collider`
    /// instance simultaneously will result in a panic.
    fn id(&self) -> HbId;

    /// Returns the group id associated with the hitbox. Default is `Some(0)`.
    ///
    /// If `None` is returned, then no collisions will be reported for this
    /// hitbox at all.
    fn group(&self) -> Option<HbGroup> {
        Some(0)
    }

    /// Returns a list of groups that this hitbox can interact with. Default is
    /// `[0]`.
    ///
    /// Using large lists of groups may be inefficient.
    fn interact_groups(&self) -> &'static [HbGroup] {
        &DEFAULT_GROUPS
    }

    /// Returns true if the pair of hitboxes should be checked for collisions.
    ///
    /// This method should be commutative. This method should be consistent with
    /// `group` and `interact_groups`, although possibly more restrictive.
    fn can_interact(&self, other: &Self) -> bool;
}
<|endoftext|>