<commit_before>//! Types for the *m.room.member* event.

use std::collections::HashMap;

use ruma_events_macros::ruma_event;
use ruma_identifiers::UserId;
use serde::{Deserialize, Serialize};

ruma_event! {
    /// The current membership state of a user in the room.
    ///
    /// Adjusts the membership state for a user in a room. It is preferable to use the membership
    /// APIs (`/rooms/<room id>/invite` etc) when performing membership actions rather than
    /// adjusting the state directly as there are a restricted set of valid transformations. For
    /// example, user A cannot force user B to join a room, and trying to force this state change
    /// directly will fail.
    ///
    /// The `third_party_invite` property will be set if this invite is an *invite* event and is the
    /// successor of an *m.room.third_party_invite* event, and absent otherwise.
    ///
    /// This event may also include an `invite_room_state` key inside the event's unsigned data. If
    /// present, this contains an array of `StrippedState` events. These events provide information
    /// on a subset of state events such as the room name. Note that ruma-events treats unsigned
    /// data on events as arbitrary JSON values, and the ruma-events types for this event don't
    /// provide direct access to `invite_room_state`. If you need this data, you must extract and
    /// convert it from a `serde_json::Value` yourself.
    ///
    /// The user for which a membership applies is represented by the `state_key`. Under some
    /// conditions, the `sender` and `state_key` may not match - this may be interpreted as the
    /// `sender` affecting the membership state of the `state_key` user.
    ///
    /// The membership for a given user can change over time. Previous membership can be retrieved
    /// from the `prev_content` object on an event. If not present, the user's previous membership
    /// must be assumed as leave.
    MemberEvent {
        kind: StateEvent,
        event_type: "m.room.member",
        content: {
            /// The avatar URL for this user, if any. This is added by the homeserver.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub avatar_url: Option<String>,

            /// The display name for this user, if any. This is added by the homeserver.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub displayname: Option<String>,

            /// Flag indicating if the room containing this event was created
            /// with the intention of being a direct chat.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub is_direct: Option<bool>,

            /// The membership state of this user.
            pub membership: MembershipState,

            /// If this member event is the successor to a third party invitation, this field will
            /// contain information about that invitation.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub third_party_invite: Option<ThirdPartyInvite>,
        },
    }
}

/// The membership state of a user.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum MembershipState {
    /// The user is banned.
    #[serde(rename = "ban")]
    Ban,

    /// The user has been invited.
    #[serde(rename = "invite")]
    Invite,

    /// The user has joined.
    #[serde(rename = "join")]
    Join,

    /// The user has requested to join.
    #[serde(rename = "knock")]
    Knock,

    /// The user has left.
    #[serde(rename = "leave")]
    Leave,

    /// Additional variants may be added in the future and will not be considered breaking changes
    /// to ruma-events.
    #[doc(hidden)]
    #[serde(skip)]
    __Nonexhaustive,
}

impl_enum! {
    MembershipState {
        Ban => "ban",
        Invite => "invite",
        Join => "join",
        Knock => "knock",
        Leave => "leave",
    }
}

/// Information about a third party invitation.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ThirdPartyInvite {
    /// A name which can be displayed to represent the user instead of their third party
    /// identifier.
    pub display_name: String,

    /// A block of content which has been signed, which servers can use to verify the event.
    /// Clients should ignore this.
    pub signed: SignedContent,
}

/// A block of content which has been signed, which servers can use to verify a third party
/// invitation.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct SignedContent {
    /// The invited Matrix user ID.
    ///
    /// Must be equal to the user_id property of the event.
    pub mxid: UserId,

    /// A single signature from the verifying server, in the format specified by the Signing Events
    /// section of the server-server API.
    pub signatures: HashMap<String, HashMap<String, String>>,

    /// The token property of the containing third_party_invite object.
    pub token: String,
}

/// Translation of the membership change in `m.room.member` event.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum MembershipChange {
    /// No change.
    None,

    /// Must never happen.
    Error,

    /// User joined the room.
    Joined,

    /// User left the room.
    Left,

    /// User was banned.
    Banned,

    /// User was unbanned.
    Unbanned,

    /// User was kicked.
    Kicked,

    /// User was invited.
    Invited,

    /// User was kicked and banned.
    KickedAndBanned,

    /// User rejected the invite.
    InvitationRejected,

    /// User had their invite revoked.
    InvitationRevoked,

    /// `displayname` or `avatar_url` changed.
    ProfileChanged,

    /// Not implemented.
    NotImplemented,

    /// Additional variants may be added in the future and will not be considered breaking changes
    /// to ruma-events.
    #[doc(hidden)]
    __Nonexhaustive,
}

