<commit_before>//! Hubcaps provides a set of building blocks for interacting with the Github API
//!
//! # Examples
//!
//!  Typical use will require instantiation of a Github client. Which requires
//! a user agent string, a `hyper::Client`, and set of `hubcaps::Credentials`.
//!
//! The hyper client should be configured with tls.
//!
//! ```no_run
//! extern crate hubcaps;
//! extern crate hyper;
//! extern crate hyper_native_tls;
//!
//! use hubcaps::{Credentials, Github};
//! use hyper::Client;
//! use hyper::net::HttpsConnector;
//! use hyper_native_tls::NativeTlsClient;
//!
//! fn main() {
//!   let github = Github::new(
//!     String::from("user-agent-name"),
//!     Client::with_connector(
//!       HttpsConnector::new(
//!         NativeTlsClient::new().unwrap()
//!       )
//!     ),
//!     Credentials::Token(
//!       String::from("personal-access-token")
//!     )
//!   );
//! }
//! ```
//!
//! Github enterprise users will want to create a client with the
//! [Github#host](struct.Github.html#method.host) method
//!
//! Access to various services are provided via methods on instances of the `Github` type.
//!
//! The convention for executing operations typically looks like
//! `github.repo(.., ..).service().operation(OperationOptions)` where operation may be `create`,
//! `delete`, ect.

//! Services and their types are packaged under their own module namespace.
//! A service interface will provide access to operations and operations may access options types
//! this define the various parameter options available for the operation. Most operation option
//! types expose `builder()` methods for a builder oriented style of constructing options.
//!
//! # Errors
//!
//! Operations typically result in a `hubcaps::Result` Type which is an alias for Rust's
//! built-in Result with the Err Type fixed to the
//! [hubcaps::Error](errors/enum.Error.html) enum type.
//!
#[macro_use]
extern crate log;
#[macro_use]
extern crate hyper;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate url;

// all the modules!
use serde::de::Deserialize;
pub mod users;
pub mod comments;
pub mod review_comments;
pub mod pull_commits;
pub mod keys;
pub mod gists;
pub mod deployments;
pub mod errors;
pub mod hooks;
pub mod issues;
pub mod labels;
pub mod rep;
pub mod releases;
pub mod repositories;
pub mod statuses;
pub mod pulls;
pub mod search;
pub mod organizations;

// fixme: move reps to modules
pub use rep::*;

pub use errors::Error;
use gists::{Gists, UserGists};
use search::Search;
use hyper::Client;
use hyper::client::RequestBuilder;
use hyper::method::Method;
use hyper::header::{qitem, Accept, Authorization, ContentLength, UserAgent};
use hyper::mime::Mime;
use hyper::status::StatusCode;
use repositories::{Repository, Repositories, UserRepositories, OrganizationRepositories};
use organizations::{Organizations, UserOrganizations};
use std::fmt;
use std::io::Read;
use url::Url;
use std::collections::HashMap;

header! { (Link, "Link") => [String] }

const DEFAULT_HOST: &'static str = "https://api.github.com";

/// alias for Result that infers hubcaps::Error as Err
pub type Result<T> = std::result::Result<T, Error>;

enum MediaType {
    Json,
    Preview(&'static str),
}

impl Default for MediaType {
    fn default() -> MediaType {
        MediaType::Json
    }
}

impl From<MediaType> for Mime {
    fn from(media: MediaType) -> Mime {
        match media {
            MediaType::Json => "application/vnd.github.v3+json".parse().unwrap(),
            MediaType::Preview(codename) => {
                format!("application/vnd.github.{}-preview+json", codename).parse().unwrap()
            }
        }
    }
}

/// enum representation of Github list sorting options
#[derive(Clone, Debug, PartialEq)]
pub enum SortDirection {
    /// Sort in ascending order
    Asc,
    /// Sort in descending order
    Desc,
}

impl fmt::Display for SortDirection {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "{}",
               match *self {
                   SortDirection::Asc => "asc",
                   SortDirection::Desc => "desc",
               })
    }
}

impl Default for SortDirection {
    fn default() -> SortDirection {
        SortDirection::Asc
    }
}

/// Various forms of authentication credentials supported by Github
#[derive(Debug, PartialEq)]
pub enum Credentials {
    /// No authentication
    None,
    /// Oauth token string
    /// https://developer.github.com/v3/#oauth2-token-sent-in-a-header
    Token(String),
    /// Oauth client id and secret
    /// https://developer.github.com/v3/#oauth2-keysecret
    Client(String, String),
}

impl Default for Credentials {
    fn default() -> Credentials {
        Credentials::None
    }
}

/// Entry point interface for interacting with Github API
pub struct Github {
    host: String,
    agent: String,
    client: Client,
    credentials: Credentials,
}

impl Github {
    /// Create a new Github instance
    pub fn new<A>(agent: A, client: Client, credentials: Credentials) -> Github
        where A: Into<String>
    {
        Github::host(DEFAULT_HOST, agent, client, credentials)
    }

    /// Create a new Github instance hosted at a custom location.
    /// Useful for github enterprise installations ( yourdomain.com/api/v3/ )
    pub fn host<H, A>(host: H, agent: A, client: Client, credentials: Credentials) -> Github
        where H: Into<String>,
              A: Into<String>
    {
        Github {
            host: host.into(),
            agent: agent.into(),
            client: client,
            credentials: credentials,
        }
    }

    /// Return a reference to a Github reposistory
    pub fn repo<O, R>(&self, owner: O, repo: R) -> Repository
        where O: Into<String>,
              R: Into<String>
    {
        Repository::new(self, owner, repo)
    }

    /// Return a reference to the collection of repositories owned by and
    /// associated with an owner
    pub fn user_repos<S>(&self, owner: S) -> UserRepositories
        where S: Into<String>
    {
        UserRepositories::new(self, owner)
    }

    /// Return a reference to the collection of repositories owned by the user
    /// associated with the current authentication credentials
    pub fn repos(&self) -> Repositories {
        Repositories::new(self)
    }

    /// Return a reference to the collection of organizations that the user
    /// associated with the current authentication credentials is in
    pub fn orgs(&self) -> Organizations {
        Organizations::new(self)
    }

    /// Return a reference to the collection of organizations a user
    /// is publicly associated with
    pub fn user_orgs<U>(&self, user: U) -> UserOrganizations
        where U: Into<String>
    {
        UserOrganizations::new(self, user)
    }

    /// Return a reference to an interface that provides access to a user's gists
    pub fn user_gists<O>(&self, owner: O) -> UserGists
        where O: Into<String>
    {
        UserGists::new(self, owner)
    }

    /// Return a reference to an interface that provides access to the
    /// gists belonging to the owner of the token used to configure this client
    pub fn gists(&self) -> Gists {
        Gists::new(self)
    }

    pub fn search(&self) -> Search {
        Search::new(self)
    }

    /// Return a reference to the collection of repositories owned by and
    /// associated with an organization
    pub fn org_repos<O>(&self, org: O) -> OrganizationRepositories
        where O: Into<String>
    {
        OrganizationRepositories::new(self, org)
    }

    fn authenticate(&self, method: Method, url: String) -> RequestBuilder {
        match self.credentials {
            Credentials::Token(ref token) => {
                self.client.request(method, &url).header(Authorization(format!("token {}", token)))
            }
            Credentials::Client(ref id, ref secret) => {

                let mut parsed = Url::parse(&url).unwrap();
                parsed.query_pairs_mut()
                    .append_pair("client_id", id)
                    .append_pair("client_secret", secret);
                self.client.request(method, parsed)
            }
            Credentials::None => self.client.request(method, &url),
        }
    }

    fn iter<'a, D, I>(&'a self, uri: String, into_items: fn(D) -> Vec<I>) -> Result<Iter<'a, D, I>>
        where D: Deserialize
    {
        Iter::new(self, self.host.clone() + &uri, into_items)
    }

    fn request<D>(&self,
                  method: Method,
                  uri: String,
                  body: Option<&[u8]>,
                  media_type: MediaType)
                  -> Result<(Option<Links>, D)>
        where D: Deserialize
    {
        let builder = self.authenticate(method, uri)
            .header(UserAgent(self.agent.to_owned()))
            .header(Accept(vec![qitem(From::from(media_type))]));

        let mut res = try!(match body {
            Some(ref bod) => builder.body(*bod).send(),
            _ => builder.send(),
        });

        let mut body = match res.headers.clone().get::<ContentLength>() {
            Some(&ContentLength(len)) => String::with_capacity(len as usize),
            _ => String::new(),
        };
        try!(res.read_to_string(&mut body));

        let links = res.headers.get::<Link>().map(|&Link(ref value)| Links::new(value.to_owned()));

        debug!("rec response {:#?} {:#?} {}", res.status, res.headers, body);
        match res.status {
            StatusCode::Conflict |
            StatusCode::BadRequest |
            StatusCode::UnprocessableEntity |
            StatusCode::Unauthorized |
            StatusCode::NotFound |
            StatusCode::Forbidden => {
                Err(Error::Fault {
                    code: res.status,
                    error: try!(serde_json::from_str::<errors::ClientError>(&body)),
                })
            }
            _ => Ok((links, try!(serde_json::from_str::<D>(&body)))),
        }
    }

    fn request_entity<D>(&self,
                         method: Method,
                         uri: String,
                         body: Option<&[u8]>,
                         media_type: MediaType)
                         -> Result<D>
        where D: Deserialize
    {
        self.request(method, uri, body, media_type).map(|(_, entity)| entity)
    }

    fn get<D>(&self, uri: &str) -> Result<D>
        where D: Deserialize
    {
        self.request_entity(Method::Get, self.host.clone() + uri, None, MediaType::Json)
    }

    fn delete(&self, uri: &str) -> Result<()> {
        match self.request_entity::<()>(Method::Delete,
                                        self.host.clone() + uri,
                                        None,
                                        MediaType::Json) {
            Err(Error::Codec(_)) => Ok(()),
            otherwise => otherwise,
        }
    }

    fn post<D>(&self, uri: &str, message: &[u8]) -> Result<D>
        where D: Deserialize
    {
        self.request_entity(Method::Post,
                            self.host.clone() + uri,
                            Some(message),
                            MediaType::Json)
    }

    fn patch<D>(&self, uri: &str, message: &[u8]) -> Result<D>
        where D: Deserialize
    {
        self.request_entity(Method::Patch,
                            self.host.clone() + uri,
                            Some(message),
                            MediaType::Json)
    }

    fn put<D>(&self, uri: &str, message: &[u8]) -> Result<D>
        where D: Deserialize
    {
        self.request_entity(Method::Put,
                            self.host.clone() + uri,
                            Some(message),
                            MediaType::Json)
    }
}

pub struct Iter<'a, D, I> {
    github: &'a Github,
    next_link: Option<String>,
    into_items: fn(D) -> Vec<I>,
    items: Vec<I>,
}

impl<'a, D, I> Iter<'a, D, I>
    where D: Deserialize
{
    pub fn new(github: &'a Github,
               uri: String,
               into_items: fn(D) -> Vec<I>)
               -> Result<Iter<'a, D, I>> {
        let (links, payload) = try!(github.request::<D>(Method::Get, uri, None, MediaType::Json));
        Ok(Iter {
            github: github,
            next_link: links.and_then(|l| l.next()),
            into_items: into_items,
            items: into_items(payload),
        })
    }

    fn set_next(&mut self, next: Option<String>) {
        self.next_link = next;
    }
}

