#![warn(missing_docs)]
use bounded_integer::BoundedU8;
use card_play::*;
use once_cell::sync::OnceCell;
use std::collections::HashMap;
use std::convert::Infallible;
use std::fmt;
use std::fmt::Display;
use std::str::FromStr;
pub type UpperLetter = BoundedU8<65, 90>;
fn letter_into_value(ul: &UpperLetter) -> LetterValue {
LetterValue::new(u8::from(*ul) - 64).unwrap()
}
fn value_into_letter(lv: &LetterValue) -> UpperLetter {
UpperLetter::new(u8::from(*lv) + 64).unwrap()
}
type LetterValue = BoundedU8<1, 26>;
type CardValue = BoundedU8<1, 53>;
fn card_val_into_position(cv: &CardValue) -> CardPosition {
CardPosition::new(u8::from(*cv)).unwrap()
}
fn card_val_into_let_val(cv: CardValue) -> LetterValue {
LetterValue::new((u8::from(cv) - 1) % 26 + 1).unwrap()
}
type CardPosition = BoundedU8<1, 54>;
#[derive(Debug, Clone, Default)]
pub struct PlainText(pub Vec<UpperLetter>);
impl PlainText {
#[allow(missing_docs)]
pub fn new() -> PlainText {
PlainText(Vec::new())
}
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.0.len()
}
#[allow(missing_docs)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl Display for PlainText {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::new();
for c in self.0.iter() {
s.push(u8::from(*c) as char);
}
write!(f, "{}", s)
}
}
impl FromStr for PlainText {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut pt = PlainText(Vec::new());
for letter in s.to_uppercase().bytes() {
match UpperLetter::new(letter) {
Some(l) => pt.0.push(l),
None => continue,
}
}
while pt.0.len() % 5 != 0 {
pt.0.push(UpperLetter::new(b'X').unwrap());
}
Ok(pt)
}
}
#[derive(Debug, Default)]
pub struct CypherText(pub Vec<UpperLetter>);
impl CypherText {
#[allow(missing_docs)]
pub fn new() -> CypherText {
CypherText(Vec::new())
}
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.0.len()
}
#[allow(missing_docs)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl FromStr for CypherText {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut ct = CypherText(Vec::new());
for letter in s.to_uppercase().bytes() {
match UpperLetter::new(letter) {
Some(l) => ct.0.push(l),
None => continue,
}
}
Ok(ct)
}
}
impl Display for CypherText {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::new();
for (i, c) in self.0.iter().enumerate() {
if i != 0 && i % 5 == 0 {
s.push(' ');
}
s.push(u8::from(*c) as char);
}
write!(f, "{}", s)
}
}
#[derive(Debug, Default)]
pub struct Passphrase(pub Vec<UpperLetter>);
impl Passphrase {
fn iter(&self) -> std::slice::Iter<'_, UpperLetter> {
self.0.iter()
}
#[allow(missing_docs)]
pub fn new() -> Passphrase {
Passphrase(Vec::new())
}
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.0.len()
}
#[allow(missing_docs)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl FromStr for Passphrase {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut pp = Passphrase(Vec::new());
let s = &remove_ticks(s);
for letter in s.to_uppercase().bytes() {
if let Some(l) = UpperLetter::new(letter) {
pp.0.push(l);
} else {
return Err("string contains non-letter");
}
}
Ok(pp)
}
}
impl Display for Passphrase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::new();
for c in self.0.iter() {
s.push(u8::from(*c) as char);
}
write!(f, "{}", s)
}
}
#[derive(Debug, Default)]
pub struct KeyStream(pub Vec<UpperLetter>);
impl KeyStream {
#[allow(missing_docs)]
pub fn new() -> KeyStream {
KeyStream(Vec::new())
}
#[allow(missing_docs)]
pub fn len(&self) -> usize {
self.0.len()
}
#[allow(missing_docs)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl Display for KeyStream {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::new();
for (i, c) in self.0.iter().enumerate() {
if i != 0 && i % 5 == 0 {
s.push(' ');
}
s.push(u8::from(*c) as char);
}
write!(f, "{}", s)
}
}
impl FromStr for KeyStream {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut ks = KeyStream(Vec::new());
for letter in s.to_uppercase().bytes() {
match UpperLetter::new(letter) {
Some(l) => ks.0.push(l),
None => continue,
}
}
Ok(ks)
}
}
static VALUES: OnceCell<HashMap<Card, CardValue>> = OnceCell::new();
trait Value {
fn value(&self) -> CardValue;
}
impl Value for Card {
fn value(&self) -> CardValue {
*VALUES.get_or_init(value_init).get(self).unwrap()
}
}
fn value_init() -> HashMap<Card, CardValue> {
let mut values = HashMap::new();
let new_deck = Cards::new(1, JokersPerDeck::new(2).unwrap()); for (i, card) in new_deck.0.iter().enumerate() {
if i < 53 {
values.insert(*card, CardValue::new((i + 1) as u8).unwrap()); }
}
values.insert(Card::Joker(JokerId::B), CardValue::new(53u8).unwrap());
values
}
fn next_deck_state(mut key_deck: Cards) -> Cards {
assert!(key_deck.move_card_circular(Card::Joker(JokerId::A), 0, 1));
assert!(key_deck.move_card_circular(Card::Joker(JokerId::B), 0, 2));
let mut above_fa = key_deck.draw_till(Card::Joker(JokerId::A)).unwrap();
if let Some(above_both) = above_fa.draw_till(Card::Joker(JokerId::B)) {
let fb = above_fa.draw_count(1).unwrap();
let fa = key_deck.draw_count(1).unwrap();
key_deck.append(fb);
key_deck.append(above_fa);
key_deck.append(fa);
key_deck.append(above_both);
} else if let Some(mut above_fb) = key_deck.draw_till(Card::Joker(JokerId::B)) {
let fa = above_fb.draw_count(1).unwrap();
let fb = key_deck.draw_count(1).unwrap();
key_deck.append(fa);
key_deck.append(above_fb);
key_deck.append(fb);
key_deck.append(above_fa);
}
let bottom_card_value = &key_deck.
look_at(key_deck.0.len() - 1)
.unwrap()
.value();
let TwoStacks(top, mut bottom) = key_deck.cut((*bottom_card_value).into());
let bottom_card = bottom
.0
.pop()
.unwrap();
bottom.append(top);
bottom.append(Cards(vec![bottom_card]));
bottom.clone()
}
pub fn key_deck_from_passphrase(passphrase: &Passphrase) -> Cards {
let mut deck = Cards::new(1, JokersPerDeck::new(2).unwrap());
for letter in passphrase.iter() {
deck = next_deck_state(deck);
let letter_value = letter_into_value(letter);
let TwoStacks(top, mut bottom) = deck.clone().cut(letter_value.into());
let bottom_card = bottom
.0
.pop()
.unwrap();
bottom.append(top);
bottom.append(Cards(vec![bottom_card]));
deck = bottom.clone();
}
deck
}
pub fn get_key_stream(key_deck: Cards, key_length: usize) -> KeyStream {
let mut key_deck = key_deck;
let mut key_stream = KeyStream::new();
let key_length = ((key_length + 4) / 5) * 5; while key_stream.0.len() < key_length {
key_deck = next_deck_state(key_deck);
let top_card_value = &key_deck
.look_at(0)
.unwrap()
.value();
let output_card_candidate_position = card_val_into_position(top_card_value);
let output_card_candidate = &key_deck
.look_at(output_card_candidate_position.into())
.unwrap();
if **output_card_candidate != Card::Joker(JokerId::A)
&& **output_card_candidate != Card::Joker(JokerId::B)
{
key_stream.0.push(value_into_letter(&card_val_into_let_val(
(*output_card_candidate).value(),
)));
}
}
key_stream
}
pub fn encrypt(pt: &PlainText, ks: &KeyStream) -> CypherText {
if pt.0.len() > ks.0.len() {
panic!("KeyStream not long enough");
}
let pt = (*pt).clone();
let mut ct: CypherText = CypherText(vec![]);
for (i, p) in pt.0.iter().enumerate() {
let pt_value = letter_into_value(p);
let key_value = letter_into_value(&ks.0[i]);
ct.0.push(value_into_letter(
&(LetterValue::new(((u8::from(pt_value) + key_value - 1) % 26) + 1).unwrap()),
));
}
ct
}
pub fn decrypt(ct: &CypherText, ks: &KeyStream) -> PlainText {
if ct.0.len() > ks.0.len() {
panic!("KeyStream not long enough");
}
let mut pt: PlainText = PlainText(vec![]);
for (i, c) in ct.0.iter().enumerate() {
let ct_value = letter_into_value(c);
let k_value = letter_into_value(&ks.0[i]);
pt.0.push(value_into_letter(
&(LetterValue::new(
(((i16::from(ct_value) - i16::from(k_value) - 1).rem_euclid(26)) + 1) as u8,
)
.unwrap()),
));
}
pt
}
fn remove_ticks(s: &str) -> String {
s.chars().filter(|c| *c != '\'').collect()
}
#[cfg(test)]
mod tests {
use super::*;
use regex::Regex;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
#[test]
fn test_vectors_from_wiki() {
let pt_re = Regex::new(r"^Plaintext: +([A-Z]+) *$").unwrap();
let ct_re = Regex::new(r"^Ciphertext: +((([A-Z]{5}+) *)+) *$").unwrap();
let key_re = Regex::new(r"^Key: +('([a-z]+)'|(<null key>)) *$").unwrap();
let lines = read_lines("./sol-test.txt");
let lines = lines.expect("failed to open file");
let mut pt: PlainText = PlainText::new();
let mut pp: Passphrase = Passphrase::from_str("").unwrap();
let mut ct: CypherText;
let mut key_deck: Cards;
let mut ks: KeyStream;
let mut got_one = false;
for line in lines {
let line = line.expect("could not read line");
if let Some(pt_str) = pt_re.captures(&line) {
pt = PlainText::from_str(&pt_str[1]).unwrap();
} else if let Some(pp_str) = key_re.captures(&line) {
if let Ok(ppp) = Passphrase::from_str(&pp_str[1]) {
pp = ppp;
} else {
pp = Passphrase::from_str("").unwrap();
}
} else if let Some(ct_str) = ct_re.captures(&line) {
ct = CypherText::from_str(&ct_str[1]).unwrap();
key_deck = Cards::new(1, JokersPerDeck::new(2).unwrap());
if !pp.is_empty() {
key_deck = key_deck_from_passphrase(&pp);
}
ks = get_key_stream(key_deck, pt.0.len());
let computed_ct = encrypt(&pt, &ks);
let computed_ct_str = computed_ct.to_string();
let ct_str = ct.to_string();
assert_eq!(computed_ct_str, ct_str);
let computed_pt = decrypt(&ct, &ks);
let computed_pt_str = computed_pt.to_string();
assert_eq!(computed_pt_str, pt.to_string());
got_one = true;
}
}
assert!(got_one, "failed to run any test vectors");
}
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
}