impl MemberEvent {
    /// Helper function for membership change. Check [the specification][spec] for details.
    ///
    /// [spec]: https://matrix.org/docs/spec/client_server/latest#m-room-member
    pub fn membership_change(&self) -> MembershipChange {
        use MembershipState::*;
        let prev_membership = if let Some(prev_content) = &self.prev_content {
            prev_content.membership
        } else {
            Leave
        };
        match (prev_membership, &self.content.membership) {
            (Invite, Invite) | (Leave, Leave) | (Ban, Ban) => MembershipChange::None,
            (Invite, Join) | (Leave, Join) => MembershipChange::Joined,
            (Invite, Leave) => {
                if self.sender.to_string() == self.state_key {
                    MembershipChange::InvitationRevoked
                } else {
                    MembershipChange::InvitationRejected
                }
            }
            (Invite, Ban) | (Leave, Ban) => MembershipChange::Banned,
            (Join, Invite) | (Ban, Invite) | (Ban, Join) => MembershipChange::Error,
            (Join, Join) => MembershipChange::ProfileChanged,
            (Join, Leave) => {
                if self.sender.to_string() == self.state_key {
                    MembershipChange::Left
                } else {
                    MembershipChange::Kicked
                }
            }
            (Join, Ban) => MembershipChange::KickedAndBanned,
            (Leave, Invite) => MembershipChange::Invited,
            (Ban, Leave) => MembershipChange::Unbanned,
            (Knock, _) | (_, Knock) => MembershipChange::NotImplemented,
            (__Nonexhaustive, _) | (_, __Nonexhaustive) => {
                panic!("__Nonexhaustive enum variant is not intended for use.")
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::convert::TryFrom;

    use js_int::UInt;
    use ruma_identifiers::{EventId, RoomId, UserId};
    use serde_json::{json, Map};

    use super::*;
    use crate::util::serde_json_eq_try_from_raw;

    #[test]
    fn serde_with_no_prev_content() {
        let event = MemberEvent {
            content: MemberEventContent {
                avatar_url: None,
                displayname: None,
                is_direct: None,
                membership: MembershipState::Join,
                third_party_invite: None,
            },
            event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
            origin_server_ts: UInt::new(1).unwrap(),
            room_id: Some(RoomId::try_from("!n8f893n9:example.com").unwrap()),
            sender: UserId::try_from("@carl:example.com").unwrap(),
            state_key: "example.com".to_string(),
            unsigned: Map::new(),
            prev_content: None,
        };
        let json = json!({
            "type": "m.room.member",
            "content": {
                "membership": "join"
            },
            "event_id": "$h29iv0s8:example.com",
            "origin_server_ts": 1,
            "room_id": "!n8f893n9:example.com",
            "sender": "@carl:example.com",
            "state_key": "example.com"
        });
        serde_json_eq_try_from_raw(event, json);
    }

    #[test]
    fn serde_with_prev_content() {
        let event = MemberEvent {
            content: MemberEventContent {
                avatar_url: None,
                displayname: None,
                is_direct: None,
                membership: MembershipState::Join,
                third_party_invite: None,
            },
            event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
            origin_server_ts: UInt::new(1).unwrap(),
            room_id: Some(RoomId::try_from("!n8f893n9:example.com").unwrap()),
            sender: UserId::try_from("@carl:example.com").unwrap(),
            state_key: "example.com".to_string(),
            unsigned: Map::new(),
            prev_content: Some(MemberEventContent {
                avatar_url: None,
                displayname: None,
                is_direct: None,
                membership: MembershipState::Join,
                third_party_invite: None,
            }),
        };
        let json = json!({
            "type": "m.room.member",
            "content": {
                "membership": "join"
            },
            "event_id": "$h29iv0s8:example.com",
            "origin_server_ts": 1,
            "prev_content": {
                "membership": "join"
            },
            "room_id": "!n8f893n9:example.com",
            "sender": "@carl:example.com",
            "state_key": "example.com"
        });
        serde_json_eq_try_from_raw(event, json);
    }

    #[test]
    fn serde_with_content_full() {
        let signatures = vec![(
            "magic.forest".to_owned(),
            vec![("ed25519:3".to_owned(), "foobar".to_owned())]
                .into_iter()
                .collect(),
        )]
        .into_iter()
        .collect();
        let event = MemberEvent {
            content: MemberEventContent {
                avatar_url: Some("mxc://example.org/SEsfnsuifSDFSSEF".to_owned()),
                displayname: Some("Alice Margatroid".to_owned()),
                is_direct: Some(true),
                membership: MembershipState::Invite,
                third_party_invite: Some(ThirdPartyInvite {
                    display_name: "alice".to_owned(),
                    signed: SignedContent {
                        mxid: UserId::try_from("@alice:example.org").unwrap(),
                        signatures,
                        token: "abc123".to_owned(),
                    },
                }),
            },
            event_id: EventId::try_from("$143273582443PhrSn:example.org").unwrap(),
            origin_server_ts: UInt::new(233).unwrap(),
            room_id: Some(RoomId::try_from("!jEsUZKDJdhlrceRyVU:example.org").unwrap()),
            sender: UserId::try_from("@alice:example.org").unwrap(),
            state_key: "@alice:example.org".to_string(),
            unsigned: Map::new(),
            prev_content: None,
        };
        let json = json!({
            "type": "m.room.member",
            "content": {
                "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
                "displayname": "Alice Margatroid",
                "is_direct": true,
                "membership": "invite",
                "third_party_invite": {
                    "display_name": "alice",
                    "signed": {
                        "mxid": "@alice:example.org",
                        "signatures": {
                            "magic.forest": {
                                "ed25519:3": "foobar"
                            }
                        },
                        "token": "abc123"
                    }
                }
            },
            "event_id": "$143273582443PhrSn:example.org",
            "origin_server_ts": 233,
            "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
            "sender": "@alice:example.org",
            "state_key": "@alice:example.org"
        });
        serde_json_eq_try_from_raw(event, json);
    }

    #[test]
    fn serde_with_prev_content_full() {
        let signatures = vec![(
            "magic.forest".to_owned(),
            vec![("ed25519:3".to_owned(), "foobar".to_owned())]
                .into_iter()
                .collect(),
        )]
        .into_iter()
        .collect();
        let event = MemberEvent {
            content: MemberEventContent {
                avatar_url: None,
                displayname: None,
                is_direct: None,
                membership: MembershipState::Join,
                third_party_invite: None,
            },
            event_id: EventId::try_from("$143273582443PhrSn:example.org").unwrap(),
            origin_server_ts: UInt::new(233).unwrap(),
            room_id: Some(RoomId::try_from("!jEsUZKDJdhlrceRyVU:example.org").unwrap()),
            sender: UserId::try_from("@alice:example.org").unwrap(),
            state_key: "@alice:example.org".to_string(),
            unsigned: Map::new(),
            prev_content: Some(MemberEventContent {
                avatar_url: Some("mxc://example.org/SEsfnsuifSDFSSEF".to_owned()),
                displayname: Some("Alice Margatroid".to_owned()),
                is_direct: Some(true),
                membership: MembershipState::Invite,
                third_party_invite: Some(ThirdPartyInvite {
                    display_name: "alice".to_owned(),
                    signed: SignedContent {
                        mxid: UserId::try_from("@alice:example.org").unwrap(),
                        signatures,
                        token: "abc123".to_owned(),
                    },
                }),
            }),
        };
        let json = json!({
            "type": "m.room.member",
            "content": {
                "membership": "join"
            },
            "event_id": "$143273582443PhrSn:example.org",
            "origin_server_ts": 233,
            "prev_content": {
                "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
                "displayname": "Alice Margatroid",
                "is_direct": true,
                "membership": "invite",
                "third_party_invite": {
                    "display_name": "alice",
                    "signed": {
                        "mxid": "@alice:example.org",
                        "signatures": {
                            "magic.forest": {
                                "ed25519:3": "foobar"
                            }
                        },
                        "token": "abc123"
                    }
                }
            },
            "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
            "sender": "@alice:example.org",
            "state_key": "@alice:example.org"
        });
        serde_json_eq_try_from_raw(event, json);
    }
}
<commit_msg>Remove needless clone<commit_after>//! Types for the *m.room.member* event.

use std::collections::HashMap;

use ruma_events_macros::ruma_event;
use ruma_identifiers::UserId;
use serde::{Deserialize, Serialize};

ruma_event! {
    /// The current membership state of a user in the room.
    ///
    /// Adjusts the membership state for a user in a room. It is preferable to use the membership
    /// APIs (`/rooms/<room id>/invite` etc) when performing membership actions rather than
    /// adjusting the state directly as there are a restricted set of valid transformations. For
    /// example, user A cannot force user B to join a room, and trying to force this state change
    /// directly will fail.
    ///
    /// The `third_party_invite` property will be set if this invite is an *invite* event and is the
    /// successor of an *m.room.third_party_invite* event, and absent otherwise.
    ///
    /// This event may also include an `invite_room_state` key inside the event's unsigned data. If
    /// present, this contains an array of `StrippedState` events. These events provide information
    /// on a subset of state events such as the room name. Note that ruma-events treats unsigned
    /// data on events as arbitrary JSON values, and the ruma-events types for this event don't
    /// provide direct access to `invite_room_state`. If you need this data, you must extract and
    /// convert it from a `serde_json::Value` yourself.
    ///
    /// The user for which a membership applies is represented by the `state_key`. Under some
    /// conditions, the `sender` and `state_key` may not match - this may be interpreted as the
    /// `sender` affecting the membership state of the `state_key` user.
    ///
    /// The membership for a given user can change over time. Previous membership can be retrieved
    /// from the `prev_content` object on an event. If not present, the user's previous membership
    /// must be assumed as leave.
    MemberEvent {
        kind: StateEvent,
        event_type: "m.room.member",
        content: {
            /// The avatar URL for this user, if any. This is added by the homeserver.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub avatar_url: Option<String>,

            /// The display name for this user, if any. This is added by the homeserver.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub displayname: Option<String>,

            /// Flag indicating if the room containing this event was created
            /// with the intention of being a direct chat.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub is_direct: Option<bool>,

            /// The membership state of this user.
            pub membership: MembershipState,

            /// If this member event is the successor to a third party invitation, this field will
            /// contain information about that invitation.
            #[serde(skip_serializing_if = "Option::is_none")]
            pub third_party_invite: Option<ThirdPartyInvite>,
        },
    }
}

/// The membership state of a user.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum MembershipState {
    /// The user is banned.
    #[serde(rename = "ban")]
    Ban,

    /// The user has been invited.
    #[serde(rename = "invite")]
    Invite,

    /// The user has joined.
    #[serde(rename = "join")]
    Join,

    /// The user has requested to join.
    #[serde(rename = "knock")]
    Knock,

    /// The user has left.
    #[serde(rename = "leave")]
    Leave,

    /// Additional variants may be added in the future and will not be considered breaking changes
    /// to ruma-events.
    #[doc(hidden)]
    #[serde(skip)]
    __Nonexhaustive,
}

impl_enum! {
    MembershipState {
        Ban => "ban",
        Invite => "invite",
        Join => "join",
        Knock => "knock",
        Leave => "leave",
    }
}

/// Information about a third party invitation.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ThirdPartyInvite {
    /// A name which can be displayed to represent the user instead of their third party
    /// identifier.
    pub display_name: String,

    /// A block of content which has been signed, which servers can use to verify the event.
    /// Clients should ignore this.
    pub signed: SignedContent,
}

/// A block of content which has been signed, which servers can use to verify a third party
/// invitation.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct SignedContent {
    /// The invited Matrix user ID.
    ///
    /// Must be equal to the user_id property of the event.
    pub mxid: UserId,

    /// A single signature from the verifying server, in the format specified by the Signing Events
    /// section of the server-server API.
    pub signatures: HashMap<String, HashMap<String, String>>,

    /// The token property of the containing third_party_invite object.
    pub token: String,
}

/// Translation of the membership change in `m.room.member` event.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum MembershipChange {
    /// No change.
    None,

    /// Must never happen.
    Error,

    /// User joined the room.
    Joined,

    /// User left the room.
    Left,

    /// User was banned.
    Banned,

    /// User was unbanned.
    Unbanned,

    /// User was kicked.
    Kicked,

    /// User was invited.
    Invited,

    /// User was kicked and banned.
    KickedAndBanned,

    /// User rejected the invite.
    InvitationRejected,

    /// User had their invite revoked.
    InvitationRevoked,

    /// `displayname` or `avatar_url` changed.
    ProfileChanged,

    /// Not implemented.
    NotImplemented,

    /// Additional variants may be added in the future and will not be considered breaking changes
    /// to ruma-events.
    #[doc(hidden)]
    __Nonexhaustive,
}