impl<'a, D, I> Iterator for Iter<'a, D, I>
    where D: Deserialize
{
    type Item = I;
    fn next(&mut self) -> Option<I> {
        self.items.pop().or_else(|| {
            self.next_link.clone().and_then(|ref next_link| {
                self.github
                    .request::<D>(Method::Get, next_link.to_owned(), None, MediaType::Json)
                    .ok()
                    .and_then(|(links, payload)| {
                        self.set_next(links.and_then(|l| l.next()));
                        self.items = (self.into_items)(payload);
                        self.next()
                    })
            })
        })
    }
}

#[derive(Debug)]
pub struct Links {
    values: HashMap<String, String>,
}

impl Links {
    pub fn new<V>(value: V) -> Links
        where V: Into<String>
    {
        let values = value.into()
            .split(",")
            .map(|link| {
                let parts = link.split(";").collect::<Vec<_>>();
                (parts[1].to_owned().replace(" rel=\"", "").replace("\"", ""),
                 parts[0].to_owned().replace("<", "").replace(">", "").replace(" ", ""))
            })
            .fold(HashMap::new(), |mut acc, (rel, link)| {
                acc.insert(rel, link);
                acc
            });
        Links { values: values }
    }

    pub fn next(&self) -> Option<String> {
        self.values.get("next").map(|s| s.to_owned())
    }

    pub fn prev(&self) -> Option<String> {
        self.values.get("prev").map(|s| s.to_owned())
    }

    pub fn last(&self) -> Option<String> {
        self.values.get("last").map(|s| s.to_owned())
    }
}

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

    #[test]
    fn test_parse_links() {
        let links = Links::new(r#"<linknext>; rel="next", <linklast>; rel="last""#);
        assert_eq!(links.next(), Some("linknext".to_owned()));
        assert_eq!(links.last(), Some("linklast".to_owned()));
    }

    #[test]
    fn default_sort_direction() {
        let default: SortDirection = Default::default();
        assert_eq!(default, SortDirection::Asc)
    }

    #[test]
    fn default_credentials() {
        let default: Credentials = Default::default();
        assert_eq!(default, Credentials::None)
    }
}
<commit_msg>start warning on docs<commit_after>//! Hubcaps provides a set of building blocks for interacting with the Github API
//!
//! # Examples
//!
//!  Typical use will require instantiation of a Github client. Which requires
//! a user agent string, a `hyper::Client`, and set of `hubcaps::Credentials`.
//!
//! The hyper client should be configured with tls.
//!
//! ```no_run
//! extern crate hubcaps;
//! extern crate hyper;
//! extern crate hyper_native_tls;
//!
//! use hubcaps::{Credentials, Github};
//! use hyper::Client;
//! use hyper::net::HttpsConnector;
//! use hyper_native_tls::NativeTlsClient;
//!
//! fn main() {
//!   let github = Github::new(
//!     String::from("user-agent-name"),
//!     Client::with_connector(
//!       HttpsConnector::new(
//!         NativeTlsClient::new().unwrap()
//!       )
//!     ),
//!     Credentials::Token(
//!       String::from("personal-access-token")
//!     )
//!   );
//! }
//! ```
//!
//! Github enterprise users will want to create a client with the
//! [Github#host](struct.Github.html#method.host) method
//!
//! Access to various services are provided via methods on instances of the `Github` type.
//!
//! The convention for executing operations typically looks like
//! `github.repo(.., ..).service().operation(OperationOptions)` where operation may be `create`,
//! `delete`, ect.

//! Services and their types are packaged under their own module namespace.
//! A service interface will provide access to operations and operations may access options types
//! this define the various parameter options available for the operation. Most operation option
//! types expose `builder()` methods for a builder oriented style of constructing options.
//!
//! # Errors
//!
//! Operations typically result in a `hubcaps::Result` Type which is an alias for Rust's
//! built-in Result with the Err Type fixed to the
//! [hubcaps::Error](errors/enum.Error.html) enum type.
//!
#![warn(missing_docs)] // todo: make this a deny eventually

#[macro_use]
extern crate log;
#[macro_use]
extern crate hyper;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate url;

// all the modules!
use serde::de::Deserialize;
pub mod users;
pub mod comments;
pub mod review_comments;
pub mod pull_commits;
pub mod keys;
pub mod gists;
pub mod deployments;
pub mod errors;
pub mod hooks;
pub mod issues;
pub mod labels;
pub mod rep;
pub mod releases;
pub mod repositories;
pub mod statuses;
pub mod pulls;
pub mod search;
pub mod organizations;

// fixme: move reps to modules
pub use rep::*;

pub use errors::Error;
use gists::{Gists, UserGists};
use search::Search;
use hyper::Client;
use hyper::client::RequestBuilder;
use hyper::method::Method;
use hyper::header::{qitem, Accept, Authorization, ContentLength, UserAgent};
use hyper::mime::Mime;
use hyper::status::StatusCode;
use repositories::{Repository, Repositories, UserRepositories, OrganizationRepositories};
use organizations::{Organizations, UserOrganizations};
use std::fmt;
use std::io::Read;
use url::Url;
use std::collections::HashMap;

/// Link header type
header! { (Link, "Link") => [String] }

const DEFAULT_HOST: &'static str = "https://api.github.com";

/// alias for Result that infers hubcaps::Error as Err
pub type Result<T> = std::result::Result<T, Error>;

/// Github defined Media types
/// See [this doc](https://developer.github.com/v3/media/) for more for more information
enum MediaType {
    /// Return json (the default)
    Json,
    /// Return json in preview form
    Preview(&'static str),
}

impl Default for MediaType {
    fn default() -> MediaType {
        MediaType::Json
    }
}

impl From<MediaType> for Mime {
    fn from(media: MediaType) -> Mime {
        match media {
            MediaType::Json => "application/vnd.github.v3+json".parse().unwrap(),
            MediaType::Preview(codename) => {
                format!("application/vnd.github.{}-preview+json", codename).parse().unwrap()
            }
        }
    }
}

/// enum representation of Github list sorting options
#[derive(Clone, Debug, PartialEq)]
pub enum SortDirection {
    /// Sort in ascending order (the default)
    Asc,
    /// Sort in descending order
    Desc,
}

impl fmt::Display for SortDirection {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "{}",
               match *self {
                   SortDirection::Asc => "asc",
                   SortDirection::Desc => "desc",
               })
    }
}

impl Default for SortDirection {
    fn default() -> SortDirection {
        SortDirection::Asc
    }
}

/// Various forms of authentication credentials supported by Github
#[derive(Debug, PartialEq)]
pub enum Credentials {
    /// No authentication (the default)
    None,
    /// Oauth token string
    /// https://developer.github.com/v3/#oauth2-token-sent-in-a-header
    Token(String),
    /// Oauth client id and secret
    /// https://developer.github.com/v3/#oauth2-keysecret
    Client(String, String),
}

impl Default for Credentials {
    fn default() -> Credentials {
        Credentials::None
    }
}

/// Entry point interface for interacting with Github API
pub struct Github {
    host: String,
    agent: String,
    client: Client,
    credentials: Credentials,
}

impl Github {
    /// Create a new Github instance. This will typically be how you interface with all
    /// other operations
    pub fn new<A>(agent: A, client: Client, credentials: Credentials) -> Github
        where A: Into<String>
    {
        Github::host(DEFAULT_HOST, agent, client, credentials)
    }

    /// Create a new Github instance hosted at a custom location.
    /// Useful for github enterprise installations ( yourdomain.com/api/v3/ )
    pub fn host<H, A>(host: H, agent: A, client: Client, credentials: Credentials) -> Github
        where H: Into<String>,
              A: Into<String>
    {
        Github {
            host: host.into(),
            agent: agent.into(),
            client: client,
            credentials: credentials,
        }
    }

    /// Return a reference to a Github reposistory
    pub fn repo<O, R>(&self, owner: O, repo: R) -> Repository
        where O: Into<String>,
              R: Into<String>
    {
        Repository::new(self, owner, repo)
    }

    /// Return a reference to the collection of repositories owned by and
    /// associated with an owner
    pub fn user_repos<S>(&self, owner: S) -> UserRepositories
        where S: Into<String>
    {
        UserRepositories::new(self, owner)
    }

    /// Return a reference to the collection of repositories owned by the user
    /// associated with the current authentication credentials
    pub fn repos(&self) -> Repositories {
        Repositories::new(self)
    }

    /// Return a reference to the collection of organizations that the user
    /// associated with the current authentication credentials is in
    pub fn orgs(&self) -> Organizations {
        Organizations::new(self)
    }

    /// Return a reference to the collection of organizations a user
    /// is publicly associated with
    pub fn user_orgs<U>(&self, user: U) -> UserOrganizations
        where U: Into<String>
    {
        UserOrganizations::new(self, user)
    }

    /// Return a reference to an interface that provides access to a user's gists
    pub fn user_gists<O>(&self, owner: O) -> UserGists
        where O: Into<String>
    {
        UserGists::new(self, owner)
    }

    /// Return a reference to an interface that provides access to the
    /// gists belonging to the owner of the token used to configure this client
    pub fn gists(&self) -> Gists {
        Gists::new(self)
    }

    /// Return a reference to an interface that provides access to search operations
    pub fn search(&self) -> Search {
        Search::new(self)
    }

    /// Return a reference to the collection of repositories owned by and
    /// associated with an organization
    pub fn org_repos<O>(&self, org: O) -> OrganizationRepositories
        where O: Into<String>
    {
        OrganizationRepositories::new(self, org)
    }

    fn authenticate(&self, method: Method, url: String) -> RequestBuilder {
        match self.credentials {
            Credentials::Token(ref token) => {
                self.client.request(method, &url).header(Authorization(format!("token {}", token)))
            }
            Credentials::Client(ref id, ref secret) => {

                let mut parsed = Url::parse(&url).unwrap();
                parsed.query_pairs_mut()
                    .append_pair("client_id", id)
                    .append_pair("client_secret", secret);
                self.client.request(method, parsed)
            }
            Credentials::None => self.client.request(method, &url),
        }
    }

