Skip to content

Commit

Permalink
wordlist: Return a properly parsed password
Browse files Browse the repository at this point in the history
  • Loading branch information
felinira committed Aug 25, 2024
1 parent dcb2149 commit d1210ad
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 29 deletions.
32 changes: 29 additions & 3 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ impl<V: serde::Serialize + Send + Sync + 'static> MailboxConnection<V> {
/// # Ok(()) })}
/// ```
pub async fn create(config: AppConfig<V>, code_length: usize) -> Result<Self, WormholeError> {
Self::create_with_password(
Self::create_with_validated_password(
config,
&wordlist::default_wordlist(code_length).choose_words(),
wordlist::default_wordlist(code_length).choose_words(),
)
.await
}
Expand All @@ -159,14 +159,40 @@ impl<V: serde::Serialize + Send + Sync + 'static> MailboxConnection<V> {
/// let mailbox_connection = MailboxConnection::create_with_password(config, "secret").await?;
/// # Ok(()) })}
/// ```
///
/// TODO: Replace this with create_with_validated_password
pub async fn create_with_password(
config: AppConfig<V>,
password: &str,
) -> Result<Self, WormholeError> {
let password = password.parse().map_err(ParseCodeError::from)?;
Self::create_with_validated_password(config, password).await
}

/// Create a connection to a mailbox which is configured with a `Code` containing the nameplate and the given password.
///
/// # Arguments
///
/// * `config`: Application configuration
/// * `password`: Free text password which will be appended to the nameplate number to form the `Code`
///
/// # Examples
///
/// ```no_run
/// # fn main() -> eyre::Result<()> { async_std::task::block_on(async {
/// use magic_wormhole::{transfer::APP_CONFIG, MailboxConnection};
/// let config = APP_CONFIG;
/// let password: Password = "secret".parse()?;
/// let mailbox_connection = MailboxConnection::create_with_password(config, password).await?;
/// # Ok(()) })}
/// ```
async fn create_with_validated_password(
config: AppConfig<V>,
password: Password,
) -> Result<Self, WormholeError> {
let (mut server, welcome) =
RendezvousServer::connect(&config.id, &config.rendezvous_url).await?;
let (nameplate, mailbox) = server.allocate_claim_open().await?;
let password = password.parse().map_err(ParseCodeError::from)?;
let code = Code::from_components(nameplate, password);

Ok(MailboxConnection {
Expand Down
51 changes: 25 additions & 26 deletions src/core/wordlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use rand::{rngs::OsRng, seq::SliceRandom};
use serde_json::{self, Value};
use std::fmt;

use super::Password;

#[derive(PartialEq)]
pub struct Wordlist {
pub num_words: usize,
Expand Down Expand Up @@ -55,7 +57,7 @@ impl Wordlist {
completions
}

pub fn choose_words(&self) -> String {
pub fn choose_words(&self) -> Password {
let mut rng = OsRng;
let components: Vec<String> = self
.words
Expand All @@ -64,7 +66,10 @@ impl Wordlist {
.take(self.num_words)
.map(|words| words.choose(&mut rng).unwrap().to_string())
.collect();
components.join("-")
#[allow(unsafe_code)]
unsafe {
Password::new_unchecked(components.join("-"))
}
}

#[cfg(feature = "entropy")]
Expand Down Expand Up @@ -132,23 +137,21 @@ mod test {
assert_eq!(d.words[1][255], "zulu");
}

fn vecstrings(all: &str) -> Vec<String> {
fn vec_strs(all: &str) -> Vec<&str> {
all.split_whitespace()
.map(|s| {
if s == "." {
String::from("")
} else {
s.to_string()
}
})
.map(|s| if s == "." { "" } else { s })
.collect()
}

fn vec_strings(all: &str) -> Vec<String> {
vec_strs(all).iter().map(|s| (*s).to_owned()).collect()
}

#[test]
fn test_completion() {
let words: Vec<Vec<String>> = vec![
vecstrings("purple green yellow"),
vecstrings("sausages seltzer snobol"),
vec_strings("purple green yellow"),
vec_strings("sausages seltzer snobol"),
];

let w = Wordlist::new(2, words);
Expand All @@ -160,40 +163,36 @@ mod test {

#[test]
fn test_choose_words() {
let few_words: Vec<Vec<String>> = vec![vecstrings("purple"), vecstrings("sausages")];
let few_words: Vec<Vec<String>> = vec![vec_strings("purple"), vec_strings("sausages")];

let w = Wordlist::new(2, few_words.clone());
assert_eq!(w.choose_words(), "purple-sausages");
assert_eq!(w.choose_words().as_ref(), "purple-sausages");
let w = Wordlist::new(3, few_words.clone());
assert_eq!(w.choose_words(), "purple-sausages-purple");
assert_eq!(w.choose_words().as_ref(), "purple-sausages-purple");
let w = Wordlist::new(4, few_words);
assert_eq!(w.choose_words(), "purple-sausages-purple-sausages");
assert_eq!(w.choose_words().as_ref(), "purple-sausages-purple-sausages");
}

#[test]
fn test_choose_more_words() {
let more_words: Vec<Vec<String>> =
vec![vecstrings("purple yellow"), vecstrings("sausages")];
let more_words = vec![vec_strings("purple yellow"), vec_strings("sausages")];

let expected2 = vecstrings("purple-sausages yellow-sausages");
let expected3: Vec<String> = vec![
let expected2 = vec_strs("purple-sausages yellow-sausages");
let expected3 = vec![
"purple-sausages-purple",
"yellow-sausages-purple",
"purple-sausages-yellow",
"yellow-sausages-yellow",
]
.iter()
.map(|s| s.to_string())
.collect();
];

let w = Wordlist::new(2, more_words.clone());
for _ in 0..20 {
assert!(expected2.contains(&w.choose_words()));
assert!(expected2.contains(&w.choose_words().as_ref()));
}

let w = Wordlist::new(3, more_words);
for _ in 0..20 {
assert!(expected3.contains(&w.choose_words()));
assert!(expected3.contains(&w.choose_words().as_ref()));
}
}

Expand Down

0 comments on commit d1210ad

Please sign in to comment.