impl MemberEvent {
    /// Helper function for membership change. Check [the specification][spec] for details.
    ///
    /// [spec]: https://matrix.org/docs/spec/client_server/latest#m-room-member
    pub fn membership_change(&self) -> MembershipChange {
        use MembershipState::*;
        let prev_membership = if let Some(prev_content) = &self.prev_content {
            prev_content.membership
        } else {
            Leave
        };
        match (prev_membership, &self.content.membership) {
            (Invite, Invite) | (Leave, Leave) | (Ban, Ban) => MembershipChange::None,
            (Invite, Join) | (Leave, Join) => MembershipChange::Joined,
            (Invite, Leave) => {
                if self.sender == self.state_key {
                    MembershipChange::InvitationRevoked
                } else {
                    MembershipChange::InvitationRejected
                }
            }
            (Invite, Ban) | (Leave, Ban) => MembershipChange::Banned,
            (Join, Invite) | (Ban, Invite) | (Ban, Join) => MembershipChange::Error,
            (Join, Join) => MembershipChange::ProfileChanged,
            (Join, Leave) => {
                if self.sender == self.state_key {
                    MembershipChange::Left
                } else {
                    MembershipChange::Kicked
                }
            }
            (Join, Ban) => MembershipChange::KickedAndBanned,
            (Leave, Invite) => MembershipChange::Invited,
            (Ban, Leave) => MembershipChange::Unbanned,
            (Knock, _) | (_, Knock) => MembershipChange::NotImplemented,
            (__Nonexhaustive, _) | (_, __Nonexhaustive) => {
                panic!("__Nonexhaustive enum variant is not intended for use.")
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::convert::TryFrom;

    use js_int::UInt;
    use ruma_identifiers::{EventId, RoomId, UserId};
    use serde_json::{json, Map};

    use super::*;
    use crate::util::serde_json_eq_try_from_raw;

    #[test]
    fn serde_with_no_prev_content() {
        let event = MemberEvent {
            content: MemberEventContent {
                avatar_url: None,
                displayname: None,
                is_direct: None,
                membership: MembershipState::Join,
                third_party_invite: None,
            },
            event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
            origin_server_ts: UInt::new(1).unwrap(),
            room_id: Some(RoomId::try_from("!n8f893n9:example.com").unwrap()),
            sender: UserId::try_from("@carl:example.com").unwrap(),
            state_key: "example.com".to_string(),
            unsigned: Map::new(),
            prev_content: None,
        };
        let json = json!({
            "type": "m.room.member",
            "content": {
                "membership": "join"
            },
            "event_id": "$h29iv0s8:example.com",
            "origin_server_ts": 1,
            "room_id": "!n8f893n9:example.com",
            "sender": "@carl:example.com",
            "state_key": "example.com"
        });
        serde_json_eq_try_from_raw(event, json);
    }

    #[test]
    fn serde_with_prev_content() {
        let event = MemberEvent {
            content: MemberEventContent {
                avatar_url: None,
                displayname: None,
                is_direct: None,
                membership: MembershipState::Join,
                third_party_invite: None,
            },
            event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
            origin_server_ts: UInt::new(1).unwrap(),
            room_id: Some(RoomId::try_from("!n8f893n9:example.com").unwrap()),
            sender: UserId::try_from("@carl:example.com").unwrap(),
            state_key: "example.com".to_string(),
            unsigned: Map::new(),
            prev_content: Some(MemberEventContent {
                avatar_url: None,
                displayname: None,
                is_direct: None,
                membership: MembershipState::Join,
                third_party_invite: None,
            }),
        };
        let json = json!({
            "type": "m.room.member",
            "content": {
                "membership": "join"
            },
            "event_id": "$h29iv0s8:example.com",
            "origin_server_ts": 1,
            "prev_content": {
                "membership": "join"
            },
            "room_id": "!n8f893n9:example.com",
            "sender": "@carl:example.com",
            "state_key": "example.com"
        });
        serde_json_eq_try_from_raw(event, json);
    }

    #[test]
    fn serde_with_content_full() {
        let signatures = vec![(
            "magic.forest".to_owned(),
            vec![("ed25519:3".to_owned(), "foobar".to_owned())]
                .into_iter()
                .collect(),
        )]
        .into_iter()
        .collect();
        let event = MemberEvent {
            content: MemberEventContent {
                avatar_url: Some("mxc://example.org/SEsfnsuifSDFSSEF".to_owned()),
                displayname: Some("Alice Margatroid".to_owned()),
                is_direct: Some(true),
                membership: MembershipState::Invite,
                third_party_invite: Some(ThirdPartyInvite {
                    display_name: "alice".to_owned(),
                    signed: SignedContent {
                        mxid: UserId::try_from("@alice:example.org").unwrap(),
                        signatures,
                        token: "abc123".to_owned(),
                    },
                }),
            },
            event_id: EventId::try_from("$143273582443PhrSn:example.org").unwrap(),
            origin_server_ts: UInt::new(233).unwrap(),
            room_id: Some(RoomId::try_from("!jEsUZKDJdhlrceRyVU:example.org").unwrap()),
            sender: UserId::try_from("@alice:example.org").unwrap(),
            state_key: "@alice:example.org".to_string(),
            unsigned: Map::new(),
            prev_content: None,
        };
        let json = json!({
            "type": "m.room.member",
            "content": {
                "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
                "displayname": "Alice Margatroid",
                "is_direct": true,
                "membership": "invite",
                "third_party_invite": {
                    "display_name": "alice",
                    "signed": {
                        "mxid": "@alice:example.org",
                        "signatures": {
                            "magic.forest": {
                                "ed25519:3": "foobar"
                            }
                        },
                        "token": "abc123"
                    }
                }
            },
            "event_id": "$143273582443PhrSn:example.org",
            "origin_server_ts": 233,
            "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
            "sender": "@alice:example.org",
            "state_key": "@alice:example.org"
        });
        serde_json_eq_try_from_raw(event, json);
    }

    #[test]
    fn serde_with_prev_content_full() {
        let signatures = vec![(
            "magic.forest".to_owned(),
            vec![("ed25519:3".to_owned(), "foobar".to_owned())]
                .into_iter()
                .collect(),
        )]
        .into_iter()
        .collect();
        let event = MemberEvent {
            content: MemberEventContent {
                avatar_url: None,
                displayname: None,
                is_direct: None,
                membership: MembershipState::Join,
                third_party_invite: None,
            },
            event_id: EventId::try_from("$143273582443PhrSn:example.org").unwrap(),
            origin_server_ts: UInt::new(233).unwrap(),
            room_id: Some(RoomId::try_from("!jEsUZKDJdhlrceRyVU:example.org").unwrap()),
            sender: UserId::try_from("@alice:example.org").unwrap(),
            state_key: "@alice:example.org".to_string(),
            unsigned: Map::new(),
            prev_content: Some(MemberEventContent {
                avatar_url: Some("mxc://example.org/SEsfnsuifSDFSSEF".to_owned()),
                displayname: Some("Alice Margatroid".to_owned()),
                is_direct: Some(true),
                membership: MembershipState::Invite,
                third_party_invite: Some(ThirdPartyInvite {
                    display_name: "alice".to_owned(),
                    signed: SignedContent {
                        mxid: UserId::try_from("@alice:example.org").unwrap(),
                        signatures,
                        token: "abc123".to_owned(),
                    },
                }),
            }),
        };
        let json = json!({
            "type": "m.room.member",
            "content": {
                "membership": "join"
            },
            "event_id": "$143273582443PhrSn:example.org",
            "origin_server_ts": 233,
            "prev_content": {
                "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
                "displayname": "Alice Margatroid",
                "is_direct": true,
                "membership": "invite",
                "third_party_invite": {
                    "display_name": "alice",
                    "signed": {
                        "mxid": "@alice:example.org",
                        "signatures": {
                            "magic.forest": {
                                "ed25519:3": "foobar"
                            }
                        },
                        "token": "abc123"
                    }
                }
            },
            "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
            "sender": "@alice:example.org",
            "state_key": "@alice:example.org"
        });
        serde_json_eq_try_from_raw(event, json);
    }
}
<|endoftext|>


<commit_before>extern crate num_cpus;
pub mod filter;
pub mod collection;

#[cfg(test)]
mod tests {
    use filter::{
        prime_filter,
        prime_filter_section,
        old_prime_filter,
    };
    extern crate time;
    extern crate num_cpus;
    use self::time::PreciseTime;
    #[test]
    fn test_prime_filter(){
        let n = 12345;
        let new_pf = prime_filter(n);
        let old_pf = old_prime_filter(n);
        for i in 0..n{
            assert_eq!(new_pf[i], old_pf[i], "Failed on number {}", i)
        }
    }
    use collection::{
        primes,
        primes_section,
        primes_concurrently,
        // primes_section_sequentially,
    };
    #[test]
    fn test_prime_collection(){
        let some_primes = [ 2, 3, 5, 7, 11, 13,
                            17, 19, 23, 29, 31,
                            37, 41, 43, 47, 53,
                            59, 61, 67, 71, 73,
                            79, 83, 89, 97, 101,
                            103, 107, 109, 113, 127,
                            131, 137, 139, 149, 151,
                            157, 163, 167, 173, 179,
                            181, 191, 193, 197, 199];

        let test_primes: Vec<usize> = primes(200);
        for i in 0..test_primes.len(){
            assert_eq!(test_primes[i], some_primes[i], "Mismatch")
        };
    }
    #[test]
    fn test_prime_sec_collection(){
        let some_primes = [ 101,
                            103, 107, 109, 113, 127,
                            131, 137, 139, 149, 151,
                            157, 163, 167, 173, 179,
                            181, 191, 193, 197, 199];

        let test_primes: Vec<usize> = primes_section(100, 200);
        for i in 0..test_primes.len(){
            assert_eq!(test_primes[i], some_primes[i], "Mismatch")
        };
    }
    #[test]
    fn can_take_any_section_filter(){
        let some_primes = old_prime_filter(200);
        for min in 0..200{
            for max in (min+1)..200{
                for (i, is_prime) in prime_filter_section(min, max).into_iter().enumerate(){
                    assert_eq!(some_primes[min+i], is_prime, "bad case from {} to {}", min, max);
                }
            }
        }
    }
    #[test]
    fn can_take_any_section(){
        let some_primes = old_prime_filter(200);
        for min in 0..200{
            for max in (min+1)..200{
                for prime in primes_section(min, max).into_iter(){
                    assert!(some_primes[prime], "bad case from {} to {}: prime: {}", min, max, prime);
                }
            }
        }
    }
    #[test]
    fn count_primes(){
        let n = 100_000_000;
        let start = PreciseTime::now();
        let primes = primes(n);
        let end = PreciseTime::now();
        println!("{} seconds to find all primes less than {}!", start.to(end), n);
        println!("{} primes total!", primes.len());
        let total_primes = primes.len();
        assert_eq!(total_primes, 5761455);
    }
    #[test]
    fn count_primes_concurrently(){
        let threads = num_cpus::get()*4;
        let n = 1_000_000_000;
        let start = PreciseTime::now();
        let primes = primes_concurrently(n, threads);
        let end = PreciseTime::now();
        println!("{} seconds to find all primes less than {}! (Threads: {})", start.to(end), n, threads);
        println!("{} primes total!", primes.len());
        let total_primes = primes.len();
        assert_eq!(total_primes, 50_847_534);
    }
    #[test]
    fn count_prime_section(){
        let min = 1_000_000;
        let max = 10_000_000;
        let primes = primes_section(min, max);
        let total_primes = primes.len();
        assert_eq!(total_primes, 586081);
    }

}
<commit_msg>Shorten time for test....<commit_after>extern crate num_cpus;
pub mod filter;
pub mod collection;

#[cfg(test)]
mod tests {
    use filter::{
        prime_filter,
        prime_filter_section,
        old_prime_filter,
    };
    extern crate time;
    extern crate num_cpus;
    use self::time::PreciseTime;
    #[test]
    fn test_prime_filter(){
        let n = 12345;
        let new_pf = prime_filter(n);
        let old_pf = old_prime_filter(n);
        for i in 0..n{
            assert_eq!(new_pf[i], old_pf[i], "Failed on number {}", i)
        }
    }
    use collection::{
        primes,
        primes_section,
        primes_concurrently,
        // primes_section_sequentially,
    };
    #[test]
    fn test_prime_collection(){
        let some_primes = [ 2, 3, 5, 7, 11, 13,
                            17, 19, 23, 29, 31,
                            37, 41, 43, 47, 53,
                            59, 61, 67, 71, 73,
                            79, 83, 89, 97, 101,
                            103, 107, 109, 113, 127,
                            131, 137, 139, 149, 151,
                            157, 163, 167, 173, 179,
                            181, 191, 193, 197, 199];

        let test_primes: Vec<usize> = primes(200);
        for i in 0..test_primes.len(){
            assert_eq!(test_primes[i], some_primes[i], "Mismatch")
        };
    }
    #[test]
    fn test_prime_sec_collection(){
        let some_primes = [ 101,
                            103, 107, 109, 113, 127,
                            131, 137, 139, 149, 151,
                            157, 163, 167, 173, 179,
                            181, 191, 193, 197, 199];

        let test_primes: Vec<usize> = primes_section(100, 200);
        for i in 0..test_primes.len(){
            assert_eq!(test_primes[i], some_primes[i], "Mismatch")
        };
    }
    #[test]
    fn can_take_any_section_filter(){
        let some_primes = old_prime_filter(200);
        for min in 120..200{
            for max in (min+1)..200{
                for (i, is_prime) in prime_filter_section(min, max).into_iter().enumerate(){
                    assert_eq!(some_primes[min+i], is_prime, "bad case from {} to {}", min, max);
                }
            }
        }
    }
    #[test]
    fn can_take_any_section(){
        let some_primes = old_prime_filter(200);
        for min in 120..200{
            for max in (min+1)..200{
                for prime in primes_section(min, max).into_iter(){
                    assert!(some_primes[prime], "bad case from {} to {}: prime: {}", min, max, prime);
                }
            }
        }
    }
    #[test]
    fn count_primes(){
        let n = 100_000_000;
        let start = PreciseTime::now();
        let primes = primes(n);
        let end = PreciseTime::now();
        println!("{} seconds to find all primes less than {}!", start.to(end), n);
        println!("{} primes total!", primes.len());
        let total_primes = primes.len();
        assert_eq!(total_primes, 5761455);
    }
    #[test]
    fn count_primes_concurrently(){
        let threads = num_cpus::get()*4;
        let n = 1_000_000_000;
        let start = PreciseTime::now();
        let primes = primes_concurrently(n, threads);
        let end = PreciseTime::now();
        println!("{} seconds to find all primes less than {}! (Threads: {})", start.to(end), n, threads);
        println!("{} primes total!", primes.len());
        let total_primes = primes.len();
        assert_eq!(total_primes, 50_847_534);
    }
    #[test]
    fn count_prime_section(){
        let min = 1_000_000;
        let max = 10_000_000;
        let primes = primes_section(min, max);
        let total_primes = primes.len();
        assert_eq!(total_primes, 586081);
    }

}
<|endoftext|>


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

//! Library for retrieving and interacting with the [crates.io index].
//!
//! [crates.io index]: https://github.com/rust-lang/crates.io-index
//!
//! ## Examples
//!
//! ```rust
//! extern crate crates_index;
//!
//! let index = crates_index::Index::new("_index".into());
//! if !index.exists() {
//!    index.retrieve().expect("Could not fetch crates.io index");
//! }
//! for crate_ in index.crates() {
//!    let latest_version = crate_.latest_version();
//!    println!("crate name: {}", latest_version.name());
//!    println!("crate version: {}", latest_version.version());
//! }
//! ```

use std::ascii::AsciiExt;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
use std::io::{BufRead, BufReader};
use std::iter;
use std::path::{Path, PathBuf};

extern crate git2;
extern crate glob;
extern crate rustc_serialize;

static INDEX_GIT_URL: &'static str = "https://github.com/rust-lang/crates.io-index";

/// A single version of a crate published to the index
#[derive(RustcDecodable, Clone)]
pub struct Version {
    name: String,
    vers: String,
    deps: Vec<Dependency>,
    cksum: String,
    features: HashMap<String, Vec<String>>,
    yanked: bool,
}

impl Version {
    /// Name of the crate
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Name of this version
    pub fn version(&self) -> &str {
        &self.vers
    }

    /// Dependencies for this version
    pub fn dependencies(&self) -> &[Dependency] {
        &self.deps
    }

    /// Checksum of the package for this version
    pub fn checksum(&self) -> &str {
        &self.cksum
    }

    pub fn features(&self) -> &HashMap<String, Vec<String>> {
        &self.features
    }

    /// Whether this version was [yanked](http://doc.crates.io/crates-io.html#cargo-yank) from the
    /// index
    pub fn is_yanked(&self) -> bool {
        self.yanked
    }
}

/// A single dependency of a specific crate version
#[derive(RustcDecodable, Clone)]
pub struct Dependency {
    name: String,
    req: String,
    features: Vec<String>,
    optional: bool,
    default_features: bool,
    target: Option<String>,
    kind: Option<String>,
}

impl Dependency {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn requirement(&self) -> &str {
        &self.req
    }

    pub fn features(&self) -> &[String] {
        &self.features
    }

    pub fn is_optional(&self) -> bool {
        self.optional
    }

    pub fn has_default_features(&self) -> bool {
        self.default_features
    }

    pub fn target(&self) -> Option<&str> {
        match self.target {
            Some(ref s) => Some(s),
            None => None,
        }
    }

    pub fn kind(&self) -> Option<&str> {
        match self.kind {
            Some(ref s) => Some(s),
            None => None,
        }
    }
}

/// Constructed from `Index::crates`
pub struct Crates(CrateIndexPaths);

impl Iterator for Crates {
    type Item = Crate;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|p| Crate::new(&p))
    }
}

/// Constructed from `Index::crate_index_paths`
pub struct CrateIndexPaths(iter::Chain<glob::Paths, glob::Paths>);

impl CrateIndexPaths {
    fn new<P: AsRef<Path>>(path: P) -> CrateIndexPaths {
        let mut match_options = glob::MatchOptions::new();
        match_options.require_literal_leading_dot = true;
        let path = path.as_ref();

        let glob_pattern = format!("{}/*/*/*", path.to_str().unwrap());
        let index_paths1 = glob::glob_with(&glob_pattern, &match_options).unwrap();

        let glob_pattern = format!("{}/[12]/*", path.to_str().unwrap());
        let index_paths2 = glob::glob_with(&glob_pattern, &match_options).unwrap();

        CrateIndexPaths(index_paths1.chain(index_paths2))
    }
}

impl Iterator for CrateIndexPaths {
    type Item = PathBuf;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|glob_result| glob_result.unwrap())
    }
}