    fn iter<'a, D, I>(&'a self, uri: String, into_items: fn(D) -> Vec<I>) -> Result<Iter<'a, D, I>>
        where D: Deserialize
    {
        Iter::new(self, self.host.clone() + &uri, into_items)
    }

    fn request<D>(&self,
                  method: Method,
                  uri: String,
                  body: Option<&[u8]>,
                  media_type: MediaType)
                  -> Result<(Option<Links>, D)>
        where D: Deserialize
    {
        let builder = self.authenticate(method, uri)
            .header(UserAgent(self.agent.to_owned()))
            .header(Accept(vec![qitem(From::from(media_type))]));

        let mut res = try!(match body {
            Some(ref bod) => builder.body(*bod).send(),
            _ => builder.send(),
        });

        let mut body = match res.headers.clone().get::<ContentLength>() {
            Some(&ContentLength(len)) => String::with_capacity(len as usize),
            _ => String::new(),
        };
        try!(res.read_to_string(&mut body));

        let links = res.headers.get::<Link>().map(|&Link(ref value)| Links::new(value.to_owned()));

        debug!("rec response {:#?} {:#?} {}", res.status, res.headers, body);
        match res.status {
            StatusCode::Conflict |
            StatusCode::BadRequest |
            StatusCode::UnprocessableEntity |
            StatusCode::Unauthorized |
            StatusCode::NotFound |
            StatusCode::Forbidden => {
                Err(Error::Fault {
                    code: res.status,
                    error: try!(serde_json::from_str::<errors::ClientError>(&body)),
                })
            }
            _ => Ok((links, try!(serde_json::from_str::<D>(&body)))),
        }
    }

    fn request_entity<D>(&self,
                         method: Method,
                         uri: String,
                         body: Option<&[u8]>,
                         media_type: MediaType)
                         -> Result<D>
        where D: Deserialize
    {
        self.request(method, uri, body, media_type).map(|(_, entity)| entity)
    }

    fn get<D>(&self, uri: &str) -> Result<D>
        where D: Deserialize
    {
        self.request_entity(Method::Get, self.host.clone() + uri, None, MediaType::Json)
    }

    fn delete(&self, uri: &str) -> Result<()> {
        match self.request_entity::<()>(Method::Delete,
                                        self.host.clone() + uri,
                                        None,
                                        MediaType::Json) {
            Err(Error::Codec(_)) => Ok(()),
            otherwise => otherwise,
        }
    }

    fn post<D>(&self, uri: &str, message: &[u8]) -> Result<D>
        where D: Deserialize
    {
        self.request_entity(Method::Post,
                            self.host.clone() + uri,
                            Some(message),
                            MediaType::Json)
    }

    fn patch<D>(&self, uri: &str, message: &[u8]) -> Result<D>
        where D: Deserialize
    {
        self.request_entity(Method::Patch,
                            self.host.clone() + uri,
                            Some(message),
                            MediaType::Json)
    }

    fn put<D>(&self, uri: &str, message: &[u8]) -> Result<D>
        where D: Deserialize
    {
        self.request_entity(Method::Put,
                            self.host.clone() + uri,
                            Some(message),
                            MediaType::Json)
    }
}

/// An abstract type used for iterating over result sets
pub struct Iter<'a, D, I> {
    github: &'a Github,
    next_link: Option<String>,
    into_items: fn(D) -> Vec<I>,
    items: Vec<I>,
}

impl<'a, D, I> Iter<'a, D, I>
    where D: Deserialize
{
    /// creates a new instance of an Iter
    pub fn new(github: &'a Github,
               uri: String,
               into_items: fn(D) -> Vec<I>)
               -> Result<Iter<'a, D, I>> {
        let (links, payload) = try!(github.request::<D>(Method::Get, uri, None, MediaType::Json));
        Ok(Iter {
            github: github,
            next_link: links.and_then(|l| l.next()),
            into_items: into_items,
            items: into_items(payload),
        })
    }

    fn set_next(&mut self, next: Option<String>) {
        self.next_link = next;
    }
}

impl<'a, D, I> Iterator for Iter<'a, D, I>
    where D: Deserialize
{
    type Item = I;
    fn next(&mut self) -> Option<I> {
        self.items.pop().or_else(|| {
            self.next_link.clone().and_then(|ref next_link| {
                self.github
                    .request::<D>(Method::Get, next_link.to_owned(), None, MediaType::Json)
                    .ok()
                    .and_then(|(links, payload)| {
                        self.set_next(links.and_then(|l| l.next()));
                        self.items = (self.into_items)(payload);
                        self.next()
                    })
            })
        })
    }
}

/// An abstract collection of Link header urls
/// Exposes interfaces to access link relations github typically
/// sends as headers
#[derive(Debug)]
pub struct Links {
    values: HashMap<String, String>,
}

impl Links {
    /// Creates a new Links instance given a raw header string value
    pub fn new<V>(value: V) -> Links
        where V: Into<String>
    {
        let values = value.into()
            .split(",")
            .map(|link| {
                let parts = link.split(";").collect::<Vec<_>>();
                (parts[1].to_owned().replace(" rel=\"", "").replace("\"", ""),
                 parts[0].to_owned().replace("<", "").replace(">", "").replace(" ", ""))
            })
            .fold(HashMap::new(), |mut acc, (rel, link)| {
                acc.insert(rel, link);
                acc
            });
        Links { values: values }
    }

    /// Returns next link url, when vailable
    pub fn next(&self) -> Option<String> {
        self.values.get("next").map(|s| s.to_owned())
    }

    /// Returns prev link url, when vailable
    pub fn prev(&self) -> Option<String> {
        self.values.get("prev").map(|s| s.to_owned())
    }

    /// Returns last link url, when vailable
    pub fn last(&self) -> Option<String> {
        self.values.get("last").map(|s| s.to_owned())
    }
}

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

    #[test]
    fn test_parse_links() {
        let links = Links::new(r#"<linknext>; rel="next", <linklast>; rel="last""#);
        assert_eq!(links.next(), Some("linknext".to_owned()));
        assert_eq!(links.last(), Some("linklast".to_owned()));
    }

    #[test]
    fn default_sort_direction() {
        let default: SortDirection = Default::default();
        assert_eq!(default, SortDirection::Asc)
    }

    #[test]
    fn default_credentials() {
        let default: Credentials = Default::default();
        assert_eq!(default, Credentials::None)
    }
}
<|endoftext|>


<commit_before>extern crate chrono;
extern crate copy_dir;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;
extern crate regex;
#[macro_use]
extern crate serde_derive;
extern crate shlex;
extern crate tempdir;

#[cfg(test)]
extern crate toml;

pub mod errors;
mod config;
#[macro_use]
pub mod helpers;

use std::path::Path;
use std::fs::{self, File};
use std::io::Write;
use tempdir::TempDir;
use regex::Regex;
use chrono::Local;

use errors::*;
pub use config::Config;

const RUST_GITHUB_REPO: &'static str = "https://github.com/rust-lang/rust";

/// The main entrypoint of the program.
///
/// This will:
///
/// 1. Update the rust repo to the latest version
/// 2. Tell rustbuild to build the docs by tweaking its `config.toml`
/// 3. Iterate through each crate in `src/` and build its docs with
///    `./x.py doc src/libfoo`
/// 4. Upload the generated documentation to github pages if there were
///    no errors or if `config.error_handling.upload_with_errors` is set.
pub fn run(cfg: Config) -> Result<()> {
    info!("Starting documentation generation");

    if !cfg.stages.skip_git_update {
        update_rust_repo(&cfg.rust_dir).chain_err(|| "Failed to update the rust repo")?;
    }
    setup_rustbuild_config_file(&cfg.rust_dir)
        .chain_err(|| "Couldn't make sure the config.toml is set properly")?;

    let crates = find_internal_crates(&cfg.rust_dir)
        .chain_err(|| "Unable to get a list of the internal crates")?;

    let mut errors = Vec::new();

    info!("Generating documentation");
    for krate in crates {
        if let Err(e) = generate_docs(&krate, &cfg.rust_dir) {
            if cfg.error_handling.fail_fast {
                return Err(e);
            } else {
                warn!("Error generating docs for {}, {}", krate, e);
                errors.push((krate, e));
            }
        }
    }

    if !cfg.stages.skip_upload && (errors.is_empty() || cfg.error_handling.upload_with_errors) {
        upload_docs(&cfg.rust_dir, &cfg.git_repo).chain_err(|| "Uploading docs failed")?;
    }

    if errors.is_empty() {
        info!("Documentation generation completed successfully");
        Ok(())
    } else {
        info!(
            "Documentation generation completed with {} errors",
            errors.len()
        );

        debug!("Crates which errored:");
        for &(ref krate, _) in &errors {
            debug!("  {}", krate);
        }

        Err(ErrorKind::DocGeneration(errors).into())
    }
}

/// Generate the documentation for the specified library in a local checkout
/// of the rust repo.
fn generate_docs(name: &str, root: &Path) -> Result<()> {
    debug!("Generating docs for {}", name);
    cmd!(in root, "./x.py doc --stage 1 -v {}", name)?;

    Ok(())
}

