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}