/// Wrapper around managing the crates.io-index git repository
pub struct Index {
    path: PathBuf,
}

impl Index {
    /// Construct a new Index supplying a path where the index lives or should live
    pub fn new(path: PathBuf) -> Index {
        Index { path: path }
    }

    /// Determines if a crates.io repository exists at `self.path`
    pub fn exists(&self) -> bool {
        git2::Repository::discover(&self.path)
            .map(|repository| {
                repository
                    .find_remote("origin")
                    .map(|remote| remote.url() == Some(INDEX_GIT_URL))
                    .unwrap_or(false)
            })
            .unwrap_or(false)
    }

    /// Downloads the index to the path specified from the constructor
    pub fn retrieve(&self) -> Result<(), git2::Error> {
        let _ = try!(git2::Repository::clone(INDEX_GIT_URL, &self.path));
        Ok(())
    }

    /// Retrieve a single crate by name (case insensitive) from the index
    pub fn crate_(&self, crate_name: &str) -> Option<Crate> {
        self.crate_index_paths()
            .find(|path| {
                      path.file_name()
                          .and_then(OsStr::to_str)
                          .map(|file_name| file_name.eq_ignore_ascii_case(crate_name))
                          .unwrap_or(false)
                  })
            .map(|p| Crate::new(&p))
    }

    /// Retrieve an iterator over all the crates in the index
    pub fn crates(&self) -> Crates {
        Crates(self.crate_index_paths())
    }

    /// Returns all the crate index file paths in the index
    pub fn crate_index_paths(&self) -> CrateIndexPaths {
        CrateIndexPaths::new(self.path.clone()) // TODO: remove this clone
    }
}

/// A single crate that contains many published versions
pub struct Crate {
    versions: Vec<Version>,
}

impl Crate {
    pub fn new<P: AsRef<Path>>(index_path: P) -> Crate {
        let index_path = index_path.as_ref();
        let mut versions = vec![];
        let file = fs::File::open(&index_path).unwrap();
        for line in BufReader::new(file).lines() {
            let version: Version = rustc_serialize::json::decode(&line.unwrap()).unwrap();
            versions.push(version);
        }
        Crate { versions: versions }
    }

    /// Published versions of this crate sorted chronologically by date published
    pub fn versions(&self) -> &[Version] {
        &self.versions
    }

    pub fn earliest_version(&self) -> &Version {
        &self.versions[0]
    }

    pub fn latest_version(&self) -> &Version {
        &self.versions[self.versions.len() - 1]
    }
}

#[cfg(test)]
mod test {
    use std::fs;
    use super::Index;

    static TEST_INDEX_DIR: &'static str = "_test";

    #[test]
    fn test_dependencies() {
        let _ = fs::remove_dir_all(TEST_INDEX_DIR);

        let index = Index::new(TEST_INDEX_DIR.into());
        let crate_ = index.crates().nth(0).unwrap();
        let version = crate_.latest_version();
        let _ = version.deps;

        let _ = fs::remove_dir_all(TEST_INDEX_DIR);
    }

    #[test]
    fn test_exists() {
        let _ = fs::remove_dir_all(TEST_INDEX_DIR);

        let index = Index::new(TEST_INDEX_DIR.into());
        assert!(!index.exists());
        index.retrieve().unwrap();
        assert!(index.exists());

        let _ = fs::remove_dir_all(TEST_INDEX_DIR);
    }
}
<commit_msg>Add update methods.<commit_after>// Copyright 2015 Corey Farwell
// Copyright 2015 Contributors of github.com/huonw/crates.io-graph
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Library for retrieving and interacting with the [crates.io index].
//!
//! [crates.io index]: https://github.com/rust-lang/crates.io-index
//!
//! ## Examples
//!
//! ```rust
//! extern crate crates_index;
//!
//! let index = crates_index::Index::new("_index".into());
//! if !index.exists() {
//!    index.retrieve().expect("Could not fetch crates.io index");
//! }
//! for crate_ in index.crates() {
//!    let latest_version = crate_.latest_version();
//!    println!("crate name: {}", latest_version.name());
//!    println!("crate version: {}", latest_version.version());
//! }
//! ```

use std::ascii::AsciiExt;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
use std::io::{BufRead, BufReader};
use std::iter;
use std::path::{Path, PathBuf};