fn upload_docs(root: &Path, git_repo: &str) -> Result<()> {
    // FIXME: should probably not hard-code this...
    let target = "x86_64-unknown-linux-gnu";

    let docs_dir = root.join("build").join(target).join("crate-docs");

    if !docs_dir.exists() {
        bail!(
            "Couldn't find {} ... were any docs even generated?",
            docs_dir.display()
        );
    }

    let temp = TempDir::new("rustc-internal-docs")?;
    cmd!("git clone {} {}", git_repo, temp.path().display())?;
    cmd!(in temp.path(), "git checkout gh-pages")?;

    debug!("Copying generated docs to {}", temp.path().display());
    helpers::recursive_copy(temp.path(), docs_dir)?;

    // Make a page to redirect people to rustc/index.html if it doesn't
    // already exist
    let index = temp.path().join("index.html");
    if !index.exists() {
        let redirect =
            r#"<meta http-equiv="refresh" content="0; URL=rustc/index.html">"#;
        File::create(index)?.write_all(redirect.as_bytes())?;
    }

    debug!("Pushing to GitHub pages");
    cmd!(in temp.path(), "git add .")?;
    cmd!(in temp.path(), r#"git commit -m "update {}""#, Local::now())?;
    cmd!(in temp.path(), "git push origin gh-pages")?;
    debug!("Docs uploaded");

    Ok(())
}

/// Do a `git clone` or `git pull` to make sure the Rust repo is up to date.
fn update_rust_repo(root: &Path) -> Result<()> {
    if !root.exists() {
        info!("Rust directory not found, cloning into {}", root.display());
        cmd!("git clone {} {}", RUST_GITHUB_REPO, root.display())?;
    } else {
        info!("Updating rust checkout");
        cmd!(in root, "git pull origin master --ff-only")?;
    }

    info!("Rust directory is up to date");
    Ok(())
}

/// Copy across the template config file from `src/bootstrap/config.toml.example`
/// if it doesn't already exist, and make sure we tell rustbuild to build *all*
/// docs.
fn setup_rustbuild_config_file(root: &Path) -> Result<()> {
    debug!("Making sure config.toml is set up correctly");

    let config_file = root.join("config.toml");
    let template = root.join("src/bootstrap/config.toml.example");

    if !config_file.exists() {
        fs::copy(&template, &config_file).chain_err(|| {
            format!("Couldn't copy the template config ({})", template.display())
        })?;
    }

    let mut config_contents = helpers::read_file(&config_file)?;

    let patterns = vec![
        (
            Regex::new("^#?compiler-docs .*$").unwrap(),
            "compiler-docs = true",
        ),
        (Regex::new("^#?docs .*$").unwrap(), "docs = true"),
    ];

    for (pattern, replace) in patterns {
        config_contents = pattern.replace(&config_contents, replace).into_owned();
    }

    debug!("Config file: {}", config_file.display());

    File::create(&config_file)
        .expect("Config file should already exist")
        .write_all(config_contents.as_bytes())
        .chain_err(|| "Couldn't write to config.toml")?;

    Ok(())
}

fn find_internal_crates(root: &Path) -> Result<Vec<String>> {
    debug!("Generating a list of internal crates to document");
    let output = cmd!(in root, "./x.py doc --help --verbose")?;

    let mut crates = Vec::new();

    let crate_pattern = Regex::new(r"\./x\.py doc (src/[^\s]+)").unwrap();

    for line in String::from_utf8_lossy(&output.stdout).lines() {
        if let Some(matches) = crate_pattern.captures(&line) {
            let name = &matches[1];
            trace!("Found {}", name);
            crates.push(name.to_string());
        }
    }

    Ok(crates)
}
<commit_msg>Apparently the example rustbuild toml file moved to the root<commit_after>extern crate chrono;
extern crate copy_dir;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;
extern crate regex;
#[macro_use]
extern crate serde_derive;
extern crate shlex;
extern crate tempdir;

#[cfg(test)]
extern crate toml;

pub mod errors;
mod config;
#[macro_use]
pub mod helpers;

use std::path::Path;
use std::fs::{self, File};
use std::io::Write;
use tempdir::TempDir;
use regex::Regex;
use chrono::Local;

use errors::*;
pub use config::Config;

const RUST_GITHUB_REPO: &'static str = "https://github.com/rust-lang/rust";

/// The main entrypoint of the program.
///
/// This will:
///
/// 1. Update the rust repo to the latest version
/// 2. Tell rustbuild to build the docs by tweaking its `config.toml`
/// 3. Iterate through each crate in `src/` and build its docs with
///    `./x.py doc src/libfoo`
/// 4. Upload the generated documentation to github pages if there were
///    no errors or if `config.error_handling.upload_with_errors` is set.
pub fn run(cfg: Config) -> Result<()> {
    info!("Starting documentation generation");

    if !cfg.stages.skip_git_update {
        update_rust_repo(&cfg.rust_dir).chain_err(|| "Failed to update the rust repo")?;
    }
    setup_rustbuild_config_file(&cfg.rust_dir)
        .chain_err(|| "Couldn't make sure the config.toml is set properly")?;

    let crates = find_internal_crates(&cfg.rust_dir)
        .chain_err(|| "Unable to get a list of the internal crates")?;

    let mut errors = Vec::new();

    info!("Generating documentation");
    for krate in crates {
        if let Err(e) = generate_docs(&krate, &cfg.rust_dir) {
            if cfg.error_handling.fail_fast {
                return Err(e);
            } else {
                warn!("Error generating docs for {}, {}", krate, e);
                errors.push((krate, e));
            }
        }
    }

    if !cfg.stages.skip_upload && (errors.is_empty() || cfg.error_handling.upload_with_errors) {
        upload_docs(&cfg.rust_dir, &cfg.git_repo).chain_err(|| "Uploading docs failed")?;
    }

    if errors.is_empty() {
        info!("Documentation generation completed successfully");
        Ok(())
    } else {
        info!(
            "Documentation generation completed with {} errors",
            errors.len()
        );

        debug!("Crates which errored:");
        for &(ref krate, _) in &errors {
            debug!("  {}", krate);
        }

        Err(ErrorKind::DocGeneration(errors).into())
    }
}

/// Generate the documentation for the specified library in a local checkout
/// of the rust repo.
fn generate_docs(name: &str, root: &Path) -> Result<()> {
    debug!("Generating docs for {}", name);
    cmd!(in root, "./x.py doc --stage 1 -v {}", name)?;

    Ok(())
}

fn upload_docs(root: &Path, git_repo: &str) -> Result<()> {
    // FIXME: should probably not hard-code this...
    let target = "x86_64-unknown-linux-gnu";

    let docs_dir = root.join("build").join(target).join("crate-docs");

    if !docs_dir.exists() {
        bail!(
            "Couldn't find {} ... were any docs even generated?",
            docs_dir.display()
        );
    }

    let temp = TempDir::new("rustc-internal-docs")?;
    cmd!("git clone {} {}", git_repo, temp.path().display())?;
    cmd!(in temp.path(), "git checkout gh-pages")?;

    debug!("Copying generated docs to {}", temp.path().display());
    helpers::recursive_copy(temp.path(), docs_dir)?;

    // Make a page to redirect people to rustc/index.html if it doesn't
    // already exist
    let index = temp.path().join("index.html");
    if !index.exists() {
        let redirect =
            r#"<meta http-equiv="refresh" content="0; URL=rustc/index.html">"#;
        File::create(index)?.write_all(redirect.as_bytes())?;
    }

    debug!("Pushing to GitHub pages");
    cmd!(in temp.path(), "git add .")?;
    cmd!(in temp.path(), r#"git commit -m "update {}""#, Local::now())?;
    cmd!(in temp.path(), "git push origin gh-pages")?;
    debug!("Docs uploaded");

    Ok(())
}

/// Do a `git clone` or `git pull` to make sure the Rust repo is up to date.
fn update_rust_repo(root: &Path) -> Result<()> {
    if !root.exists() {
        info!("Rust directory not found, cloning into {}", root.display());
        cmd!("git clone {} {}", RUST_GITHUB_REPO, root.display())?;
    } else {
        info!("Updating rust checkout");
        cmd!(in root, "git pull origin master --ff-only")?;
    }

    info!("Rust directory is up to date");
    Ok(())
}

/// Copy across the template config file from `src/bootstrap/config.toml.example`
/// if it doesn't already exist, and make sure we tell rustbuild to build *all*
/// docs.
fn setup_rustbuild_config_file(root: &Path) -> Result<()> {
    debug!("Making sure config.toml is set up correctly");

    let config_file = root.join("config.toml");
    let template = root.join("config.toml.example");

    if !config_file.exists() {
        fs::copy(&template, &config_file).chain_err(|| {
            format!("Couldn't copy the template config ({})", template.display())
        })?;
    }

    let mut config_contents = helpers::read_file(&config_file)?;

    let patterns = vec![
        (
            Regex::new("^#?compiler-docs .*$").unwrap(),
            "compiler-docs = true",
        ),
        (Regex::new("^#?docs .*$").unwrap(), "docs = true"),
    ];

    for (pattern, replace) in patterns {
        config_contents = pattern.replace(&config_contents, replace).into_owned();
    }

    debug!("Config file: {}", config_file.display());

    File::create(&config_file)
        .expect("Config file should already exist")
        .write_all(config_contents.as_bytes())
        .chain_err(|| "Couldn't write to config.toml")?;

    Ok(())
}

fn find_internal_crates(root: &Path) -> Result<Vec<String>> {
    debug!("Generating a list of internal crates to document");
    let output = cmd!(in root, "./x.py doc --help --verbose")?;

    let mut crates = Vec::new();

    let crate_pattern = Regex::new(r"\./x\.py doc (src/[^\s]+)").unwrap();

    for line in String::from_utf8_lossy(&output.stdout).lines() {
        if let Some(matches) = crate_pattern.captures(&line) {
            let name = &matches[1];
            trace!("Found {}", name);
            crates.push(name.to_string());
        }
    }

    Ok(crates)
}
<|endoftext|>


<commit_before>extern crate libc;

use libc::{c_int, c_char};

#[link(name = "exempi")]

pub enum Xmp {}
pub enum XmpFile {}
pub enum XmpString {}
pub enum XmpIterator {}

pub const XMP_OPEN_NOOPTION: c_int = 0x00000000;        /**< No open option */
pub const XMP_OPEN_READ: c_int = 0x00000001;            /**< Open for read-only access. */
pub const XMP_OPEN_FORUPDATE: c_int = 0x00000002;       /**< Open for reading and writing. */
pub const XMP_OPEN_ONLYXMP: c_int = 0x00000004;         /**< Only the XMP is wanted, allows space/time optimizations. */
pub const XMP_OPEN_CACHETNAIL: c_int = 0x00000008;      /**< Cache thumbnail if possible, GetThumbnail will be called. */
pub const XMP_OPEN_STRICTLY: c_int = 0x00000010;        /**< Be strict about locating XMP and reconciling with other forms. */
pub const XMP_OPEN_USESMARTHANDLER: c_int = 0x00000020; /**< Require the use of a smart handler. */
pub const XMP_OPEN_USEPACKETSCANNING: c_int = 0x00000040; /**< Force packet scanning, don't use a smart handler. */
pub const XMP_OPEN_LIMITSCANNING: c_int = 0x00000080;     /**< Only packet scan files "known" to need scanning. */
pub const XMP_OPEN_REPAIR_FILE: c_int = 0x00000100; /**< Attempt to repair a file opened for update, default is to not open (throw an exception). */
pub const XMP_OPEN_OPTIMIZEFILELAYOUT: c_int = 0x00000200; /**< Optimize MPEG4 to support stream when updating This can take some time */
pub const XMP_OPEN_INBACKGROUND: c_int = 0x10000000; /**< Set if calling from background thread. */

pub const XMP_CLOSE_NOOPTION: c_int = 0x0000;  /**< No close option */
pub const XMP_CLOSE_SAFEUPDATE: c_int = 0x0001; /**< Write into a temporary file and swap for crash safety. */

extern "C" {
    pub fn xmp_init() -> bool;
    pub fn xmp_terminate();

    pub fn xmp_get_error() -> c_int;

    pub fn xmp_files_new() -> *mut XmpFile;
    pub fn xmp_files_open_new(p: *const c_char, options: c_int) -> *mut XmpFile;
    pub fn xmp_files_open(xf: *mut XmpFile, p:  *const c_char,
                          options: c_int) -> bool;
    pub fn xmp_files_close(xf: *mut XmpFile, options: c_int) -> bool;

    pub fn xmp_files_free(xf: *mut XmpFile) -> bool;

}

#[test]
fn it_works() {
    let inited: bool;
    unsafe {
        inited = xmp_init();

        assert!(inited);

        let xf = xmp_files_new();

        assert!(!xf.is_null());
        assert!(xmp_files_free(xf));
        assert!(xmp_get_error() == 0);

        xmp_terminate();
    }
}
<commit_msg>better unsafe {} style<commit_after>extern crate libc;

use libc::{c_int, c_char};

#[link(name = "exempi")]

pub enum Xmp {}
pub enum XmpFile {}
pub enum XmpString {}
pub enum XmpIterator {}

pub const XMP_OPEN_NOOPTION: c_int = 0x00000000;        /**< No open option */
pub const XMP_OPEN_READ: c_int = 0x00000001;            /**< Open for read-only access. */
pub const XMP_OPEN_FORUPDATE: c_int = 0x00000002;       /**< Open for reading and writing. */
pub const XMP_OPEN_ONLYXMP: c_int = 0x00000004;         /**< Only the XMP is wanted, allows space/time optimizations. */
pub const XMP_OPEN_CACHETNAIL: c_int = 0x00000008;      /**< Cache thumbnail if possible, GetThumbnail will be called. */
pub const XMP_OPEN_STRICTLY: c_int = 0x00000010;        /**< Be strict about locating XMP and reconciling with other forms. */
pub const XMP_OPEN_USESMARTHANDLER: c_int = 0x00000020; /**< Require the use of a smart handler. */
pub const XMP_OPEN_USEPACKETSCANNING: c_int = 0x00000040; /**< Force packet scanning, don't use a smart handler. */
pub const XMP_OPEN_LIMITSCANNING: c_int = 0x00000080;     /**< Only packet scan files "known" to need scanning. */
pub const XMP_OPEN_REPAIR_FILE: c_int = 0x00000100; /**< Attempt to repair a file opened for update, default is to not open (throw an exception). */
pub const XMP_OPEN_OPTIMIZEFILELAYOUT: c_int = 0x00000200; /**< Optimize MPEG4 to support stream when updating This can take some time */
pub const XMP_OPEN_INBACKGROUND: c_int = 0x10000000; /**< Set if calling from background thread. */

pub const XMP_CLOSE_NOOPTION: c_int = 0x0000;  /**< No close option */
pub const XMP_CLOSE_SAFEUPDATE: c_int = 0x0001; /**< Write into a temporary file and swap for crash safety. */

extern "C" {
    pub fn xmp_init() -> bool;
    pub fn xmp_terminate();

    pub fn xmp_get_error() -> c_int;

    pub fn xmp_files_new() -> *mut XmpFile;
    pub fn xmp_files_open_new(p: *const c_char, options: c_int) -> *mut XmpFile;
    pub fn xmp_files_open(xf: *mut XmpFile, p:  *const c_char,
                          options: c_int) -> bool;
    pub fn xmp_files_close(xf: *mut XmpFile, options: c_int) -> bool;

    pub fn xmp_files_free(xf: *mut XmpFile) -> bool;

}

#[test]
fn it_works() {
    let inited = unsafe { xmp_init() };

    assert!(inited);

    let xf = unsafe { xmp_files_new() };

    assert!(!xf.is_null());
    assert!(unsafe { xmp_files_free(xf) });
    assert!(unsafe { xmp_get_error() } == 0);

    unsafe { xmp_terminate() };
}
<|endoftext|>


<commit_before>extern crate num;

// Implements binary operators "&T op U", "T op &U" and "&T op &U"
// based on "T op U" where T and U are expected to be `Copy`able.
// Taken from stdlib. Extremely useful.
macro_rules! ref_binop {
    (impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
        impl<'a> $imp<$u> for &'a $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(*self, other)
            }
        }

        impl<'a> $imp<&'a $u> for $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(self, *other)
            }
        }

        impl<'a, 'b> $imp<&'a $u> for &'b $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(*self, *other)
            }
        }
    }
}

