cardseed/
deck.rs

1use crate::card::Card;
2use crate::errors;
3use crate::suit::Suit;
4use crate::{DECK_SIZE, SUIT_SIZE};
5use hmac;
6use pbkdf2;
7use rand;
8use sha2;
9use std::{self, fmt};
10
11/// The number of PBKDF2 iterations used to derive secure entropy from a `Deck`.
12const PBKDF2_ITERATIONS: u32 = 1 << 16;
13
14/// A `Deck` represents a vector of `Card`s.
15#[derive(Debug, PartialEq)]
16pub struct Deck {
17    pub cards: Vec<Card>,
18}
19
20impl fmt::Display for Deck {
21    /// Formats the `Deck` as a space-delimited string of formatted `Card`s.
22    ///
23    /// ```
24    /// use cardseed::Deck;
25    ///
26    /// let s = format!("{}", Deck::new());
27    /// assert_eq!(s, "AS 2S 3S 4S 5S 6S 7S 8S 9S TS JS QS KS \
28    ///                AC 2C 3C 4C 5C 6C 7C 8C 9C TC JC QC KC \
29    ///                AH 2H 3H 4H 5H 6H 7H 8H 9H TH JH QH KH \
30    ///                AD 2D 3D 4D 5D 6D 7D 8D 9D TD JD QD KD");
31    /// ```
32    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33        for (i, card) in self.cards.iter().enumerate() {
34            if i == 0 {
35                write!(f, "{}", self.cards[0])?;
36            } else {
37                write!(f, " {}", card)?;
38            }
39        }
40        Ok(())
41    }
42}
43
44impl std::str::FromStr for Deck {
45    type Err = errors::ParseError;
46
47    /// Parses a `Deck` from a string of whitespace-delimited card strings.
48    ///
49    /// Beware, parsing a `Deck` accepts any set of valid cards, even
50    /// if some are duplicates. Use `Deck`'s `duplicates` method to
51    /// check for duplicates.
52    ///
53    /// ```
54    /// use cardseed::Deck;
55    ///
56    /// let deck = "QC JH 5D".parse::<Deck>().unwrap();
57    /// assert!(!deck.has_duplicates());
58    /// ```
59    fn from_str(s: &str) -> Result<Deck, errors::ParseError> {
60        let mut deck = Deck { cards: vec![] };
61        for chunk in s.split_whitespace() {
62            deck.cards.push(chunk.parse::<Card>()?);
63        }
64
65        Ok(deck)
66    }
67}
68
69impl Deck {
70    /// Creates a new `Deck` by appending every card in a standard playing card deck,
71    /// sorted in ascending order from the ace of spades to the king of diamonds.
72    ///
73    /// ```
74    /// use cardseed::Deck;
75    ///
76    /// let s = format!("{}", Deck::new());
77    /// assert_eq!(s, "AS 2S 3S 4S 5S 6S 7S 8S 9S TS JS QS KS \
78    ///                AC 2C 3C 4C 5C 6C 7C 8C 9C TC JC QC KC \
79    ///                AH 2H 3H 4H 5H 6H 7H 8H 9H TH JH QH KH \
80    ///                AD 2D 3D 4D 5D 6D 7D 8D 9D TD JD QD KD");
81    /// ```
82    pub fn new() -> Deck {
83        let mut deck = Deck { cards: vec![] };
84        let suits = Suit::all();
85        for i in 0..DECK_SIZE {
86            deck.cards.push(Card {
87                value: (i % SUIT_SIZE) as u32,
88                suit: suits[i / SUIT_SIZE],
89            });
90        }
91        deck
92    }
93
94    /// Randomly shuffles the `Deck` using a secure OS RNG.
95    pub fn shuffle(&self) -> Deck {
96        let samples = rand::seq::index::sample(&mut rand::rngs::OsRng, DECK_SIZE, DECK_SIZE);
97        let mut shuffled = Deck::new();
98        for (i, j) in std::iter::zip(0..DECK_SIZE, samples) {
99            shuffled.cards[i] = self.cards[j];
100        }
101        shuffled
102    }
103
104    /// Returns true if the `Deck` contains any duplicate cards.
105    ///
106    /// ```
107    /// use cardseed::Deck;
108    ///
109    /// let mut deck = "AS 2C AS".parse::<Deck>().unwrap();
110    /// assert!(deck.has_duplicates());
111    ///
112    /// deck = "9D 4H 3S".parse::<Deck>().unwrap();
113    /// assert!(!deck.has_duplicates());
114    /// ```
115    pub fn has_duplicates(&self) -> bool {
116        let mut seen = std::collections::HashSet::new();
117        for card in self.cards.iter() {
118            if seen.contains(&card) {
119                return true;
120            }
121            seen.insert(card);
122        }
123        false
124    }
125
126    /// Computes a deterministic hash of the `Deck` using
127    /// [PBKDF2](https://cryptobook.nakov.com/mac-and-key-derivation/pbkdf2) with
128    /// SHA256 as the underlying hash function. If the `password` parameter is not
129    /// None, it will be appended (colon-delimited) to the hash preimage to supply
130    /// additional entropy.
131    ///
132    /// Uses the PBKDF2_ITERATIONS constant to determine how many iterations of the to apply.
133    pub fn hash(&self, password: Option<&str>) -> Result<[u8; 32], Box<dyn std::error::Error>> {
134        let mut preimage = self.to_string();
135        match password {
136            Some(password) => {
137                preimage.push_str(":");
138                preimage.push_str(password);
139            }
140            None => {}
141        };
142
143        let mut output = [0u8; 32];
144        pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>(
145            preimage.as_bytes(),
146            b"",
147            PBKDF2_ITERATIONS,
148            &mut output,
149        )?;
150        Ok(output)
151    }
152
153    /// Assuming the deck is randomly shuffled, this method returns the number of bits
154    /// of shannon entropy contained in the deck. More entropy is more secure for deriving
155    /// passwords, keys, or other cryptographically sensitive secrets.
156    pub fn entropy_bits(&self) -> f64 {
157        (factorial(self.cards.len()) as f64).log2()
158    }
159}
160
161fn factorial(n: usize) -> usize {
162    if n <= 1 {
163        return 1;
164    }
165    n * factorial(n - 1)
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn deck_new() {
174        let deck = Deck::new();
175        assert_eq!(
176            deck.cards[15],
177            Card {
178                value: 2,
179                suit: Suit::Clubs,
180            }
181        )
182    }
183
184    #[test]
185    fn shuffle() {
186        let deck = Deck::new().shuffle();
187        assert_ne!(deck.cards[0], Card::ace_of_spades());
188    }
189
190    #[test]
191    fn to_string() -> Result<(), Box<dyn std::error::Error>> {
192        assert_eq!(
193            Deck::new().to_string(),
194            "AS 2S 3S 4S 5S 6S 7S 8S 9S TS JS QS KS AC 2C 3C 4C 5C 6C \
195             7C 8C 9C TC JC QC KC AH 2H 3H 4H 5H 6H 7H 8H 9H TH JH QH \
196             KH AD 2D 3D 4D 5D 6D 7D 8D 9D TD JD QD KD"
197        );
198
199        Ok(())
200    }
201
202    #[test]
203    fn from_string() -> Result<(), Box<dyn std::error::Error>> {
204        assert_eq!(
205            " AS\n 2D 3C  8H \tQD\n".parse::<Deck>(),
206            Ok(Deck {
207                cards: vec![
208                    Card {
209                        value: 0,
210                        suit: Suit::Spades
211                    },
212                    Card {
213                        value: 1,
214                        suit: Suit::Diamonds
215                    },
216                    Card {
217                        value: 2,
218                        suit: Suit::Clubs
219                    },
220                    Card {
221                        value: 7,
222                        suit: Suit::Hearts
223                    },
224                    Card {
225                        value: 11,
226                        suit: Suit::Diamonds
227                    },
228                ],
229            })
230        );
231
232        Ok(())
233    }
234
235    #[test]
236    fn hash() -> Result<(), Box<dyn std::error::Error>> {
237        assert_eq!(
238            Deck::new().hash(None)?,
239            [
240                204, 147, 92, 129, 195, 255, 197, 30, 16, 196, 216, 17, 114, 172, 27, 55, 31, 20,
241                238, 190, 66, 93, 236, 204, 173, 229, 53, 227, 189, 76, 227, 224
242            ]
243        );
244
245        assert_eq!(
246            Deck::new().hash(Some("slick"))?,
247            [
248                234, 182, 196, 8, 21, 159, 226, 239, 223, 128, 66, 185, 211, 166, 63, 83, 198, 254,
249                27, 246, 199, 237, 44, 207, 237, 34, 164, 191, 222, 104, 17, 133
250            ]
251        );
252
253        Ok(())
254    }
255}