extern crate git2;
extern crate glob;
extern crate rustc_serialize;

static INDEX_GIT_URL: &'static str = "https://github.com/rust-lang/crates.io-index";

/// A single version of a crate published to the index
#[derive(RustcDecodable, Clone)]
pub struct Version {
    name: String,
    vers: String,
    deps: Vec<Dependency>,
    cksum: String,
    features: HashMap<String, Vec<String>>,
    yanked: bool,
}

impl Version {
    /// Name of the crate
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Name of this version
    pub fn version(&self) -> &str {
        &self.vers
    }

    /// Dependencies for this version
    pub fn dependencies(&self) -> &[Dependency] {
        &self.deps
    }

    /// Checksum of the package for this version
    pub fn checksum(&self) -> &str {
        &self.cksum
    }

    pub fn features(&self) -> &HashMap<String, Vec<String>> {
        &self.features
    }

    /// Whether this version was [yanked](http://doc.crates.io/crates-io.html#cargo-yank) from the
    /// index
    pub fn is_yanked(&self) -> bool {
        self.yanked
    }
}

/// A single dependency of a specific crate version
#[derive(RustcDecodable, Clone)]
pub struct Dependency {
    name: String,
    req: String,
    features: Vec<String>,
    optional: bool,
    default_features: bool,
    target: Option<String>,
    kind: Option<String>,
}

impl Dependency {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn requirement(&self) -> &str {
        &self.req
    }

    pub fn features(&self) -> &[String] {
        &self.features
    }

    pub fn is_optional(&self) -> bool {
        self.optional
    }

    pub fn has_default_features(&self) -> bool {
        self.default_features
    }

    pub fn target(&self) -> Option<&str> {
        match self.target {
            Some(ref s) => Some(s),
            None => None,
        }
    }

    pub fn kind(&self) -> Option<&str> {
        match self.kind {
            Some(ref s) => Some(s),
            None => None,
        }
    }
}

/// Constructed from `Index::crates`
pub struct Crates(CrateIndexPaths);

impl Iterator for Crates {
    type Item = Crate;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|p| Crate::new(&p))
    }
}

/// Constructed from `Index::crate_index_paths`
pub struct CrateIndexPaths(iter::Chain<glob::Paths, glob::Paths>);

impl CrateIndexPaths {
    fn new<P: AsRef<Path>>(path: P) -> CrateIndexPaths {
        let mut match_options = glob::MatchOptions::new();
        match_options.require_literal_leading_dot = true;
        let path = path.as_ref();

        let glob_pattern = format!("{}/*/*/*", path.to_str().unwrap());
        let index_paths1 = glob::glob_with(&glob_pattern, &match_options).unwrap();

        let glob_pattern = format!("{}/[12]/*", path.to_str().unwrap());
        let index_paths2 = glob::glob_with(&glob_pattern, &match_options).unwrap();

        CrateIndexPaths(index_paths1.chain(index_paths2))
    }
}

impl Iterator for CrateIndexPaths {
    type Item = PathBuf;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|glob_result| glob_result.unwrap())
    }
}

/// Wrapper around managing the crates.io-index git repository
pub struct Index {
    path: PathBuf,
}

impl Index {
    /// Construct a new Index supplying a path where the index lives or should live
    pub fn new(path: PathBuf) -> Index {
        Index { path: path }
    }

    /// Determines if a crates.io repository exists at `self.path`
    pub fn exists(&self) -> bool {
        git2::Repository::discover(&self.path)
            .map(|repository| {
                repository
                    .find_remote("origin")
                    .map(|remote| remote.url() == Some(INDEX_GIT_URL))
                    .unwrap_or(false)
            })
            .unwrap_or(false)
    }

    /// Downloads the index to the path specified from the constructor
    pub fn retrieve(&self) -> Result<(), git2::Error> {
        let _ = try!(git2::Repository::clone(INDEX_GIT_URL, &self.path));
        Ok(())
    }

    /// Assumes the index already exists at `self.path`, and updates it
    pub fn update(&self) -> Result<(), git2::Error> {
        debug_assert!(self.exists());
        let repo = git2::Repository::discover(&self.path)?;
        let mut origin_remote = repo.find_remote("origin")?;
        origin_remote.fetch(&["master"], None, None)?;
        let mut branches = repo.branches(Some(git2::BranchType::Remote))?;
        let origin_master_branch = branches.next().expect("could not find branch origin/master")?.0;
        debug_assert_eq!(origin_master_branch.name()?, Some("origin/master"));
        let target_oid = origin_master_branch.get().target().unwrap();
        let object = repo.find_object(target_oid, None)?;
        repo.reset(&object, git2::ResetType::Hard, None)?;
        Ok(())
    }

    /// Downloads the index to the path specified from the constructor
    pub fn retrieve_or_update(&self) -> Result<(), git2::Error> {
        if self.exists() {
            self.update()
        } else {
            self.retrieve()
        }
    }

    /// Retrieve a single crate by name (case insensitive) from the index
    pub fn crate_(&self, crate_name: &str) -> Option<Crate> {
        self.crate_index_paths()
            .find(|path| {
                      path.file_name()
                          .and_then(OsStr::to_str)
                          .map(|file_name| file_name.eq_ignore_ascii_case(crate_name))
                          .unwrap_or(false)
                  })
            .map(|p| Crate::new(&p))
    }

    /// Retrieve an iterator over all the crates in the index
    pub fn crates(&self) -> Crates {
        Crates(self.crate_index_paths())
    }

    /// Returns all the crate index file paths in the index
    pub fn crate_index_paths(&self) -> CrateIndexPaths {
        CrateIndexPaths::new(self.path.clone()) // TODO: remove this clone
    }
}

/// A single crate that contains many published versions
pub struct Crate {
    versions: Vec<Version>,
}

impl Crate {
    pub fn new<P: AsRef<Path>>(index_path: P) -> Crate {
        let index_path = index_path.as_ref();
        let mut versions = vec![];
        let file = fs::File::open(&index_path).unwrap();
        for line in BufReader::new(file).lines() {
            let version: Version = rustc_serialize::json::decode(&line.unwrap()).unwrap();
            versions.push(version);
        }
        Crate { versions: versions }
    }

    /// Published versions of this crate sorted chronologically by date published
    pub fn versions(&self) -> &[Version] {
        &self.versions
    }

    pub fn earliest_version(&self) -> &Version {
        &self.versions[0]
    }

    pub fn latest_version(&self) -> &Version {
        &self.versions[self.versions.len() - 1]
    }
}

#[cfg(test)]
mod test {
    use std::fs;
    use super::Index;

    static TEST_INDEX_DIR: &'static str = "_test";

    #[test]
    fn test_dependencies() {
        let _ = fs::remove_dir_all(TEST_INDEX_DIR);

        let index = Index::new(TEST_INDEX_DIR.into());
        index.retrieve().expect("could not fetch crates io index");
        let crate_ = index.crates().nth(0).expect("could not find a crate in the index");
        let version = crate_.latest_version();
        let _ = version.deps;

        let _ = fs::remove_dir_all(TEST_INDEX_DIR);
    }

    #[test]
    fn test_retrieve_or_update() {
        let _ = fs::remove_dir_all(TEST_INDEX_DIR);

        let index = Index::new(TEST_INDEX_DIR.into());
        index.retrieve_or_update().expect("could not fetch crates io index");
        assert!(index.exists());
        index.retrieve_or_update().expect("could not fetch crates io index");
        assert!(index.exists());

        let _ = fs::remove_dir_all(TEST_INDEX_DIR);
    }

    #[test]
    fn test_exists() {
        let _ = fs::remove_dir_all(TEST_INDEX_DIR);

        let index = Index::new(TEST_INDEX_DIR.into());
        assert!(!index.exists());
        index.retrieve().unwrap();
        assert!(index.exists());

        let _ = fs::remove_dir_all(TEST_INDEX_DIR);
    }
}
<|endoftext|>


<commit_before>pub mod raw;

extern crate libc;

use raw::*;
use libc::c_int;
use std::ptr;
use std::ffi;

pub struct FitsFile {
    fptr: *mut fitsfile,
    status: c_int,
}

impl FitsFile {
    pub fn open(filename: &str) -> FitsFile {
        let mut fptr: *mut fitsfile = ptr::null_mut();
        let mut status: c_int = 0;
        let c_filename = ffi::CString::new(filename).unwrap();

        unsafe {
            ffopen(&mut fptr as *mut *mut fitsfile, c_filename.as_ptr(), 0, &mut status);
        }

        return match status {
            0 => FitsFile {
                fptr: fptr,
                status: status,
            },
            status => panic!("Invalid status code: {}", status),
        }

    }

    /// Returns the current HDU number, 0-indexed
    ///
    /// The FITS standard is 1-indexed (thanks Fortran), so this function
    /// converts the indexing to be 0-based so the primary HDU is at
    /// index 0.
    ///
    /// # Examples
    ///
    /// ```
    /// use fitsio::FitsFile;
    ///
    /// let f = FitsFile::open("testdata/full_example.fits");
    /// assert_eq!(f.current_hdu_number(), 0);
    /// ```
    pub fn current_hdu_number(&self) -> u32 {
        let mut hdu_num: c_int = 0;
        unsafe {
            ffghdn(self.fptr, &mut hdu_num);
        }
        assert!(hdu_num >= 1);
        (hdu_num - 1) as u32
    }
}

impl Drop for FitsFile {
    fn drop(&mut self) {
        unsafe {
            ffclos(self.fptr, &mut self.status);
        }
    }
}

mod test {
    use super::FitsFile;

    #[test]
    fn opening_an_existing_file() {
        let f = FitsFile::open("testdata/full_example.fits");
        assert_eq!(f.status, 0);
    }

    #[test]
    fn getting_current_hdu_number() {
        let f = FitsFile::open("testdata/full_example.fits");
        assert_eq!(f.current_hdu_number(), 0u32);
    }
}
<commit_msg>Add test for changing hdu<commit_after>pub mod raw;

extern crate libc;

use raw::*;
use libc::c_int;
use std::ptr;
use std::ffi;

pub struct FitsFile {
    fptr: *mut fitsfile,
    status: c_int,
}