mod vector;
mod matrix;

<commit_msg>export vectors<commit_after>extern crate num;

// Implements binary operators "&T op U", "T op &U" and "&T op &U"
// based on "T op U" where T and U are expected to be `Copy`able.
// Taken from stdlib. Extremely useful.
macro_rules! ref_binop {
    (impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
        impl<'a> $imp<$u> for &'a $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(*self, other)
            }
        }

        impl<'a> $imp<&'a $u> for $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(self, *other)
            }
        }

        impl<'a, 'b> $imp<&'a $u> for &'b $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(*self, *other)
            }
        }
    }
}

mod vector;
mod matrix;

pub use vector::{
    Vec2,
    Vec3,
    Vec4,
};
<|endoftext|>


<commit_before>#![feature(lang_items)]
#![feature(asm)]
#![no_std]

mod runtime;
mod vmx;

pub type CChar = i8;

macro_rules! cstring {
    ($e:expr) => (concat!($e, "\0").as_ptr() as *const CChar)
}

extern "C" {
    fn printk(format: *const CChar, ...);
}

#[no_mangle]
pub extern "C" fn entry() {
    unsafe {
        printk(cstring!("Hello Linux!\n"));
    }
}
<commit_msg>Import module as public<commit_after>#![feature(lang_items)]
#![feature(asm)]
#![no_std]

mod runtime;
pub mod vmx;

pub type CChar = i8;

macro_rules! cstring {
    ($e:expr) => (concat!($e, "\0").as_ptr() as *const CChar)
}

extern "C" {
    fn printk(format: *const CChar, ...);
}

#[no_mangle]
pub extern "C" fn entry() {
    unsafe {
        printk(cstring!("Hello Linux!\n"));
    }
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 Florian Wilkens
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// Crate dependencies
extern crate libc;
extern crate pam_sys as pam;
extern crate users;

// Modules
mod ffi;
mod simple;

// Re-Exports
pub use simple::*;

// Usings
use pam::{PamConversation, PamFlag, PamHandle, PamReturnCode};

/// Main struct to authenticate a user
pub struct Authenticator<'a> {
    handle:         *mut PamHandle,
    credentials:    Box<[&'a str; 2]>
}

impl <'a> Authenticator<'a> {
    /// Creates a new Authenticator with a given service name
    pub fn new(service: &str) -> Option<Authenticator> {
        use std::ffi::CString;
        use std::ptr;

        let creds = Box::new([""; 2]);
        let conv = PamConversation {
            conv:       Some(ffi::converse),
            data_ptr:   creds.as_ptr() as *mut ::libc::c_void
        };
        let mut handle: *mut PamHandle = ptr::null_mut();

        match unsafe {
            pam::start(CString::new(service).unwrap().as_ptr(), ptr::null(), &conv, &mut handle)
        } {
            PamReturnCode::SUCCESS => Some(Authenticator {
                handle:         handle,
                credentials:    creds
            }),
            _   => None
        }
    }

    /// Set the credentials which should be used in the authentication process
    pub fn set_credentials(&mut self, user: &'a str, password: &'a str) {
        self.credentials[0] = user;
        self.credentials[1] = password;
    }

    /// Perform the authentication with the provided credentials
    pub fn authenticate(&self) -> Result<(), PamReturnCode> {
        let success = PamReturnCode::SUCCESS;

        let mut res = unsafe { pam::authenticate(self.handle, PamFlag::NONE) };
        if res != success {
            return self.fail(res);
        }

        res = unsafe { pam::acct_mgmt(self.handle, PamFlag::NONE) };
        if res != success {
            return self.fail(res);
        }

        res = unsafe { pam::setcred(self.handle, PamFlag::ESTABLISH_CRED) };
        if res != success {
            return self.fail(res);
        }
        Ok(())
    }

    /// Open a session for a previously authenticated user and
    /// initialize the environment appropriately (in PAM and regular enviroment variables).
    ///
    /// Does not currently check for authentication and just calls the ffi method,
    /// but clients should not rely on that.
    pub fn open_session(&self) -> Result<(), PamReturnCode> {
        let res = unsafe { pam::open_session(self.handle, PamFlag::NONE) };
        if res != PamReturnCode::SUCCESS {
            return self.fail(res);
        }
        self.initialize_environment();
        Ok(())
    }

    // Initialize the client environment with common variables.
    // Currently always called from Authenticator.open_session()
    fn initialize_environment(&self) {
        let user = users::get_user_by_name(self.credentials[0])
            .expect("Could not get user by name!");

        self.set_env("USER", &user.name);
        self.set_env("LOGNAME", &user.name);
        self.set_env("HOME", &user.home_dir);
        self.set_env("PWD", &user.home_dir);
        self.set_env("SHELL", &user.shell);
        // Taken from https://github.com/gsingh93/display-manager/blob/master/pam.c
        // Should be a better way to get this. Revisit later.
        self.set_env("PATH", "/usr/local/sbin:/usr/local/bin:/usr/bin");
    }

    // Utility function to set an environment variable in PAM and the process
    fn set_env(&self, key: &str, value: &str) {
        use std::env;
        use std::ffi::CString;

        // Set regular environment variable
        env::set_var(key, value);

        // Set pam environment variable
        let name_value = CString::new(format!("{}={}", key, value)).unwrap();
        match unsafe { pam::putenv(self.handle, name_value.as_ptr()) } {
            PamReturnCode::SUCCESS => (),
            _   => panic!("set_env failed!")    //TODO: add proper error handling (through results?)
        }
    }

    // Utility function to properly clean up pam
    // TODO: Move or copy this into Authenticator.drop()?
    fn fail(&self, code: PamReturnCode) -> Result<(), PamReturnCode> {
        unsafe {
            pam::setcred(self.handle, pam::PamFlag::DELETE_CRED);
            pam::end(self.handle, 0);
        }
        Err(code)
    }
}
<commit_msg>Added better error handling to Authenticator struct<commit_after>// Copyright (C) 2015 Florian Wilkens
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// Crate dependencies
extern crate libc;
extern crate pam_sys as pam;
extern crate users;

// Modules
mod ffi;
mod simple;

// Re-Exports
pub use simple::*;

// Usings
use pam::{PamConversation, PamFlag, PamHandle, PamReturnCode};

/// Main struct to authenticate a user
pub struct Authenticator<'a> {
    handle:         *mut PamHandle,
    credentials:    Box<[&'a str; 2]>
}

impl <'a> Authenticator<'a> {
    /// Creates a new Authenticator with a given service name
    pub fn new(service: &str) -> Option<Authenticator> {
        use std::ffi::CString;
        use std::ptr;

        let creds = Box::new([""; 2]);
        let conv = PamConversation {
            conv:       Some(ffi::converse),
            data_ptr:   creds.as_ptr() as *mut ::libc::c_void
        };
        let mut handle: *mut PamHandle = ptr::null_mut();

        match unsafe {
            pam::start(CString::new(service).unwrap().as_ptr(), ptr::null(), &conv, &mut handle)
        } {
            PamReturnCode::SUCCESS => Some(Authenticator {
                handle:         handle,
                credentials:    creds
            }),
            _   => None
        }
    }

    /// Set the credentials which should be used in the authentication process
    pub fn set_credentials(&mut self, user: &'a str, password: &'a str) {
        self.credentials[0] = user;
        self.credentials[1] = password;
    }

    /// Perform the authentication with the provided credentials
    pub fn authenticate(&self) -> Result<(), PamReturnCode> {
        let success = PamReturnCode::SUCCESS;

        let mut res = unsafe { pam::authenticate(self.handle, PamFlag::NONE) };
        if res != success {
            return self.fail(res);
        }

        res = unsafe { pam::acct_mgmt(self.handle, PamFlag::NONE) };
        if res != success {
            return self.fail(res);
        }

        res = unsafe { pam::setcred(self.handle, PamFlag::ESTABLISH_CRED) };
        if res != success {
            return self.fail(res);
        }
        Ok(())
    }

    /// Open a session for a previously authenticated user and
    /// initialize the environment appropriately (in PAM and regular enviroment variables).
    ///
    /// Does not currently check for authentication and just calls the ffi method,
    /// but clients should not rely on that.
    pub fn open_session(&self) -> Result<(), PamReturnCode> {
        let res = unsafe { pam::open_session(self.handle, PamFlag::NONE) };
        if res != PamReturnCode::SUCCESS {
            return self.fail(res);
        }
        self.initialize_environment();
        Ok(())
    }

    // Initialize the client environment with common variables.
    // Currently always called from Authenticator.open_session()
    fn initialize_environment(&self) {
        let user = users::get_user_by_name(self.credentials[0])
            .expect("Could not get user by name!");

        let result = self.set_env("USER", &user.name)
            .and(self.set_env("LOGNAME", &user.name))
            .and(self.set_env("HOME", &user.home_dir))
            .and(self.set_env("PWD", &user.home_dir))
            .and(self.set_env("SHELL", &user.shell))
            // Taken from https://github.com/gsingh93/display-manager/blob/master/pam.c
            // Should be a better way to get this. Revisit later.
            .and(self.set_env("PATH", "$PATH:/usr/local/sbin:/usr/local/bin:/usr/bin"));

        if result.is_err() {
            println!("Error while environment initialization!");
            println!("pam_putenv returned: {}", result.unwrap_err());
        }
    }

    // Utility function to set an environment variable in PAM and the process
    fn set_env(&self, key: &str, value: &str) -> Result<(), PamReturnCode> {
        use std::env;
        use std::ffi::CString;

        // Set regular environment variable
        env::set_var(key, value);

        // Set pam environment variable
        let name_value = CString::new(format!("{}={}", key, value)).unwrap();
        match unsafe { pam::putenv(self.handle, name_value.as_ptr()) } {
            PamReturnCode::SUCCESS  => Ok(()),
            code                    => Err(code)
        }
    }

    // Utility function to properly clean up pam
    // TODO: Move or copy this into Authenticator.drop()?
    fn fail(&self, code: PamReturnCode) -> Result<(), PamReturnCode> {
        unsafe {
            pam::setcred(self.handle, pam::PamFlag::DELETE_CRED);
            pam::end(self.handle, 0);
        }
        Err(code)
    }
}
<|endoftext|>


<commit_before>
extern crate rustc_serialize;
extern crate amqp;
#[macro_use]
extern crate log;
extern crate argparse;
extern crate rand;

use amqp::{ConsumerCallBackFn, Session, Table, Basic, Channel, Options, Consumer};
use amqp::protocol;
use std::default::Default;
use rustc_serialize::json;
use std::slice;

// for debugging
fn listen_discovery(channel: &mut Channel) {
    fn participant_discovered(channel: &mut Channel, deliver: protocol::basic::Deliver,
                            headers: protocol::basic::BasicProperties, body: Vec<u8>){
        let s = std::str::from_utf8(&body).unwrap();
        let info: ParticipantInfo = json::decode(s).unwrap();
        info!("MsgFlo participant discovered: {:?}", info);
        channel.basic_ack(deliver.delivery_tag, false);
    }

    let queue_name = "fbp";
    let consumer_name = channel.basic_consume(participant_discovered as ConsumerCallBackFn, queue_name,
                                                "", false, false, false, false, Table::new());
    info!("listening for discovery messages");
}

#[derive(Debug, Default, RustcDecodable, RustcEncodable)]
pub struct ParticipantPort {
    pub id: String, // port name
    pub queue: String, // the associated message queue
    // FIXME: support. Is a keyword so needs some special handling   type: String, // datatype, ex: "boolean"
    // options: queue options as specified by the message queue implementation
    // description?? TODO: standardize
}

#[derive(Debug, Default, RustcDecodable, RustcEncodable)]
pub struct ParticipantInfo {
    pub id: String, // unique name
    pub role: String ,// role participant has
    pub component: String, // component the participant is instance of
    pub label: Option<String>, // (optional) short human-readable description
    pub icon: Option<String>, // (optional)
    pub inports: Vec<ParticipantPort>,
    pub outports: Vec<ParticipantPort>,
}

type SendFunction = fn(String, Vec<u8>);
pub type ProcessFunction = fn(Vec<u8>) -> Result<Vec<u8>, Vec<u8>>;
pub struct Participant {
    pub info: ParticipantInfo,
    pub process: ProcessFunction,
}

struct Connection {
    session: Session,
    channel: Channel,
}

// FIXME: wrap in protocol/command/payload envelope
fn create_queue_and_send(channel: &mut Channel, queue_name: &str, payload: String) {

    let queue_declare = channel.queue_declare(queue_name, false, true, false, false, false, Table::new());
    queue_declare.expect("queue creation failed");
    let content_type = Some("application/json".to_string());
    let props = protocol::basic::BasicProperties { content_type: content_type, ..Default::default() };
    debug!("sending on {}: {}", queue_name, payload);
    let res = channel.basic_publish("", queue_name, true, false, props, payload.into_bytes());
    res.expect("send on new queue failed");
}

fn send_discovery(channel: &mut Channel, info: &ParticipantInfo) {
    let queue_name = "fbp"; // TODO: use an exchange istead, requires protocol change in msgflo

    let payload = json::encode(&info).unwrap();
    create_queue_and_send(channel, queue_name, payload);
}

struct PortConsumer {
    process: ProcessFunction,
    portname: String,
    outqueue: String, // FIXME: allow sending on any port, also multiple times
}

fn send_out(channel: &mut Channel, exchange: String, data: Vec<u8>) {

    let routing_key = "".to_string();
    let content_type = Some("application/json".to_string()); // TODO: should be parameter
    let props = protocol::basic::BasicProperties { content_type: content_type, ..Default::default() };
    let s = channel.basic_publish(exchange, routing_key, true, false, props, data);
    s.expect("failed to send");
    debug!("sent output");
}

impl Consumer for PortConsumer {
    fn handle_delivery(&mut self,
                       channel: &mut Channel,
                       deliver: protocol::basic::Deliver,
                       headers: protocol::basic::BasicProperties,
                       body: Vec<u8>) {

        debug!("calling process()");
        let f = self.process;
        let res = f(body);
        debug!("process() returned");

        if res.is_ok() {
            debug!("ACKing and sending");
            send_out(channel, self.outqueue.to_string(), res.unwrap());
            let r = channel.basic_ack(deliver.delivery_tag, false);
        } else {
            error!("process() errored");
            let r = channel.basic_nack(deliver.delivery_tag, true, true);
        }
        // FIXME: send error data
    }
}

// FIXME: actually call ProcessFunction
fn setup_inport(participant: &Participant, port: &ParticipantPort, connection: &mut Connection) {
    debug!("setup inport: {}", port.queue.to_string());

    let consumer = PortConsumer {
        process: participant.process,
        portname: port.id.to_string(),
        outqueue: participant.info.outports[0].queue.to_string(),
    };

    // create
    let declare = connection.channel.queue_declare(port.queue.to_string(), false, true, false, false, false, Table::new());
    declare.expect("inport queue creation failed");

    // subscribe
    let q = port.queue.to_string();
    let cons = connection.channel.basic_consume(consumer, q,
                                                "".to_string(), false, false, false, false, Table::new());

    cons.expect("iport setup failed");

    debug!("inport setup done: {:?}, {:?}", port.id.to_string(), port.queue.to_string());
}

fn setup_outport(participant: &Participant, port: &ParticipantPort, connection: &mut Connection) {

    let exchange_type = "fanout".to_string();
    let declare = connection.channel.exchange_declare(port.queue.to_string(), exchange_type,
                                                    false, true, false, false, false, Table::new());
    declare.expect("outport setup failed");

    debug!("setup outport done: {:?}, {:?}", port.id.to_string(), port.queue.to_string());
}

fn start_participant(participant: &Participant, options: &ParticipantOptions) -> Connection {

    let mut session = Session::open_url(&options.broker).expect("Can't create AMQP session");
    let mut channel = session.open_channel(1).expect("could not open AMQP channel");

    let mut conn = Connection { session: session, channel: channel };

    // setup ports
    setup_inport(&participant, &participant.info.inports[0], &mut conn);
    setup_outport(&participant, &participant.info.outports[0], &mut conn);

    // send MsgFlo participant discovery message
    send_discovery(&mut conn.channel, &participant.info);

    return conn;
}

fn stop_participant(participant: &Participant, connection: &mut Connection) {

    let closed = connection.channel.close(200, "Bye".to_string());
    connection.session.close(200, "Good Bye".to_string());
}

use log::{LogRecord, LogLevel, LogMetadata, LogLevelFilter};

struct SimpleLogger {
    showlevel: LogLevel,
}

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= self.showlevel
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            println!("{} - {}", record.level(), record.args());
        }
    }
}

pub fn init_logger(level: &str) {

    let log_level = match level {
        "error" => LogLevel::Error,
        _ => LogLevel::Debug,
    };

    log::set_logger(|maxlog| {
        maxlog.set(LogLevelFilter::Debug);
        Box::new(SimpleLogger { showlevel: log_level } )
    });

}

#[derive(Debug)]
struct ParticipantOptions {
    role: String,
    broker: String,
    log: String,
}

impl Default for ParticipantOptions {
    fn default() -> ParticipantOptions {
        use rand::{thread_rng, Rng};

        let id: String = thread_rng().gen_ascii_chars().take(5).collect();
        ParticipantOptions {
            broker: "amqp://localhost//".to_string(),
            role: format!("msgflo-rust-{}", id),
            log: "error".to_string(),
        }
    }
}

fn parse(options: &mut ParticipantOptions) {

    use argparse::{StoreTrue, Store};
    let mut parser = argparse::ArgumentParser::new();

    parser.refer(&mut options.role)
        .add_option(&["--role"], Store, "Participant role name");
    parser.refer(&mut options.broker)
        .add_option(&["--broker"], Store, "Address of messaging broker")
        .envvar("MSGFLO_BROKER");
    parser.refer(&mut options.log)
        .add_option(&["--log"], Store, "Log level")
        .envvar("MSGFLO_RUST_LOGLEVEL");

    parser.parse_args_or_exit(); // XXX: should return out
}

// XXX: seems rust-amqp makes program hangs forever if error occurs / channel is borked?
// TODO: pass port info in/out of process()
// TODO: nicer way to declare ports? ideally they are enums not stringly typed?
pub fn participant_main(p: Participant) {

    let mut options = ParticipantOptions { .. Default::default() };
    parse(&mut options);
    init_logger(&options.log);

    let mut c = start_participant(&p, &options);
    c.channel.start_consuming();
    println!("{}({}) started", &options.role, &p.info.component);

    stop_participant(&p, &mut c);
}

<commit_msg>Kill log options, use RUST_LOG instead<commit_after>
extern crate rustc_serialize;
extern crate amqp;
#[macro_use]
extern crate log;
extern crate argparse;
extern crate rand;

use amqp::{ConsumerCallBackFn, Session, Table, Basic, Channel, Options, Consumer};
use amqp::protocol;
use std::default::Default;
use rustc_serialize::json;
use std::slice;

// for debugging
fn listen_discovery(channel: &mut Channel) {
    fn participant_discovered(channel: &mut Channel, deliver: protocol::basic::Deliver,
                            headers: protocol::basic::BasicProperties, body: Vec<u8>){
        let s = std::str::from_utf8(&body).unwrap();
        let info: ParticipantInfo = json::decode(s).unwrap();
        info!("MsgFlo participant discovered: {:?}", info);
        channel.basic_ack(deliver.delivery_tag, false);
    }

    let queue_name = "fbp";
    let consumer_name = channel.basic_consume(participant_discovered as ConsumerCallBackFn, queue_name,
                                                "", false, false, false, false, Table::new());
    info!("listening for discovery messages");
}

#[derive(Debug, Default, RustcDecodable, RustcEncodable)]
pub struct ParticipantPort {
    pub id: String, // port name
    pub queue: String, // the associated message queue
    // FIXME: support. Is a keyword so needs some special handling   type: String, // datatype, ex: "boolean"
    // options: queue options as specified by the message queue implementation
    // description?? TODO: standardize
}

#[derive(Debug, Default, RustcDecodable, RustcEncodable)]
pub struct ParticipantInfo {
    pub id: String, // unique name
    pub role: String ,// role participant has
    pub component: String, // component the participant is instance of
    pub label: Option<String>, // (optional) short human-readable description
    pub icon: Option<String>, // (optional)
    pub inports: Vec<ParticipantPort>,
    pub outports: Vec<ParticipantPort>,
}