impl FitsFile {
    pub fn open(filename: &str) -> FitsFile {
        let mut fptr: *mut fitsfile = ptr::null_mut();
        let mut status: c_int = 0;
        let c_filename = ffi::CString::new(filename).unwrap();

        unsafe {
            ffopen(&mut fptr as *mut *mut fitsfile, c_filename.as_ptr(), 0, &mut status);
        }

        return match status {
            0 => FitsFile {
                fptr: fptr,
                status: status,
            },
            status => panic!("Invalid status code: {}", status),
        }

    }

    /// Returns the current HDU number, 0-indexed
    ///
    /// The FITS standard is 1-indexed (thanks Fortran), so this function
    /// converts the indexing to be 0-based so the primary HDU is at
    /// index 0.
    ///
    /// # Examples
    ///
    /// ```
    /// use fitsio::FitsFile;
    ///
    /// let f = FitsFile::open("testdata/full_example.fits");
    /// assert_eq!(f.current_hdu_number(), 0);
    /// ```
    pub fn current_hdu_number(&self) -> u32 {
        let mut hdu_num: c_int = 0;
        unsafe {
            ffghdn(self.fptr, &mut hdu_num);
        }
        assert!(hdu_num >= 1);
        (hdu_num - 1) as u32
    }

    pub fn change_hdu(&mut self, hdu_num: u32) {
        let mut _hdu_type: c_int = 0;
        unsafe {
            ffmahd(self.fptr, (hdu_num + 1) as i32,
                &mut _hdu_type, &mut self.status);
        }
    }
}

impl Drop for FitsFile {
    fn drop(&mut self) {
        unsafe {
            ffclos(self.fptr, &mut self.status);
        }
    }
}

mod test {
    use super::FitsFile;

    #[test]
    fn opening_an_existing_file() {
        let f = FitsFile::open("testdata/full_example.fits");
        assert_eq!(f.status, 0);
    }

    #[test]
    fn change_hdu() {
        let mut f = FitsFile::open("testdata/full_example.fits");
        f.change_hdu(1);
        assert_eq!(f.current_hdu_number(), 1u32);
    }

    #[test]
    fn getting_current_hdu_number() {
        let f = FitsFile::open("testdata/full_example.fits");
        assert_eq!(f.current_hdu_number(), 0u32);
    }
}
<|endoftext|>


<commit_before>#![feature(read_exact)]

mod ntr_sender;
use ntr_sender::NtrSender;

extern crate byteorder;
extern crate time;
extern crate regex;

use byteorder::{ByteOrder, LittleEndian};
use time::{Duration, PreciseTime};
use regex::Regex;

use std::mem;
use std::io;
use std::io::prelude::*;
use std::net::TcpStream;
use std::thread;
use std::sync::{Arc, Mutex};
use std::sync::mpsc;
use std::sync::mpsc::Receiver;

pub struct Ntr {
    ntr_sender: Arc<Mutex<NtrSender>>,
    mem_read_rx: Receiver<Vec<u8>>,
    get_pid_rx: Receiver<String>,
}
impl Ntr {
    pub fn connect(addr: &str) -> io::Result<Self> {
        let mut tcp_stream = try!(TcpStream::connect(&(addr.to_owned() + ":8000") as &str));
        let (mem_read_tx, mem_read_rx) = mpsc::channel();
        let (get_pid_tx, get_pid_rx) = mpsc::channel();

        let ntr_sender = Arc::new(Mutex::new(NtrSender::new(try!(tcp_stream.try_clone()))));

        // spawn heartbeat thread
        {
            let ntr_sender = ntr_sender.clone();
            thread::spawn(move || {
                let one_second = Duration::seconds(1);
                let mut heartbeat_sent_time = PreciseTime::now();
                loop {
                    let mut ntr_sender = ntr_sender.lock().unwrap();
                    if heartbeat_sent_time.to(PreciseTime::now()) >= one_second
                     && ntr_sender.is_heartbeat_sendable() {
                        ntr_sender.send_heartbeat_packet().unwrap();
                        heartbeat_sent_time = PreciseTime::now();
                        ntr_sender.set_is_heartbeat_sendable(false);
                    }
                    mem::drop(ntr_sender);
                    thread::sleep_ms(500);
                }
            });
        }

        // spawn receiver thread
        {
            let ntr_sender = ntr_sender.clone();
            thread::spawn(move || {
                let mut buf = [0u8; 84];
                loop {
                    tcp_stream.read_exact(&mut buf).unwrap();
                    let cmd = LittleEndian::read_u32(&buf[12..16]);
                    let data_len = LittleEndian::read_u32(&buf[80..84]) as usize;

                    if data_len != 0 {
                        let mut data_buf = vec![0u8; data_len];
                        tcp_stream.read_exact(&mut data_buf[0..data_len]).unwrap();
                        if cmd == 0 {
                            ntr_sender.lock().unwrap().set_is_heartbeat_sendable(true);

                            let msg = String::from_utf8(data_buf).unwrap();
                            if let Some(_) = msg.find("end of process list.") {
                                get_pid_tx.send(msg).unwrap();
                            }
                        } else if cmd == 9 {
                            mem_read_tx.send(data_buf).unwrap();
                        }
                    }
                }
            });
        }

        Ok(Ntr {
            ntr_sender: ntr_sender,
            mem_read_rx: mem_read_rx,
            get_pid_rx: get_pid_rx,
        })
    }

    pub fn get_pid(&mut self, tid: u64) -> io::Result<Option<u32>> {
        try!(self.ntr_sender.lock().unwrap().send_list_process_packet());
        let msg = self.get_pid_rx.recv().unwrap();
        let cap = {
            let mut re = r"pid: 0x(\d{8}), pname:[^,]*, tid: ".to_owned();
            re.push_str(&format!("{:016x}", tid));
            Regex::new(&re)
                .unwrap()
                .captures(&msg)
        };
        Ok(match cap {
            Some(x) => Some(u32::from_str_radix(x.at(1).unwrap(), 16).unwrap()),
            None => None,
        })
    }

    pub fn mem_read(&mut self, addr: u32, size: u32, pid: u32) -> io::Result<Vec<u8>> {
        try!(self.ntr_sender.lock().unwrap().send_mem_read_packet(addr, size, pid));
        Ok(self.mem_read_rx.recv().unwrap())
    }

    pub fn mem_write(&mut self, addr: u32, data: &Vec<u8>, pid: u32) -> io::Result<usize> {
        self.ntr_sender.lock().unwrap().send_mem_write_packet(addr, pid, data)
    }
}<commit_msg>Reorder imports.<commit_after>#![feature(read_exact)]

mod ntr_sender;

extern crate byteorder;
extern crate regex;
extern crate time;

use std::io;
use std::io::prelude::*;
use std::mem;
use std::net::TcpStream;
use std::sync::{Arc, Mutex};
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::thread;
use byteorder::{ByteOrder, LittleEndian};
use regex::Regex;
use time::{Duration, PreciseTime};

use ntr_sender::NtrSender;

pub struct Ntr {
    ntr_sender: Arc<Mutex<NtrSender>>,
    mem_read_rx: Receiver<Vec<u8>>,
    get_pid_rx: Receiver<String>,
}
impl Ntr {
    pub fn connect(addr: &str) -> io::Result<Self> {
        let mut tcp_stream = try!(TcpStream::connect(&(addr.to_owned() + ":8000") as &str));
        let (mem_read_tx, mem_read_rx) = mpsc::channel();
        let (get_pid_tx, get_pid_rx) = mpsc::channel();

        let ntr_sender = Arc::new(Mutex::new(NtrSender::new(try!(tcp_stream.try_clone()))));

        // spawn heartbeat thread
        {
            let ntr_sender = ntr_sender.clone();
            thread::spawn(move || {
                let one_second = Duration::seconds(1);
                let mut heartbeat_sent_time = PreciseTime::now();
                loop {
                    let mut ntr_sender = ntr_sender.lock().unwrap();
                    if heartbeat_sent_time.to(PreciseTime::now()) >= one_second
                     && ntr_sender.is_heartbeat_sendable() {
                        ntr_sender.send_heartbeat_packet().unwrap();
                        heartbeat_sent_time = PreciseTime::now();
                        ntr_sender.set_is_heartbeat_sendable(false);
                    }
                    mem::drop(ntr_sender);
                    thread::sleep_ms(500);
                }
            });
        }

        // spawn receiver thread
        {
            let ntr_sender = ntr_sender.clone();
            thread::spawn(move || {
                let mut buf = [0u8; 84];
                loop {
                    tcp_stream.read_exact(&mut buf).unwrap();
                    let cmd = LittleEndian::read_u32(&buf[12..16]);
                    let data_len = LittleEndian::read_u32(&buf[80..84]) as usize;

                    if data_len != 0 {
                        let mut data_buf = vec![0u8; data_len];
                        tcp_stream.read_exact(&mut data_buf[0..data_len]).unwrap();
                        if cmd == 0 {
                            ntr_sender.lock().unwrap().set_is_heartbeat_sendable(true);

                            let msg = String::from_utf8(data_buf).unwrap();
                            if let Some(_) = msg.find("end of process list.") {
                                get_pid_tx.send(msg).unwrap();
                            }
                        } else if cmd == 9 {
                            mem_read_tx.send(data_buf).unwrap();
                        }
                    }
                }
            });
        }

        Ok(Ntr {
            ntr_sender: ntr_sender,
            mem_read_rx: mem_read_rx,
            get_pid_rx: get_pid_rx,
        })
    }

    pub fn get_pid(&mut self, tid: u64) -> io::Result<Option<u32>> {
        try!(self.ntr_sender.lock().unwrap().send_list_process_packet());
        let msg = self.get_pid_rx.recv().unwrap();
        let cap = {
            let mut re = r"pid: 0x(\d{8}), pname:[^,]*, tid: ".to_owned();
            re.push_str(&format!("{:016x}", tid));
            Regex::new(&re)
                .unwrap()
                .captures(&msg)
        };
        Ok(match cap {
            Some(x) => Some(u32::from_str_radix(x.at(1).unwrap(), 16).unwrap()),
            None => None,
        })
    }

    pub fn mem_read(&mut self, addr: u32, size: u32, pid: u32) -> io::Result<Vec<u8>> {
        try!(self.ntr_sender.lock().unwrap().send_mem_read_packet(addr, size, pid));
        Ok(self.mem_read_rx.recv().unwrap())
    }

    pub fn mem_write(&mut self, addr: u32, data: &Vec<u8>, pid: u32) -> io::Result<usize> {
        self.ntr_sender.lock().unwrap().send_mem_write_packet(addr, pid, data)
    }
}<|endoftext|>


<commit_before>// Copyright (c) 2016 Mark Lee
//
// 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.

//! Low-level bindings to OSX/macOS/iOS's `CommonCrypto` library.

#![warn(missing_docs)]

extern crate hex;
extern crate libc;

use libc::{c_int, c_uint};

/// Total number of operations.
const MD5_CBLOCK: usize = 64;
/// Number of operations per round.
const MD5_LBLOCK: usize = MD5_CBLOCK / 4;
/// Number of bytes for an MD5 hash.
pub const MD5_DIGEST_LENGTH: usize = 16;

const SHA_LBLOCK: usize = 16;
/// Number of bytes for an SHA1 hash.
pub const SHA1_DIGEST_LENGTH: usize = 20;
/// Number of bytes for an SHA256 hash.
pub const SHA256_DIGEST_LENGTH: usize = 32;
/// Number of bytes for an SHA384 hash.
pub const SHA384_DIGEST_LENGTH: usize = 48;
/// Number of bytes for an SHA512 hash.
pub const SHA512_DIGEST_LENGTH: usize = 64;

/// Struct used to generate MD5 hashes.
#[allow(non_camel_case_types, non_snake_case)]
#[derive(Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub struct CC_MD5_CTX {
    A: c_uint,
    B: c_uint,
    C: c_uint,
    D: c_uint,
    Nl: c_uint,
    Nh: c_uint,
    data: [c_uint; MD5_LBLOCK],
    num: c_uint,
}

/// Struct used to generate SHA1 hashes.
#[allow(non_camel_case_types, non_snake_case)]
#[derive(Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub struct CC_SHA_CTX {
    h0: c_uint,
    h1: c_uint,
    h2: c_uint,
    h3: c_uint,
    h4: c_uint,
    Nl: c_uint,
    Nh: c_uint,
    data: [c_uint; SHA_LBLOCK],
    num: c_uint,
}

macro_rules! cc_sha2_struct {
    ($ctx_name: ident, $ty: ty) => {
        /// Struct used to generate SHA2 hashes with the given bits.
        #[allow(non_camel_case_types, non_snake_case)]
        #[derive(Clone, Debug, Default, PartialEq)]
        #[repr(C)]
        pub struct $ctx_name {
            count: [$ty; 2],
            hash: [$ty; 8],
            wbuf: [$ty; 16],
        }
    }
}

cc_sha2_struct!(CC_SHA256_CTX, u32);
cc_sha2_struct!(CC_SHA512_CTX, u64);

/// Digest algorithm used in `CCDigest*()` functions.
#[repr(C)]
pub enum CCDigestAlgorithm {
    /// No digest algorithm
    kCCDigestNone = 0,
    /// MD2
    kCCDigestMD2 = 1,
    /// MD4
    kCCDigestMD4 = 2,
    /// MD5
    kCCDigestMD5 = 3,
    /// RIPEMD-128
    kCCDigestRMD128 = 4,
    /// RIPEMD-160
    kCCDigestRMD160 = 5,
    /// RIPEMD-256
    kCCDigestRMD256 = 6,
    /// RIPEMD-320
    kCCDigestRMD320 = 7,
    /// SHA1
    kCCDigestSHA1 = 8,
    /// SHA224
    kCCDigestSHA224 = 9,
    /// SHA256
    kCCDigestSHA256 = 10,
    /// SHA384
    kCCDigestSHA384 = 11,
    /// SHA512
    kCCDigestSHA512 = 12,
    /// Skein, 128 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein128 = 13,
    /// Skein, 160 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein160 = 14,
    /// Skein, 224 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein224 = 16,
    /// Skein, 256 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein256 = 17,
    /// Skein, 384 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein384 = 18,
    /// Skein, 512 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein512 = 19,
}

const CC_DIGEST_SIZE: usize = 1032;

/// Context used in `CCDigest*()` functions.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub struct CCDigestCtx {
    context: [u8; CC_DIGEST_SIZE],
}