type SendFunction = fn(String, Vec<u8>);
pub type ProcessFunction = fn(Vec<u8>) -> Result<Vec<u8>, Vec<u8>>;
pub struct Participant {
    pub info: ParticipantInfo,
    pub process: ProcessFunction,
}

struct Connection {
    session: Session,
    channel: Channel,
}

// FIXME: wrap in protocol/command/payload envelope
fn create_queue_and_send(channel: &mut Channel, queue_name: &str, payload: String) {

    let queue_declare = channel.queue_declare(queue_name, false, true, false, false, false, Table::new());
    queue_declare.expect("queue creation failed");
    let content_type = Some("application/json".to_string());
    let props = protocol::basic::BasicProperties { content_type: content_type, ..Default::default() };
    debug!("sending on {}: {}", queue_name, payload);
    let res = channel.basic_publish("", queue_name, true, false, props, payload.into_bytes());
    res.expect("send on new queue failed");
}

fn send_discovery(channel: &mut Channel, info: &ParticipantInfo) {
    let queue_name = "fbp"; // TODO: use an exchange istead, requires protocol change in msgflo

    let payload = json::encode(&info).unwrap();
    create_queue_and_send(channel, queue_name, payload);
}

struct PortConsumer {
    process: ProcessFunction,
    portname: String,
    outqueue: String, // FIXME: allow sending on any port, also multiple times
}

fn send_out(channel: &mut Channel, exchange: String, data: Vec<u8>) {

    let routing_key = "".to_string();
    let content_type = Some("application/json".to_string()); // TODO: should be parameter
    let props = protocol::basic::BasicProperties { content_type: content_type, ..Default::default() };
    let s = channel.basic_publish(exchange, routing_key, true, false, props, data);
    s.expect("failed to send");
    debug!("sent output");
}

impl Consumer for PortConsumer {
    fn handle_delivery(&mut self,
                       channel: &mut Channel,
                       deliver: protocol::basic::Deliver,
                       headers: protocol::basic::BasicProperties,
                       body: Vec<u8>) {

        debug!("calling process()");
        let f = self.process;
        let res = f(body);
        debug!("process() returned");

        if res.is_ok() {
            debug!("ACKing and sending");
            send_out(channel, self.outqueue.to_string(), res.unwrap());
            let r = channel.basic_ack(deliver.delivery_tag, false);
        } else {
            error!("process() errored");
            let r = channel.basic_nack(deliver.delivery_tag, true, true);
        }
        // FIXME: send error data
    }
}

// FIXME: actually call ProcessFunction
fn setup_inport(participant: &Participant, port: &ParticipantPort, connection: &mut Connection) {
    debug!("setup inport: {}", port.queue.to_string());

    let consumer = PortConsumer {
        process: participant.process,
        portname: port.id.to_string(),
        outqueue: participant.info.outports[0].queue.to_string(),
    };

    // create
    let declare = connection.channel.queue_declare(port.queue.to_string(), false, true, false, false, false, Table::new());
    declare.expect("inport queue creation failed");

    // subscribe
    let q = port.queue.to_string();
    let cons = connection.channel.basic_consume(consumer, q,
                                                "".to_string(), false, false, false, false, Table::new());

    cons.expect("iport setup failed");

    debug!("inport setup done: {:?}, {:?}", port.id.to_string(), port.queue.to_string());
}

fn setup_outport(participant: &Participant, port: &ParticipantPort, connection: &mut Connection) {

    let exchange_type = "fanout".to_string();
    let declare = connection.channel.exchange_declare(port.queue.to_string(), exchange_type,
                                                    false, true, false, false, false, Table::new());
    declare.expect("outport setup failed");

    debug!("setup outport done: {:?}, {:?}", port.id.to_string(), port.queue.to_string());
}

fn start_participant(participant: &Participant, options: &ParticipantOptions) -> Connection {

    let mut session = Session::open_url(&options.broker).expect("Can't create AMQP session");
    let mut channel = session.open_channel(1).expect("could not open AMQP channel");

    let mut conn = Connection { session: session, channel: channel };

    // setup ports
    setup_inport(&participant, &participant.info.inports[0], &mut conn);
    setup_outport(&participant, &participant.info.outports[0], &mut conn);

    // send MsgFlo participant discovery message
    send_discovery(&mut conn.channel, &participant.info);

    return conn;
}

fn stop_participant(participant: &Participant, connection: &mut Connection) {

    let closed = connection.channel.close(200, "Bye".to_string());
    connection.session.close(200, "Good Bye".to_string());
}

use log::{LogRecord, LogLevel, LogMetadata, LogLevelFilter};

struct SimpleLogger {
    showlevel: LogLevel,
}

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= self.showlevel
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            println!("{} - {}", record.level(), record.args());
        }
    }
}

#[derive(Debug)]
struct ParticipantOptions {
    role: String,
    broker: String,
    log: String,
}

impl Default for ParticipantOptions {
    fn default() -> ParticipantOptions {
        use rand::{thread_rng, Rng};

        let id: String = thread_rng().gen_ascii_chars().take(5).collect();
        ParticipantOptions {
            broker: "amqp://localhost//".to_string(),
            role: format!("msgflo-rust-{}", id),
        }
    }
}

fn parse(options: &mut ParticipantOptions) {

    use argparse::{StoreTrue, Store};
    let mut parser = argparse::ArgumentParser::new();

    parser.refer(&mut options.role)
        .add_option(&["--role"], Store, "Participant role name");
    parser.refer(&mut options.broker)
        .add_option(&["--broker"], Store, "Address of messaging broker")
        .envvar("MSGFLO_BROKER");

    parser.parse_args_or_exit(); // XXX: should return out
}

// XXX: seems rust-amqp makes program hangs forever if error occurs / channel is borked?
// TODO: pass port info in/out of process()
// TODO: nicer way to declare ports? ideally they are enums not stringly typed?
pub fn participant_main(p: Participant) {

    let mut options = ParticipantOptions { .. Default::default() };
    parse(&mut options);

    let mut c = start_participant(&p, &options);
    c.channel.start_consuming();
    println!("{}({}) started", &options.role, &p.info.component);

    stop_participant(&p, &mut c);
}

<|endoftext|>


<commit_before>#![feature(vec_push_all)]
#![feature(test)]

extern crate crypto;
extern crate rustc_serialize;
extern crate test;

use crypto::mac::{Mac, MacResult};
use crypto::sha2::Sha256;
use crypto::hmac::Hmac;
use rustc_serialize::base64;

pub const ALMOND_HASH_SEED : &'static [u8; 32] = b"this_is_a_bit_of_arbitrary_data!";

pub struct Almond {
    hash: [u8; 32],
    caveats: Vec<Vec<u8>>,
    generation: u8,
    almond_type: Vec<u8>,
}

impl Almond {
    pub fn create(key: &[u8], generation: u8, almond_type: Vec<u8>) -> Almond {
        let mut almond = Almond {
            hash: *ALMOND_HASH_SEED,
            caveats: Vec::new(),
            generation: generation,
            almond_type: almond_type,
        };

        add_to_hash(&mut almond.hash, key);
        add_to_hash(&mut almond.hash, &[generation]);
        add_to_hash(&mut almond.hash, &almond.almond_type[..]);

        almond
    }

    pub fn parse_and_verify(key: &[u8], input: &[u8]) -> Result<Almond, &'static str> {
        if input.len() < 34 {
            return Err("Too small");
        }

        let hash = &input[..32];
        let generation = input[32];

        let mut split_it = input[33..].split(|c| *c == b'\n');

        let almond_type = try!(split_it.next().ok_or("No initial newline"));

        let mut almond = Almond::create(key, generation, almond_type.to_vec());

        for caveat in split_it {
            almond.add_caveat(caveat.to_vec());
        }

        // Always compare hashes using equality operators that are
        // resistent to timing attacks.
        if MacResult::new(hash) == MacResult::new(almond.hash()) {
            Ok(almond)
        } else {
            Err("Hash do not match")
        }
    }

    pub fn add_caveat(&mut self, caveat: Vec<u8>) -> &mut Self {
        add_to_hash(&mut self.hash, &caveat[..]);
        self.caveats.push(caveat);
        self
    }

    pub fn almond_type(&self) -> &[u8] {
        &self.almond_type[..]
    }

    pub fn generation(&self) -> u8 {
        self.generation
    }

    pub fn caveats(&self) -> &[Vec<u8>] {
        &self.caveats[..]
    }

    pub fn hash(&self) -> &[u8; 32] {
        &self.hash
    }

    pub fn serialize(&self) -> Vec<u8> {
        let mut result : Vec<u8> = Vec::new();

        result.push_all(&self.hash);
        result.push(self.generation);
        result.push_all(&self.almond_type);
        result.push(b'\n');

        for caveat in &self.caveats {
            result.push_all(&caveat[..]);
            result.push(b'\n');
        }

        result.pop();

        result
    }
}

impl base64::ToBase64 for Almond {
    fn to_base64(&self, config: base64::Config) -> String {
        let serialized = self.serialize();
        serialized[..].to_base64(config)
    }
}

fn add_to_hash(hash: &mut [u8], data: &[u8]) {
    let hasher = Sha256::new();
    let mut mac = Hmac::new(hasher, hash);
    mac.input(data);
    mac.raw_result(hash);
}

struct DeconstructedCaveatEntry<'a> {
    pub key: &'a [u8],
    pub value: Option<&'a [u8]>,
    pub accepted: Option<bool>,
}

pub struct Verifier<'a> {
    caveats: Vec<DeconstructedCaveatEntry<'a>>,
    reject: bool,
}