extern "C" {
    /// Initializes MD5 hasher. See `man 3cc CC_MD5` for details.
    pub fn CC_MD5_Init(ctx: *mut CC_MD5_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_MD5` for details.
    pub fn CC_MD5_Update(ctx: *mut CC_MD5_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates MD5 hash. See `man 3cc CC_MD5` for details.
    pub fn CC_MD5_Final(md: *mut u8, ctx: *mut CC_MD5_CTX) -> c_int;
    /// Initializes SHA1 hasher. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA1_Init(ctx: *mut CC_SHA_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA1_Update(ctx: *mut CC_SHA_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates SHA1 hash. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA1_Final(md: *mut u8, ctx: *mut CC_SHA_CTX) -> c_int;
    /// Initializes SHA256 hasher. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA256_Init(ctx: *mut CC_SHA256_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA256_Update(ctx: *mut CC_SHA256_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates SHA256 hash. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA256_Final(md: *mut u8, ctx: *mut CC_SHA256_CTX) -> c_int;
    /// Initializes SHA384 hasher. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA384_Init(ctx: *mut CC_SHA512_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA384_Update(ctx: *mut CC_SHA512_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates SHA384 hash. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA384_Final(md: *mut u8, ctx: *mut CC_SHA512_CTX) -> c_int;
    /// Initializes SHA512 hasher. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA512_Init(ctx: *mut CC_SHA512_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA512_Update(ctx: *mut CC_SHA512_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates SHA512 hash. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA512_Final(md: *mut u8, ctx: *mut CC_SHA512_CTX) -> c_int;
    pub fn CCDigest(algorithm: CCDigestAlgorithm,
                    data: *const u8,
                    length: usize,
                    output: *mut u8)
                    -> c_int;
}

#[cfg(test)]
mod test {
    use hex::ToHex;

    const TO_HASH: &'static str = "The quick brown fox jumps over the lazy dog";
    const TO_HASH_MD5: &'static str = "9e107d9d372bb6826bd81d3542a419d6";
    const TO_HASH_SHA1: &'static str = "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12";
    const TO_HASH_SHA256: &'static str = "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592";
    const TO_HASH_SHA384: &'static str = "ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1";
    const TO_HASH_SHA512: &'static str = "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6";

    macro_rules! test_cc_hash {
        (
            $test_name: ident,
            $ctx_ty: ident,
            $digest_len: ident,
            $init_func: ident,
            $update_func: ident,
            $final_func: ident,
            $expected_hash: ident
        ) => {
            #[test]
            fn $test_name() {
                let mut ctx = super::$ctx_ty::default();
                let mut md = [0u8; super::$digest_len];
                unsafe {
                    assert_eq!(super::$init_func(&mut ctx), 1);
                    assert_eq!(super::$update_func(&mut ctx, TO_HASH.as_ptr(), TO_HASH.len()), 1);
                    assert_eq!(super::$final_func(md.as_mut_ptr(), &mut ctx), 1);
                }
                assert_eq!(md.to_vec().to_hex(), $expected_hash);
            }
        }
    }

    macro_rules! test_ccdigest {
        (
            $test_name: ident,
            $algorithm: ident,
            $digest_len: ident,
            $expected_hash: ident
        ) => {
            #[test]
            fn $test_name() {
                let mut md = [0u8; super::$digest_len];
                unsafe {
                    assert_eq!(super::CCDigest(super::CCDigestAlgorithm::$algorithm,
                                               TO_HASH.as_ptr(),
                                               TO_HASH.len(),
                                               md.as_mut_ptr()), 0)
                }
                assert_eq!(md.to_vec().to_hex(), $expected_hash);
            }
        }
    }

    test_cc_hash!(md5_hash,
                  CC_MD5_CTX,
                  MD5_DIGEST_LENGTH,
                  CC_MD5_Init,
                  CC_MD5_Update,
                  CC_MD5_Final,
                  TO_HASH_MD5);
    test_cc_hash!(sha1_hash,
                  CC_SHA_CTX,
                  SHA1_DIGEST_LENGTH,
                  CC_SHA1_Init,
                  CC_SHA1_Update,
                  CC_SHA1_Final,
                  TO_HASH_SHA1);
    test_cc_hash!(sha256_hash,
                  CC_SHA256_CTX,
                  SHA256_DIGEST_LENGTH,
                  CC_SHA256_Init,
                  CC_SHA256_Update,
                  CC_SHA256_Final,
                  TO_HASH_SHA256);
    test_cc_hash!(sha384_hash,
                  CC_SHA512_CTX,
                  SHA384_DIGEST_LENGTH,
                  CC_SHA384_Init,
                  CC_SHA384_Update,
                  CC_SHA384_Final,
                  TO_HASH_SHA384);
    test_cc_hash!(sha512_hash,
                  CC_SHA512_CTX,
                  SHA512_DIGEST_LENGTH,
                  CC_SHA512_Init,
                  CC_SHA512_Update,
                  CC_SHA512_Final,
                  TO_HASH_SHA512);

    test_ccdigest!(md5_ccdigest, kCCDigestMD5, MD5_DIGEST_LENGTH, TO_HASH_MD5);
    test_ccdigest!(sha1_ccdigest,
                   kCCDigestSHA1,
                   SHA1_DIGEST_LENGTH,
                   TO_HASH_SHA1);
    test_ccdigest!(sha256_ccdigest,
                   kCCDigestSHA256,
                   SHA256_DIGEST_LENGTH,
                   TO_HASH_SHA256);
    test_ccdigest!(sha384_ccdigest,
                   kCCDigestSHA384,
                   SHA384_DIGEST_LENGTH,
                   TO_HASH_SHA384);
    test_ccdigest!(sha512_ccdigest,
                   kCCDigestSHA512,
                   SHA512_DIGEST_LENGTH,
                   TO_HASH_SHA512);
}
<commit_msg>Add docs for CCDigest<commit_after>// Copyright (c) 2016 Mark Lee
//
// 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.

//! Low-level bindings to OSX/macOS/iOS's `CommonCrypto` library.

#![warn(missing_docs)]

extern crate hex;
extern crate libc;

use libc::{c_int, c_uint};

/// Total number of operations.
const MD5_CBLOCK: usize = 64;
/// Number of operations per round.
const MD5_LBLOCK: usize = MD5_CBLOCK / 4;
/// Number of bytes for an MD5 hash.
pub const MD5_DIGEST_LENGTH: usize = 16;

const SHA_LBLOCK: usize = 16;
/// Number of bytes for an SHA1 hash.
pub const SHA1_DIGEST_LENGTH: usize = 20;
/// Number of bytes for an SHA256 hash.
pub const SHA256_DIGEST_LENGTH: usize = 32;
/// Number of bytes for an SHA384 hash.
pub const SHA384_DIGEST_LENGTH: usize = 48;
/// Number of bytes for an SHA512 hash.
pub const SHA512_DIGEST_LENGTH: usize = 64;

/// Struct used to generate MD5 hashes.
#[allow(non_camel_case_types, non_snake_case)]
#[derive(Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub struct CC_MD5_CTX {
    A: c_uint,
    B: c_uint,
    C: c_uint,
    D: c_uint,
    Nl: c_uint,
    Nh: c_uint,
    data: [c_uint; MD5_LBLOCK],
    num: c_uint,
}

/// Struct used to generate SHA1 hashes.
#[allow(non_camel_case_types, non_snake_case)]
#[derive(Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub struct CC_SHA_CTX {
    h0: c_uint,
    h1: c_uint,
    h2: c_uint,
    h3: c_uint,
    h4: c_uint,
    Nl: c_uint,
    Nh: c_uint,
    data: [c_uint; SHA_LBLOCK],
    num: c_uint,
}

macro_rules! cc_sha2_struct {
    ($ctx_name: ident, $ty: ty) => {
        /// Struct used to generate SHA2 hashes with the given bits.
        #[allow(non_camel_case_types, non_snake_case)]
        #[derive(Clone, Debug, Default, PartialEq)]
        #[repr(C)]
        pub struct $ctx_name {
            count: [$ty; 2],
            hash: [$ty; 8],
            wbuf: [$ty; 16],
        }
    }
}

cc_sha2_struct!(CC_SHA256_CTX, u32);
cc_sha2_struct!(CC_SHA512_CTX, u64);

/// Digest algorithm used in `CCDigest*()` functions.
#[repr(C)]
pub enum CCDigestAlgorithm {
    /// No digest algorithm
    kCCDigestNone = 0,
    /// MD2
    kCCDigestMD2 = 1,
    /// MD4
    kCCDigestMD4 = 2,
    /// MD5
    kCCDigestMD5 = 3,
    /// RIPEMD-128
    kCCDigestRMD128 = 4,
    /// RIPEMD-160
    kCCDigestRMD160 = 5,
    /// RIPEMD-256
    kCCDigestRMD256 = 6,
    /// RIPEMD-320
    kCCDigestRMD320 = 7,
    /// SHA1
    kCCDigestSHA1 = 8,
    /// SHA224
    kCCDigestSHA224 = 9,
    /// SHA256
    kCCDigestSHA256 = 10,
    /// SHA384
    kCCDigestSHA384 = 11,
    /// SHA512
    kCCDigestSHA512 = 12,
    /// Skein, 128 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein128 = 13,
    /// Skein, 160 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein160 = 14,
    /// Skein, 224 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein224 = 16,
    /// Skein, 256 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein256 = 17,
    /// Skein, 384 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein384 = 18,
    /// Skein, 512 bit (Deprecated in iPhoneOS 6.0 and MacOSX10.9)
    kCCDigestSkein512 = 19,
}

const CC_DIGEST_SIZE: usize = 1032;

/// Context used in `CCDigest*()` functions.
#[allow(non_camel_case_types, non_snake_case)]
#[repr(C)]
pub struct CCDigestCtx {
    context: [u8; CC_DIGEST_SIZE],
}

extern "C" {
    /// Initializes MD5 hasher. See `man 3cc CC_MD5` for details.
    pub fn CC_MD5_Init(ctx: *mut CC_MD5_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_MD5` for details.
    pub fn CC_MD5_Update(ctx: *mut CC_MD5_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates MD5 hash. See `man 3cc CC_MD5` for details.
    pub fn CC_MD5_Final(md: *mut u8, ctx: *mut CC_MD5_CTX) -> c_int;
    /// Initializes SHA1 hasher. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA1_Init(ctx: *mut CC_SHA_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA1_Update(ctx: *mut CC_SHA_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates SHA1 hash. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA1_Final(md: *mut u8, ctx: *mut CC_SHA_CTX) -> c_int;
    /// Initializes SHA256 hasher. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA256_Init(ctx: *mut CC_SHA256_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA256_Update(ctx: *mut CC_SHA256_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates SHA256 hash. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA256_Final(md: *mut u8, ctx: *mut CC_SHA256_CTX) -> c_int;
    /// Initializes SHA384 hasher. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA384_Init(ctx: *mut CC_SHA512_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA384_Update(ctx: *mut CC_SHA512_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates SHA384 hash. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA384_Final(md: *mut u8, ctx: *mut CC_SHA512_CTX) -> c_int;
    /// Initializes SHA512 hasher. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA512_Init(ctx: *mut CC_SHA512_CTX) -> c_int;
    /// Appends data to be hashed. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA512_Update(ctx: *mut CC_SHA512_CTX, data: *const u8, n: usize) -> c_int;
    /// Generates SHA512 hash. See `man 3cc CC_SHA` for details.
    pub fn CC_SHA512_Final(md: *mut u8, ctx: *mut CC_SHA512_CTX) -> c_int;
    /// Generic digest hasher.
    pub fn CCDigest(algorithm: CCDigestAlgorithm,
                    data: *const u8,
                    length: usize,
                    output: *mut u8)
                    -> c_int;
}

#[cfg(test)]
mod test {
    use hex::ToHex;

    const TO_HASH: &'static str = "The quick brown fox jumps over the lazy dog";
    const TO_HASH_MD5: &'static str = "9e107d9d372bb6826bd81d3542a419d6";
    const TO_HASH_SHA1: &'static str = "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12";
    const TO_HASH_SHA256: &'static str = "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592";
    const TO_HASH_SHA384: &'static str = "ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1";
    const TO_HASH_SHA512: &'static str = "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6";

    macro_rules! test_cc_hash {
        (
            $test_name: ident,
            $ctx_ty: ident,
            $digest_len: ident,
            $init_func: ident,
            $update_func: ident,
            $final_func: ident,
            $expected_hash: ident
        ) => {
            #[test]
            fn $test_name() {
                let mut ctx = super::$ctx_ty::default();
                let mut md = [0u8; super::$digest_len];
                unsafe {
                    assert_eq!(super::$init_func(&mut ctx), 1);
                    assert_eq!(super::$update_func(&mut ctx, TO_HASH.as_ptr(), TO_HASH.len()), 1);
                    assert_eq!(super::$final_func(md.as_mut_ptr(), &mut ctx), 1);
                }
                assert_eq!(md.to_vec().to_hex(), $expected_hash);
            }
        }
    }

    macro_rules! test_ccdigest {
        (
            $test_name: ident,
            $algorithm: ident,
            $digest_len: ident,
            $expected_hash: ident
        ) => {
            #[test]
            fn $test_name() {
                let mut md = [0u8; super::$digest_len];
                unsafe {
                    assert_eq!(super::CCDigest(super::CCDigestAlgorithm::$algorithm,
                                               TO_HASH.as_ptr(),
                                               TO_HASH.len(),
                                               md.as_mut_ptr()), 0)
                }
                assert_eq!(md.to_vec().to_hex(), $expected_hash);
            }
        }
    }

    test_cc_hash!(md5_hash,
                  CC_MD5_CTX,
                  MD5_DIGEST_LENGTH,
                  CC_MD5_Init,
                  CC_MD5_Update,
                  CC_MD5_Final,
                  TO_HASH_MD5);
    test_cc_hash!(sha1_hash,
                  CC_SHA_CTX,
                  SHA1_DIGEST_LENGTH,
                  CC_SHA1_Init,
                  CC_SHA1_Update,
                  CC_SHA1_Final,
                  TO_HASH_SHA1);
    test_cc_hash!(sha256_hash,
                  CC_SHA256_CTX,
                  SHA256_DIGEST_LENGTH,
                  CC_SHA256_Init,
                  CC_SHA256_Update,
                  CC_SHA256_Final,
                  TO_HASH_SHA256);
    test_cc_hash!(sha384_hash,
                  CC_SHA512_CTX,
                  SHA384_DIGEST_LENGTH,
                  CC_SHA384_Init,
                  CC_SHA384_Update,
                  CC_SHA384_Final,
                  TO_HASH_SHA384);
    test_cc_hash!(sha512_hash,
                  CC_SHA512_CTX,
                  SHA512_DIGEST_LENGTH,
                  CC_SHA512_Init,
                  CC_SHA512_Update,
                  CC_SHA512_Final,
                  TO_HASH_SHA512);

    test_ccdigest!(md5_ccdigest, kCCDigestMD5, MD5_DIGEST_LENGTH, TO_HASH_MD5);
    test_ccdigest!(sha1_ccdigest,
                   kCCDigestSHA1,
                   SHA1_DIGEST_LENGTH,
                   TO_HASH_SHA1);
    test_ccdigest!(sha256_ccdigest,
                   kCCDigestSHA256,
                   SHA256_DIGEST_LENGTH,
                   TO_HASH_SHA256);
    test_ccdigest!(sha384_ccdigest,
                   kCCDigestSHA384,
                   SHA384_DIGEST_LENGTH,
                   TO_HASH_SHA384);
    test_ccdigest!(sha512_ccdigest,
                   kCCDigestSHA512,
                   SHA512_DIGEST_LENGTH,
                   TO_HASH_SHA512);
}
<|endoftext|>