impl <'a> Verifier<'a> {
    pub fn new(almond: &'a Almond, generation: u8, almond_type: &[u8]) -> Verifier<'a> {
        let caveats = almond.caveats().iter()
            .map(
                |caveat| {
                    let mut it = caveat.splitn(2, |c| *c == b' ');

                    // By definition this must have at least one item
                    let key = it.next().unwrap();
                    let value = it.next();

                    DeconstructedCaveatEntry {
                        key: key,
                        value: value,
                        accepted: None,
                    }
                }
            )
            .collect();

        Verifier {
            caveats: caveats,
            reject: almond.generation() != generation || almond.almond_type() != almond_type
        }
    }

    pub fn allow(&mut self, key: &[u8]) -> &mut Self {
        for item in &mut self.caveats {
            if item.key == key {
                item.accepted = item.accepted.or(Some(true));
            }
        }

        self
    }

    pub fn satisfies<F>(&mut self, key: &[u8], mut predicate: F) -> &mut Self
        where F: FnMut(Option<&[u8]>) -> bool
    {
        for item in &mut self.caveats {
            if item.key == key {
                let res = predicate(item.value);
                item.accepted = Some(res && item.accepted.unwrap_or(true));
            }
        }

        self
    }

    pub fn satisfies_exact(&mut self, key: &[u8], value: Option<&[u8]>) {
        for item in &mut self.caveats {
            if item.key == key {
                let res = item.value.map(|x| &*x) == value;
                item.accepted = Some(res && item.accepted.unwrap_or(true));
            }
        }
    }

    pub fn verify(&self) -> bool {
        !self.reject && self.caveats.iter().all(|item| item.accepted.unwrap_or(false))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use test::Bencher;
    use rustc_serialize::base64::{FromBase64, ToBase64, URL_SAFE};

    #[test]
    fn basic_test() {
        let key = b"this_is_a_secret";

        let mut almond = Almond::create(key, 1, b"login".to_vec());
        almond.add_caveat(b"user erikj".to_vec());
        let r = almond.to_base64(URL_SAFE);
        assert_eq!(
            r,
            concat!(
                "yyTNYc-CAXTVkgXkNnl8wdMzBTMgHyLRS",
                "lXrjdf5Uw0BbG9naW4KdXNlciBlcmlrag",
            )
        );

        Almond::parse_and_verify(key, &almond.serialize()[..]).unwrap();
    }

    #[test]
    fn verify_test() {
        let key = b"this_is_a_secret";
        let mut almond = Almond::create(key, 1, b"login".to_vec());
        almond.add_caveat(b"user erikj".to_vec());
        almond.add_caveat(b"guest".to_vec());

        let mut v = Verifier::new(&almond, 1, b"login");
        v.allow(b"user");
        assert!(!v.verify());

        v.satisfies_exact(b"guest", None);
        assert!(v.verify());

        v.satisfies_exact(b"user", Some(b"noterikj"));
        assert!(!v.verify());
    }

    #[test]
    fn verify_test_gen_type() {
        let key = b"this_is_a_secret";
        let mut almond = Almond::create(key, 1, b"login".to_vec());
        almond.add_caveat(b"user erikj".to_vec());

        let mut v = Verifier::new(&almond, 2, b"login");
        v.allow(b"user");
        assert!(!v.verify());

        let mut v = Verifier::new(&almond, 1, b"notlogin");
        v.allow(b"user");
        assert!(!v.verify());
    }

    #[bench]
    fn create(b: &mut Bencher) {
        let key = b"this_is_a_secret";
        b.iter(|| {
            let mut almond = Almond::create(key, 1, b"login".to_vec());
            almond.add_caveat(b"user erikj".to_vec());
            almond.add_caveat(b"fooo bar".to_vec());
            almond.add_caveat(b"testing".to_vec());
            almond.add_caveat(b"teeeeeeeeeeeest".to_vec());
            almond
        });
    }

    #[bench]
    fn parse(b: &mut Bencher) {
        let key = b"this_is_a_secret";

        let b64 = concat!(
            "Rv31sT9t5d31LHPeBFjPewo0TJ1ARbDok7vOWBVNSM4BbG9naW4KdXN",
            "lciBlcmlragpmb29vIGJhcgp0ZXN0aW5nCnRlZWVlZWVlZWVlZWVzdA",
        );
        let parsed = b64.from_base64().unwrap();

        b.iter(|| {
            let almond = Almond::parse_and_verify(
                key,
                &parsed[..],
            );
            almond.unwrap();
        });
    }
}
<commit_msg>Add test. Remove spurious [..]<commit_after>#![feature(vec_push_all)]
#![feature(test)]

extern crate crypto;
extern crate rustc_serialize;
extern crate test;

use crypto::mac::{Mac, MacResult};
use crypto::sha2::Sha256;
use crypto::hmac::Hmac;
use rustc_serialize::base64;

pub const ALMOND_HASH_SEED : &'static [u8; 32] = b"this_is_a_bit_of_arbitrary_data!";

pub struct Almond {
    hash: [u8; 32],
    caveats: Vec<Vec<u8>>,
    generation: u8,
    almond_type: Vec<u8>,
}

impl Almond {
    pub fn create(key: &[u8], generation: u8, almond_type: Vec<u8>) -> Almond {
        let mut almond = Almond {
            hash: *ALMOND_HASH_SEED,
            caveats: Vec::new(),
            generation: generation,
            almond_type: almond_type,
        };

        add_to_hash(&mut almond.hash, key);
        add_to_hash(&mut almond.hash, &[generation]);
        add_to_hash(&mut almond.hash, &almond.almond_type);

        almond
    }

    pub fn parse_and_verify(key: &[u8], input: &[u8]) -> Result<Almond, &'static str> {
        if input.len() < 34 {
            return Err("Too small");
        }

        let hash = &input[..32];
        let generation = input[32];

        let mut split_it = input[33..].split(|c| *c == b'\n');

        let almond_type = try!(split_it.next().ok_or("No initial newline"));

        let mut almond = Almond::create(key, generation, almond_type.to_vec());

        for caveat in split_it {
            almond.add_caveat(caveat.to_vec());
        }

        // Always compare hashes using equality operators that are
        // resistent to timing attacks.
        if MacResult::new(hash) == MacResult::new(almond.hash()) {
            Ok(almond)
        } else {
            Err("Hash do not match")
        }
    }

    pub fn add_caveat(&mut self, caveat: Vec<u8>) -> &mut Self {
        add_to_hash(&mut self.hash, &caveat);
        self.caveats.push(caveat);
        self
    }

    pub fn almond_type(&self) -> &[u8] {
        &self.almond_type
    }

    pub fn generation(&self) -> u8 {
        self.generation
    }

    pub fn caveats(&self) -> &[Vec<u8>] {
        &self.caveats
    }

    pub fn hash(&self) -> &[u8; 32] {
        &self.hash
    }

    pub fn serialize(&self) -> Vec<u8> {
        let mut result : Vec<u8> = Vec::new();

        result.push_all(&self.hash);
        result.push(self.generation);
        result.push_all(&self.almond_type);
        result.push(b'\n');

        for caveat in &self.caveats {
            result.push_all(&caveat);
            result.push(b'\n');
        }

        result.pop();

        result
    }
}

impl base64::ToBase64 for Almond {
    fn to_base64(&self, config: base64::Config) -> String {
        let serialized = self.serialize();
        serialized.to_base64(config)
    }
}

fn add_to_hash(hash: &mut [u8], data: &[u8]) {
    let hasher = Sha256::new();
    let mut mac = Hmac::new(hasher, hash);
    mac.input(data);
    mac.raw_result(hash);
}

struct DeconstructedCaveatEntry<'a> {
    pub key: &'a [u8],
    pub value: Option<&'a [u8]>,
    pub accepted: Option<bool>,
}

pub struct Verifier<'a> {
    caveats: Vec<DeconstructedCaveatEntry<'a>>,
    reject: bool,
}

impl <'a> Verifier<'a> {
    pub fn new(almond: &'a Almond, generation: u8, almond_type: &[u8]) -> Verifier<'a> {
        let caveats = almond.caveats().iter()
            .map(
                |caveat| {
                    let mut it = caveat.splitn(2, |c| *c == b' ');

                    // By definition this must have at least one item
                    let key = it.next().unwrap();
                    let value = it.next();

                    DeconstructedCaveatEntry {
                        key: key,
                        value: value,
                        accepted: None,
                    }
                }
            )
            .collect();

        Verifier {
            caveats: caveats,
            reject: almond.generation() != generation || almond.almond_type() != almond_type
        }
    }

    pub fn allow(&mut self, key: &[u8]) -> &mut Self {
        for item in &mut self.caveats {
            if item.key == key {
                item.accepted = item.accepted.or(Some(true));
            }
        }

        self
    }

    pub fn satisfies<F>(&mut self, key: &[u8], mut predicate: F) -> &mut Self
        where F: FnMut(Option<&[u8]>) -> bool
    {
        for item in &mut self.caveats {
            if item.key == key {
                let res = predicate(item.value);
                item.accepted = Some(res && item.accepted.unwrap_or(true));
            }
        }

        self
    }

    pub fn satisfies_exact(&mut self, key: &[u8], value: Option<&[u8]>) {
        for item in &mut self.caveats {
            if item.key == key {
                let res = item.value.map(|x| &*x) == value;
                item.accepted = Some(res && item.accepted.unwrap_or(true));
            }
        }
    }

    pub fn verify(&self) -> bool {
        !self.reject && self.caveats.iter().all(|item| item.accepted.unwrap_or(false))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use test::Bencher;
    use rustc_serialize::base64::{FromBase64, ToBase64, URL_SAFE};

    #[test]
    fn basic_test() {
        let key = b"this_is_a_secret";

        let mut almond = Almond::create(key, 1, b"login".to_vec());
        almond.add_caveat(b"user erikj".to_vec());
        let r = almond.to_base64(URL_SAFE);
        assert_eq!(
            r,
            "yyTNYc-CAXTVkgXkNnl8wdMzBTMgHyLRSlXrjdf5Uw0BbG9naW4KdXNlciBlcmlrag"
        );

        Almond::parse_and_verify(key, &almond.serialize()).unwrap();
    }

    #[test]
    fn parse_and_serialize() {
        let key = b"this_is_a_secret";

        let input = "yyTNYc-CAXTVkgXkNnl8wdMzBTMgHyLRSlXrjdf5Uw0BbG9naW4KdXNlciBlcmlrag";
        let a = Almond::parse_and_verify(key, &input.from_base64().unwrap()).unwrap();

        assert_eq!(a.to_base64(URL_SAFE), input);
    }

    #[test]
    fn verify_test() {
        let key = b"this_is_a_secret";
        let mut almond = Almond::create(key, 1, b"login".to_vec());
        almond.add_caveat(b"user erikj".to_vec());
        almond.add_caveat(b"guest".to_vec());

        let mut v = Verifier::new(&almond, 1, b"login");
        v.allow(b"user");
        assert!(!v.verify());

        v.satisfies_exact(b"guest", None);
        assert!(v.verify());

        v.satisfies_exact(b"user", Some(b"noterikj"));
        assert!(!v.verify());
    }

    #[test]
    fn verify_test_gen_type() {
        let key = b"this_is_a_secret";
        let mut almond = Almond::create(key, 1, b"login".to_vec());
        almond.add_caveat(b"user erikj".to_vec());

        let mut v = Verifier::new(&almond, 2, b"login");
        v.allow(b"user");
        assert!(!v.verify());

        let mut v = Verifier::new(&almond, 1, b"notlogin");
        v.allow(b"user");
        assert!(!v.verify());
    }

    #[bench]
    fn create(b: &mut Bencher) {
        let key = b"this_is_a_secret";
        b.iter(|| {
            let mut almond = Almond::create(key, 1, b"login".to_vec());
            almond.add_caveat(b"user erikj".to_vec());
            almond.add_caveat(b"fooo bar".to_vec());
            almond.add_caveat(b"testing".to_vec());
            almond.add_caveat(b"teeeeeeeeeeeest".to_vec());
            almond
        });
    }

    #[bench]
    fn parse(b: &mut Bencher) {
        let key = b"this_is_a_secret";

        let b64 = concat!(
            "Rv31sT9t5d31LHPeBFjPewo0TJ1ARbDok7vOWBVNSM4BbG9naW4KdXN",
            "lciBlcmlragpmb29vIGJhcgp0ZXN0aW5nCnRlZWVlZWVlZWVlZWVzdA",
        );
        let parsed = b64.from_base64().unwrap();

        b.iter(|| {
            let almond = Almond::parse_and_verify(
                key,
                &parsed,
            );
            almond.unwrap();
        });
    }
}
<|endoftext|>