| author | |
| committer | |
| log | 4c59412d43f497f29b4c3d4b9695134f634cf17f |
| tree | 36a33513df8e6702577e1bd1fe1edfc298201f41 |
| parent | e1eb16f90d89d57b6c0a1003cb2a9468c121c530 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
Two random words made codes like "678-scary-virus". A code is now nine
symbols of Crockford base32 (0-9 and A-Z without I, L, O and U), shown in
threes: two numbering the relay's room (it numbers 1 to 999, which two
symbols hold), six of secret, 30 bits, and a check symbol, the symbols
weighted 1 to 8 modulo 31, which refuses a mistyped symbol or two swapped
before it spends one of the relay's tries. Reading ignores case, hyphens and
spaces and takes I and L for 1, O for 0.
With five tries before a code burns, a guess at one code succeeds with
odds 5 in 2^30, about 1 in 215 million; every burn draws a new secret, and
the relay's lockout leaves one address about ten tries an hour. The relay
is unchanged: rooms are still code-<n>. A word code and a new code each read
as malformed on the other version, before any network.
Assisted-by: claude-opus-5.510 files changed, 233 insertions(+), 1390 deletions(-)
arc/sync.md+2-1| ... | ... | @@ -320,7 +320,8 @@ in the share's room; a guest runs the replica, queue and merge it runs on an SMB |
| 320 | 320 | them (`Notebook::open_hosted`, `Section::resume_hosted`, `Background::hosted`), so offline |
| 321 | 321 | queueing, rebases and conflict pages behave as on a share, and the host's files are only ever |
| 322 | 322 | written by its own storage, OneNote's locks included. A guest meets the host first in the |
| 323 | room of a short code (`412-violet-otter`, SPAKE2 on its words and any password) through | |
| 323 | room of a short code (`7KQ-4MZ-9XR`: Crockford base32, its room's number, 30 bits of | |
| 324 | secret and a check symbol; SPAKE2 on its secret and any password) through | |
| 324 | 325 | Snowbound's relay or by mDNS, and is welcomed with the share's room and its random secret; |
| 325 | 326 | stopping or restarting a share retires the secret. A guest's commit is checked on the host's |
| 326 | 327 | image before it is committed, and a guest can name nothing outside the notebook. Large reads |
crates/notebook/README.md+5-3| ... | ... | @@ -538,10 +538,12 @@ physical power-loss durability. |
| 538 | 538 | `live::Live::start(hello, room, reach, relay, events)` listens on a TCP port and, with a |
| 539 | 539 | `Reach`, advertises `_snowbound._tcp` by mDNS on every network or on loopback alone, |
| 540 | 540 | connecting to the peers in the same `Room` that it finds: a notebook's or a share's random |
| 541 | secret (`Room::Notebook`), or a code typed on both (`Room::join("4-violet-otter", password)`). | |
| 541 | secret (`Room::Notebook`), or a code typed on both (`Room::join("7KQ-4MZ-9XR", password)`). | |
| 542 | 542 | With a `relay` (`wss://live.example.net`, `crates/relay`) it also joins the room there and |
| 543 | 543 | meets its peers through it; the end sharing a code (`Room::share`) has the relay number its |
| 544 | words, `code()` then has the whole code, and it burns a code after too many wrong tries. | |
| 544 | secret, `code()` then has the whole code, and it burns a code after too many wrong tries. | |
| 545 | Codes are Crockford base32 (`live::code`): two symbols numbering the room, six of secret | |
| 546 | (30 bits) and a check symbol that refuses a typo before it spends one of the relay's tries. | |
| 545 | 547 | `connect(address)` meets a peer discovery did not find. Peers meet through SPAKE2 on the |
| 546 | 548 | room's secret, then every frame is AES-256-GCM under the keys it agreed: its number, which is |
| 547 | 549 | also its nonce, then a message kind and a CBOR map (`live::wire`). A frame lost, repeated, |
| ... | ... | @@ -558,7 +560,7 @@ a notebook's presence room, kept in `.snowbound/live.json` and made where it has |
| 558 | 560 | `live::share` is Live Share. `Host::start(storage, hello, sharing, name, reach, relay, events)` |
| 559 | 561 | serves `Notebook::into_storage()` to the peers in the share's room and welcomes whoever knows |
| 560 | 562 | `Sharing::code` (and its password) from the code's room; `code()` replaces a burned code with |
| 561 | new words, `guests()` lists who is connected, `touched(paths)` passes the host's own changes | |
| 563 | a new secret, `guests()` lists who is connected, `touched(paths)` passes the host's own changes | |
| 562 | 564 | on, and `stop()` lets every guest go. `join(hello, code, password, reach, relay)` returns the |
| 563 | 565 | `Welcome` (the share and its secret), or a `Refusal` saying why not: a wrong code, no one |
| 564 | 566 | sharing it, an expired code, too many wrong tries, or no relay. `Guest::start` joins the share; |
crates/notebook/src/live.rs+25-25| ... | ... | @@ -7,6 +7,7 @@ |
| 7 | 7 | //! and one writing. A connection whose frames arrive out of order is dropped and met again |
| 8 | 8 | //! from scratch. |
| 9 | 9 | |
| 10 | pub mod code; | |
| 10 | 11 | mod relay; |
| 11 | 12 | pub mod share; |
| 12 | 13 | pub mod wire; |
| ... | ... | @@ -44,10 +45,10 @@ const TRIES: u32 = 5; |
| 44 | 45 | pub enum Room { |
| 45 | 46 | /// A notebook's room, or a share's: a random secret only its members hold. |
| 46 | 47 | Notebook([u8; 16]), |
| 47 | /// A code typed on both ends, `412-violet-otter`, with a password where one is set: its | |
| 48 | /// number names it on the network, and its words and password only the two people know. | |
| 48 | /// A code typed on both ends (`code`), with a password where one is set: its room's | |
| 49 | /// number names it on the network, and its secret and password only the two people know. | |
| 49 | 50 | /// Its `owner`, the end sharing it, takes the number from a relay (the one the code has, |
| 50 | /// coming back; any free one for words alone) or picks one where it has no relay, and | |
| 51 | /// coming back; any free one for a secret alone) or picks one where it has no relay, and | |
| 51 | 52 | /// judges every try, burning the code after too many wrong ones. |
| 52 | 53 | Code { |
| 53 | 54 | code: String, |
| ... | ... | @@ -60,16 +61,16 @@ impl Room { |
| 60 | 61 | /// The room a code typed on this end leads to. |
| 61 | 62 | pub fn join(code: &str, password: &str) -> Self { |
| 62 | 63 | Self::Code { |
| 63 | code: code.trim().to_lowercase(), | |
| 64 | code: code.to_owned(), | |
| 64 | 65 | password: password.to_owned(), |
| 65 | 66 | owner: false, |
| 66 | 67 | } |
| 67 | 68 | } |
| 68 | 69 | |
| 69 | /// The room of a code this end shares: its words, or a whole code to keep its number. | |
| 70 | /// The room of a code this end shares: its secret, or a whole code to keep its number. | |
| 70 | 71 | pub fn share(code: &str, password: &str) -> Self { |
| 71 | 72 | Self::Code { |
| 72 | code: code.trim().to_lowercase(), | |
| 73 | code: code.to_owned(), | |
| 73 | 74 | password: password.to_owned(), |
| 74 | 75 | owner: true, |
| 75 | 76 | } |
| ... | ... | @@ -90,7 +91,7 @@ impl Room { |
| 90 | 91 | match self { |
| 91 | 92 | Room::Notebook(id) => id.to_vec(), |
| 92 | 93 | Room::Code { code, password, .. } => { |
| 93 | let mut secret = code_parts(code).1.to_lowercase().into_bytes(); | |
| 94 | let mut secret = code_parts(code).1.into_bytes(); | |
| 94 | 95 | if !password.is_empty() { |
| 95 | 96 | secret.push(b'\n'); |
| 96 | 97 | secret.extend_from_slice(password.as_bytes()); |
| ... | ... | @@ -105,12 +106,11 @@ impl Room { |
| 105 | 106 | } |
| 106 | 107 | } |
| 107 | 108 | |
| 108 | /// A code's number, if it has one, and its words. | |
| 109 | fn code_parts(code: &str) -> (Option<u32>, &str) { | |
| 110 | let code = code.trim(); | |
| 111 | match code.split_once('-') { | |
| 112 | Some((number, words)) if number.parse::<u32>().is_ok() => (number.parse().ok(), words), | |
| 113 | _ => (None, code), | |
| 109 | /// A code's room number, if it has one, and its secret: a whole code, or a secret alone. | |
| 110 | fn code_parts(code: &str) -> (Option<u32>, String) { | |
| 111 | match code::parse(code) { | |
| 112 | Some((number, secret)) => (Some(number), secret), | |
| 113 | None => (None, code.trim().to_uppercase()), | |
| 114 | 114 | } |
| 115 | 115 | } |
| 116 | 116 | |
| ... | ... | @@ -309,18 +309,18 @@ impl Live { |
| 309 | 309 | address.set_ip(IpAddr::V4(Ipv4Addr::LOCALHOST)); |
| 310 | 310 | } |
| 311 | 311 | let code = match room { |
| 312 | Room::Code { code, .. } if room.tag().is_some() => Some(code.clone()), | |
| 313 | // Off any relay, the end sharing words alone numbers them itself. | |
| 314 | Room::Code { code, .. } if relay.is_none() => { | |
| 315 | let mut number = [0; 2]; | |
| 316 | getrandom::fill(&mut number) | |
| 317 | .map_err(|_| io::Error::other("System random source failed"))?; | |
| 318 | Some(format!( | |
| 319 | "{}-{code}", | |
| 320 | 1000 + u16::from_le_bytes(number) % 9000 | |
| 321 | )) | |
| 322 | } | |
| 323 | _ => None, | |
| 312 | Room::Code { code, .. } => match code_parts(code) { | |
| 313 | (Some(number), secret) => code::format(number, &secret), | |
| 314 | // Off any relay, the end sharing a secret alone numbers it itself. | |
| 315 | (None, secret) if relay.is_none() => { | |
| 316 | let mut number = [0; 4]; | |
| 317 | getrandom::fill(&mut number) | |
| 318 | .map_err(|_| io::Error::other("System random source failed"))?; | |
| 319 | code::format(u32::from_le_bytes(number) % code::NAMEPLATES, &secret) | |
| 320 | } | |
| 321 | (None, _) => None, | |
| 322 | }, | |
| 323 | Room::Notebook(_) => None, | |
| 324 | 324 | }; |
| 325 | 325 | let shared = Arc::new(Shared { |
| 326 | 326 | me, |
crates/notebook/src/live/code.rs created+148| ... | ... | @@ -0,0 +1,148 @@ |
| 1 | //! Live Share's codes in Crockford's base32 (0-9 and A-Z without I, L, O and U), shown | |
| 2 | //! `7KQ-4MZ-9XR`: two symbols naming the code's room, its number on a relay; six of secret | |
| 3 | //! (30 bits), which SPAKE2 meets through; and a check symbol, so a mistyped code is refused | |
| 4 | //! here before it spends one of the relay's few tries. Reading ignores case, hyphens and | |
| 5 | //! spaces, and takes I and L for 1 and O for 0, as Crockford's decoding does. | |
| 6 | //! | |
| 7 | //! The check is the symbols' values weighted 1 to 8, summed modulo 31, the prime under 32, so | |
| 8 | //! it stays one of the code's own symbols: it catches any symbol mistyped and any two | |
| 9 | //! neighbours swapped, but for 0 and Z, whose values differ by 31. Crockford's own check | |
| 10 | //! symbol, modulo 37, adds `*~$=U`, which read badly aloud. | |
| 11 | ||
| 12 | use std::io; | |
| 13 | ||
| 14 | const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ"; | |
| 15 | /// The symbols naming a code's room, and how many rooms they name. | |
| 16 | const NAMEPLATE: usize = 2; | |
| 17 | pub const NAMEPLATES: u32 = 1 << (5 * NAMEPLATE); | |
| 18 | /// The symbols of a code's secret. | |
| 19 | pub const SECRET: usize = 6; | |
| 20 | ||
| 21 | /// A new secret, `SECRET` random symbols. | |
| 22 | pub fn secret() -> io::Result<String> { | |
| 23 | let mut bytes = [0; 4]; | |
| 24 | getrandom::fill(&mut bytes).map_err(|_| io::Error::other("System random source failed"))?; | |
| 25 | let bits = u32::from_le_bytes(bytes); | |
| 26 | Ok((0..SECRET) | |
| 27 | .map(|at| symbol((bits >> (5 * at)) as u8)) | |
| 28 | .collect()) | |
| 29 | } | |
| 30 | ||
| 31 | fn symbol(value: u8) -> char { | |
| 32 | char::from(ALPHABET[usize::from(value & 31)]) | |
| 33 | } | |
| 34 | ||
| 35 | /// A symbol's value as typed, reading I and L as 1 and O as 0. | |
| 36 | fn value(typed: char) -> Option<u8> { | |
| 37 | let typed = match typed.to_ascii_uppercase() { | |
| 38 | 'I' | 'L' => '1', | |
| 39 | 'O' => '0', | |
| 40 | typed => typed, | |
| 41 | }; | |
| 42 | ALPHABET | |
| 43 | .iter() | |
| 44 | .position(|symbol| char::from(*symbol) == typed) | |
| 45 | .map(|at| at as u8) | |
| 46 | } | |
| 47 | ||
| 48 | fn check(values: &[u8]) -> u8 { | |
| 49 | let sum: u32 = (values.iter().enumerate()) | |
| 50 | .map(|(at, value)| (at as u32 + 1) * u32::from(*value)) | |
| 51 | .sum(); | |
| 52 | (sum % 31) as u8 | |
| 53 | } | |
| 54 | ||
| 55 | /// The code for room `nameplate` and `secret`, as shown: `7KQ-4MZ-9XR`. A secret that isn't | |
| 56 | /// `SECRET` symbols has no code. | |
| 57 | pub fn format(nameplate: u32, secret: &str) -> Option<String> { | |
| 58 | let secret: Vec<u8> = secret.chars().map(value).collect::<Option<_>>()?; | |
| 59 | if nameplate >= NAMEPLATES || secret.len() != SECRET { | |
| 60 | return None; | |
| 61 | } | |
| 62 | let mut values = vec![(nameplate >> 5) as u8, (nameplate & 31) as u8]; | |
| 63 | values.extend(secret); | |
| 64 | values.push(check(&values)); | |
| 65 | let symbols: Vec<char> = values.into_iter().map(symbol).collect(); | |
| 66 | Some( | |
| 67 | symbols | |
| 68 | .chunks(3) | |
| 69 | .map(|group| group.iter().collect::<String>()) | |
| 70 | .collect::<Vec<_>>() | |
| 71 | .join("-"), | |
| 72 | ) | |
| 73 | } | |
| 74 | ||
| 75 | /// A code as typed: its room's number and its secret, where it is a code whose check holds. | |
| 76 | pub fn parse(typed: &str) -> Option<(u32, String)> { | |
| 77 | let values: Vec<u8> = typed | |
| 78 | .chars() | |
| 79 | .filter(|c| *c != '-' && !c.is_whitespace()) | |
| 80 | .map(value) | |
| 81 | .collect::<Option<_>>()?; | |
| 82 | let (check_value, values) = values.split_last()?; | |
| 83 | if values.len() != NAMEPLATE + SECRET || check(values) != *check_value { | |
| 84 | return None; | |
| 85 | } | |
| 86 | let nameplate = (u32::from(values[0]) << 5) | u32::from(values[1]); | |
| 87 | let secret = values[NAMEPLATE..].iter().copied().map(symbol).collect(); | |
| 88 | Some((nameplate, secret)) | |
| 89 | } | |
| 90 | ||
| 91 | #[cfg(test)] | |
| 92 | mod tests { | |
| 93 | use super::*; | |
| 94 | ||
| 95 | #[test] | |
| 96 | fn codes_read_back_however_they_are_typed() { | |
| 97 | let code = format(412, "4MZ9XR").unwrap(); | |
| 98 | assert_eq!(code.len(), 11); | |
| 99 | assert_eq!(parse(&code), Some((412, "4MZ9XR".into()))); | |
| 100 | let typed = code | |
| 101 | .to_lowercase() | |
| 102 | .replace('-', " ") | |
| 103 | .replace('1', "l") | |
| 104 | .replace('0', "o"); | |
| 105 | assert_eq!(parse(&typed), Some((412, "4MZ9XR".into()))); | |
| 106 | assert_eq!(parse(&format!(" {code} ")), Some((412, "4MZ9XR".into()))); | |
| 107 | assert!(format(NAMEPLATES, "4MZ9XR").is_none() && format(1, "4MZ9X").is_none()); | |
| 108 | assert!(parse("412-violet-otter").is_none() && parse("").is_none()); | |
| 109 | for _ in 0..100 { | |
| 110 | let secret = secret().unwrap(); | |
| 111 | assert_eq!(secret.len(), SECRET); | |
| 112 | assert_eq!(parse(&format(7, &secret).unwrap()), Some((7, secret))); | |
| 113 | } | |
| 114 | } | |
| 115 | ||
| 116 | /// The check refuses any one symbol mistyped, and any two neighbours swapped, but for 0 | |
| 117 | /// and Z. | |
| 118 | #[test] | |
| 119 | fn the_check_catches_a_symbol_mistyped_or_two_swapped() { | |
| 120 | let code: Vec<char> = format(999, "Q4MZ9X") | |
| 121 | .unwrap() | |
| 122 | .replace('-', "") | |
| 123 | .chars() | |
| 124 | .collect(); | |
| 125 | for at in 0..code.len() { | |
| 126 | for symbol in ALPHABET.iter().map(|byte| char::from(*byte)) { | |
| 127 | let mut typed = code.clone(); | |
| 128 | if typed[at] != symbol && !matches!((typed[at], symbol), ('0', 'Z') | ('Z', '0')) { | |
| 129 | typed[at] = symbol; | |
| 130 | assert!( | |
| 131 | parse(&typed.iter().collect::<String>()).is_none(), | |
| 132 | "{typed:?}" | |
| 133 | ); | |
| 134 | } | |
| 135 | } | |
| 136 | } | |
| 137 | for at in 0..code.len() - 1 { | |
| 138 | let mut typed = code.clone(); | |
| 139 | typed.swap(at, at + 1); | |
| 140 | if typed != code { | |
| 141 | assert!( | |
| 142 | parse(&typed.iter().collect::<String>()).is_none(), | |
| 143 | "{typed:?}" | |
| 144 | ); | |
| 145 | } | |
| 146 | } | |
| 147 | } | |
| 148 | } |
crates/notebook/src/live/relay.rs+4-2| ... | ... | @@ -372,10 +372,12 @@ fn session( |
| 372 | 372 | Ok(Notice::Nameplate(number)) => { |
| 373 | 373 | *nameplate = Some(number); |
| 374 | 374 | tag = Some(format!("code-{number}")); |
| 375 | let super::Room::Code { code: words, .. } = &shared.room else { | |
| 375 | let super::Room::Code { code, .. } = &shared.room else { | |
| 376 | continue; | |
| 377 | }; | |
| 378 | let Some(code) = super::code::format(number, &code_parts(code).1) else { | |
| 376 | 379 | continue; |
| 377 | 380 | }; |
| 378 | let code = format!("{number}-{}", code_parts(words).1); | |
| 379 | 381 | let mut state = shared.state.lock().unwrap(); |
| 380 | 382 | if state.code.as_ref() != Some(&code) { |
| 381 | 383 | state.code = Some(code); |
crates/notebook/src/live/share.rs+11-45| ... | ... | @@ -6,9 +6,6 @@ |
| 6 | 6 | //! the host welcomes it with the share's room and secret; a new share has a new secret, so |
| 7 | 7 | //! stopping retires every guest. Large bodies travel a chunk at a time, each answered before |
| 8 | 8 | //! the next, so a relay never holds much for a slow peer. |
| 9 | //! | |
| 10 | //! The code's words come from the EFF's short word list | |
| 11 | //! (<https://www.eff.org/dice>, CC BY 3.0 US), `yo-yo` replaced by `yarn`. | |
| 12 | 9 | |
| 13 | 10 | use super::{ |
| 14 | 11 | Event, Hello, Line, Live, Peer, Presence, Reach, Relayed, Room, |
| ... | ... | @@ -55,42 +52,11 @@ const WORKERS: usize = 2; |
| 55 | 52 | const STARTS: f64 = 100.0; |
| 56 | 53 | const BURST: f64 = 200.0; |
| 57 | 54 | |
| 58 | const WORDS: &str = include_str!("words.txt"); | |
| 59 | ||
| 60 | /// Two random words for a code, `violet-otter`. | |
| 61 | pub fn words() -> io::Result<String> { | |
| 62 | let list: Vec<&str> = WORDS.lines().collect(); | |
| 63 | let mut bytes = [0; 8]; | |
| 64 | getrandom::fill(&mut bytes).map_err(|_| io::Error::other("System random source failed"))?; | |
| 65 | let [first, second] = [&bytes[..4], &bytes[4..]] | |
| 66 | .map(|bytes| u32::from_le_bytes(bytes.try_into().expect("4 bytes")) as usize); | |
| 67 | let first = first % list.len(); | |
| 68 | let mut second = second % (list.len() - 1); | |
| 69 | if second >= first { | |
| 70 | second += 1; | |
| 71 | } | |
| 72 | Ok(format!("{}-{}", list[first], list[second])) | |
| 73 | } | |
| 74 | ||
| 75 | /// A code as typed, `412 Violet otter`, in the form it is met by, `412-violet-otter`; none | |
| 76 | /// where it is not a number and two words. | |
| 55 | /// A code as typed, `7kq 4mz 9xr`, as it is shown, `7KQ-4MZ-9XR`; none where it is not a | |
| 56 | /// code (`super::code`). | |
| 77 | 57 | pub fn code(typed: &str) -> Option<String> { |
| 78 | let parts: Vec<String> = typed | |
| 79 | .split(|c: char| c.is_whitespace() || c == '-') | |
| 80 | .filter(|part| !part.is_empty()) | |
| 81 | .map(str::to_lowercase) | |
| 82 | .collect(); | |
| 83 | match &parts[..] { | |
| 84 | [number, first, second] | |
| 85 | if number.parse::<u32>().is_ok() | |
| 86 | && [first, second] | |
| 87 | .iter() | |
| 88 | .all(|word| word.chars().all(|c| c.is_ascii_lowercase())) => | |
| 89 | { | |
| 90 | Some(parts.join("-")) | |
| 91 | } | |
| 92 | _ => None, | |
| 93 | } | |
| 58 | let (number, secret) = super::code::parse(typed)?; | |
| 59 | super::code::format(number, &secret) | |
| 94 | 60 | } |
| 95 | 61 | |
| 96 | 62 | /// What a host keeps of a share to take it up again after a relaunch. |
| ... | ... | @@ -99,13 +65,13 @@ pub struct Sharing { |
| 99 | 65 | pub share: [u8; 16], |
| 100 | 66 | /// The share room's secret, which every guest welcomed holds. |
| 101 | 67 | pub secret: [u8; 16], |
| 102 | /// The code: its words, with its number in front once one was given. | |
| 68 | /// The code, or its secret alone until it has a number (`super::code`). | |
| 103 | 69 | pub code: String, |
| 104 | 70 | pub password: String, |
| 105 | 71 | } |
| 106 | 72 | |
| 107 | 73 | impl Sharing { |
| 108 | /// A new share: its own id and secret, and new words. | |
| 74 | /// A new share: its own id, room secret and code. | |
| 109 | 75 | pub fn new(password: &str) -> io::Result<Self> { |
| 110 | 76 | let mut random = [0; 32]; |
| 111 | 77 | getrandom::fill(&mut random) |
| ... | ... | @@ -113,7 +79,7 @@ impl Sharing { |
| 113 | 79 | Ok(Self { |
| 114 | 80 | share: random[..16].try_into().expect("16 bytes"), |
| 115 | 81 | secret: random[16..].try_into().expect("16 bytes"), |
| 116 | code: words()?, | |
| 82 | code: super::code::secret()?, | |
| 117 | 83 | password: password.to_owned(), |
| 118 | 84 | }) |
| 119 | 85 | } |
| ... | ... | @@ -127,9 +93,9 @@ pub fn location(share: &[u8; 16]) -> String { |
| 127 | 93 | /// Why joining failed. |
| 128 | 94 | #[derive(Clone, Debug, PartialEq, Eq)] |
| 129 | 95 | pub enum Refusal { |
| 130 | /// Not a number and two words. | |
| 96 | /// Not a code, or one mistyped, which spends none of the relay's tries. | |
| 131 | 97 | Malformed, |
| 132 | /// The code's words or password are wrong. | |
| 98 | /// The code's secret or password is wrong. | |
| 133 | 99 | Wrong, |
| 134 | 100 | /// No one shares with the code's number now. |
| 135 | 101 | NoOne, |
| ... | ... | @@ -281,13 +247,13 @@ impl Host { |
| 281 | 247 | } |
| 282 | 248 | |
| 283 | 249 | /// The code guests type, once it has its number, and none once stopped. A code with too |
| 284 | /// many wrong tries is replaced by one with new words. | |
| 250 | /// many wrong tries is replaced by one with a new secret. | |
| 285 | 251 | pub fn code(&self) -> Option<String> { |
| 286 | 252 | let mut pairing = self.pairing.lock().unwrap(); |
| 287 | 253 | let pairing = pairing.as_mut()?; |
| 288 | 254 | if pairing.burned() { |
| 289 | 255 | let mut sharing = self.sharing.lock().unwrap(); |
| 290 | sharing.code = words().ok()?; | |
| 256 | sharing.code = super::code::secret().ok()?; | |
| 291 | 257 | let relay = self.relay.as_deref(); |
| 292 | 258 | *pairing = pair( |
| 293 | 259 | &self.me, |
crates/notebook/src/live/tests.rs+13-8| ... | ... | @@ -1,6 +1,11 @@ |
| 1 | 1 | use super::*; |
| 2 | 2 | use std::time::Instant; |
| 3 | 3 | |
| 4 | /// The code for room `number` and `secret`. | |
| 5 | fn code(number: u32, secret: &str) -> String { | |
| 6 | super::code::format(number, secret).unwrap() | |
| 7 | } | |
| 8 | ||
| 4 | 9 | fn hello(name: &str) -> Hello { |
| 5 | 10 | Hello::new(name.into(), Some(vec![1, 2, 3])).unwrap() |
| 6 | 11 | } |
| ... | ... | @@ -43,7 +48,7 @@ fn caret(offset: u32) -> Presence { |
| 43 | 48 | /// caret, and see the other leave. |
| 44 | 49 | #[test] |
| 45 | 50 | fn peers_meet_and_follow_presence() { |
| 46 | let room = Room::join("7-violet-otter", ""); | |
| 51 | let room = Room::join(&code(7, "ABCDEF"), ""); | |
| 47 | 52 | let ada = Live::start(hello("Ada"), &room, None, None, |_| {}).unwrap(); |
| 48 | 53 | let grace = Live::start(hello("Grace"), &room, None, None, |_| {}).unwrap(); |
| 49 | 54 | ada.set_presence(caret(1)); |
| ... | ... | @@ -70,7 +75,7 @@ fn peers_meet_and_follow_presence() { |
| 70 | 75 | fn another_code_never_meets() { |
| 71 | 76 | let ada = Live::start( |
| 72 | 77 | hello("Ada"), |
| 73 | &Room::join("7-violet-otter", ""), | |
| 78 | &Room::join(&code(7, "ABCDEF"), ""), | |
| 74 | 79 | None, |
| 75 | 80 | None, |
| 76 | 81 | |_| {}, |
| ... | ... | @@ -78,7 +83,7 @@ fn another_code_never_meets() { |
| 78 | 83 | .unwrap(); |
| 79 | 84 | let mallory = Live::start( |
| 80 | 85 | hello("Mallory"), |
| 81 | &Room::join("7-violet-ocelot", ""), | |
| 86 | &Room::join(&code(7, "ABCDEG"), ""), | |
| 82 | 87 | None, |
| 83 | 88 | None, |
| 84 | 89 | |_| {}, |
| ... | ... | @@ -115,7 +120,7 @@ fn later_fields_and_kinds_are_skipped() { |
| 115 | 120 | } |
| 116 | 121 | ); |
| 117 | 122 | |
| 118 | let room = Room::join("4-quiet-heron", ""); | |
| 123 | let room = Room::join(&code(4, "QJETHR"), ""); | |
| 119 | 124 | let grace = Live::start(hello("Grace"), &room, None, None, |_| {}).unwrap(); |
| 120 | 125 | // A later version: it greets, says something new, then where it is. |
| 121 | 126 | let later = thread::spawn(move || { |
| ... | ... | @@ -298,7 +303,7 @@ fn a_relay_numbers_a_code_and_burns_it_after_wrong_tries() { |
| 298 | 303 | }); |
| 299 | 304 | let host = Live::start( |
| 300 | 305 | hello("Ada"), |
| 301 | &Room::share("violet-otter", ""), | |
| 306 | &Room::share("ABCDEF", ""), | |
| 302 | 307 | None, |
| 303 | 308 | Some(&url), |
| 304 | 309 | |_| {}, |
| ... | ... | @@ -312,8 +317,8 @@ fn a_relay_numbers_a_code_and_burns_it_after_wrong_tries() { |
| 312 | 317 | assert!(Instant::now() < deadline, "no code"); |
| 313 | 318 | thread::sleep(Duration::from_millis(20)); |
| 314 | 319 | }; |
| 315 | let (number, words) = code.split_once('-').unwrap(); | |
| 316 | assert_eq!(words, "violet-otter"); | |
| 320 | let (number, secret) = super::code::parse(&code).unwrap(); | |
| 321 | assert_eq!(secret, "ABCDEF"); | |
| 317 | 322 | let guest = Live::start( |
| 318 | 323 | hello("Grace"), |
| 319 | 324 | &Room::join(&code, ""), |
| ... | ... | @@ -326,7 +331,7 @@ fn a_relay_numbers_a_code_and_burns_it_after_wrong_tries() { |
| 326 | 331 | until(&guest, |peers| peers.len() == 1); |
| 327 | 332 | |
| 328 | 333 | let path = format!("/v1/room/code-{number}"); |
| 329 | let wrong = Room::join(&format!("{number}-violet-ocelot"), ""); | |
| 334 | let wrong = Room::join(&self::code(number, "ABCDEG"), ""); | |
| 330 | 335 | // An end that typed a wrong code gives up at once; Mallory tries twice, and the second |
| 331 | 336 | // wrong try burns the code. |
| 332 | 337 | for _ in 0..2 { |
crates/notebook/src/live/words.txt deleted-1296| ... | ... | @@ -1,1296 +0,0 @@ |
| 1 | acid | |
| 2 | acorn | |
| 3 | acre | |
| 4 | acts | |
| 5 | afar | |
| 6 | affix | |
| 7 | aged | |
| 8 | agent | |
| 9 | agile | |
| 10 | aging | |
| 11 | agony | |
| 12 | ahead | |
| 13 | aide | |
| 14 | aids | |
| 15 | aim | |
| 16 | ajar | |
| 17 | alarm | |
| 18 | alias | |
| 19 | alibi | |
| 20 | alien | |
| 21 | alike | |
| 22 | alive | |
| 23 | aloe | |
| 24 | aloft | |
| 25 | aloha | |
| 26 | alone | |
| 27 | amend | |
| 28 | amino | |
| 29 | ample | |
| 30 | amuse | |
| 31 | angel | |
| 32 | anger | |
| 33 | angle | |
| 34 | ankle | |
| 35 | apple | |
| 36 | april | |
| 37 | apron | |
| 38 | aqua | |
| 39 | area | |
| 40 | arena | |
| 41 | argue | |
| 42 | arise | |
| 43 | armed | |
| 44 | armor | |
| 45 | army | |
| 46 | aroma | |
| 47 | array | |
| 48 | arson | |
| 49 | art | |
| 50 | ashen | |
| 51 | ashes | |
| 52 | atlas | |
| 53 | atom | |
| 54 | attic | |
| 55 | audio | |
| 56 | avert | |
| 57 | avoid | |
| 58 | awake | |
| 59 | award | |
| 60 | awoke | |
| 61 | axis | |
| 62 | bacon | |
| 63 | badge | |
| 64 | bagel | |
| 65 | baggy | |
| 66 | baked | |
| 67 | baker | |
| 68 | balmy | |
| 69 | banjo | |
| 70 | barge | |
| 71 | barn | |
| 72 | bash | |
| 73 | basil | |
| 74 | bask | |
| 75 | batch | |
| 76 | bath | |
| 77 | baton | |
| 78 | bats | |
| 79 | blade | |
| 80 | blank | |
| 81 | blast | |
| 82 | blaze | |
| 83 | bleak | |
| 84 | blend | |
| 85 | bless | |
| 86 | blimp | |
| 87 | blink | |
| 88 | bloat | |
| 89 | blob | |
| 90 | blog | |
| 91 | blot | |
| 92 | blunt | |
| 93 | blurt | |
| 94 | blush | |
| 95 | boast | |
| 96 | boat | |
| 97 | body | |
| 98 | boil | |
| 99 | bok | |
| 100 | bolt | |
| 101 | boned | |
| 102 | boney | |
| 103 | bonus | |
| 104 | bony | |
| 105 | book | |
| 106 | booth | |
| 107 | boots | |
| 108 | boss | |
| 109 | botch | |
| 110 | both | |
| 111 | boxer | |
| 112 | breed | |
| 113 | bribe | |
| 114 | brick | |
| 115 | bride | |
| 116 | brim | |
| 117 | bring | |
| 118 | brink | |
| 119 | brisk | |
| 120 | broad | |
| 121 | broil | |
| 122 | broke | |
| 123 | brook | |
| 124 | broom | |
| 125 | brush | |
| 126 | buck | |
| 127 | bud | |
| 128 | buggy | |
| 129 | bulge | |
| 130 | bulk | |
| 131 | bully | |
| 132 | bunch | |
| 133 | bunny | |
| 134 | bunt | |
| 135 | bush | |
| 136 | bust | |
| 137 | busy | |
| 138 | buzz | |
| 139 | cable | |
| 140 | cache | |
| 141 | cadet | |
| 142 | cage | |
| 143 | cake | |
| 144 | calm | |
| 145 | cameo | |
| 146 | canal | |
| 147 | candy | |
| 148 | cane | |
| 149 | canon | |
| 150 | cape | |
| 151 | card | |
| 152 | cargo | |
| 153 | carol | |
| 154 | carry | |
| 155 | carve | |
| 156 | case | |
| 157 | cash | |
| 158 | cause | |
| 159 | cedar | |
| 160 | chain | |
| 161 | chair | |
| 162 | chant | |
| 163 | chaos | |
| 164 | charm | |
| 165 | chase | |
| 166 | cheek | |
| 167 | cheer | |
| 168 | chef | |
| 169 | chess | |
| 170 | chest | |
| 171 | chew | |
| 172 | chief | |
| 173 | chili | |
| 174 | chill | |
| 175 | chip | |
| 176 | chomp | |
| 177 | chop | |
| 178 | chow | |
| 179 | chuck | |
| 180 | chump | |
| 181 | chunk | |
| 182 | churn | |
| 183 | chute | |
| 184 | cider | |
| 185 | cinch | |
| 186 | city | |
| 187 | civic | |
| 188 | civil | |
| 189 | clad | |
| 190 | claim | |
| 191 | clamp | |
| 192 | clap | |
| 193 | clash | |
| 194 | clasp | |
| 195 | class | |
| 196 | claw | |
| 197 | clay | |
| 198 | clean | |
| 199 | clear | |
| 200 | cleat | |
| 201 | cleft | |
| 202 | clerk | |
| 203 | click | |
| 204 | cling | |
| 205 | clink | |
| 206 | clip | |
| 207 | cloak | |
| 208 | clock | |
| 209 | clone | |
| 210 | cloth | |
| 211 | cloud | |
| 212 | clump | |
| 213 | coach | |
| 214 | coast | |
| 215 | coat | |
| 216 | cod | |
| 217 | coil | |
| 218 | coke | |
| 219 | cola | |
| 220 | cold | |
| 221 | colt | |
| 222 | coma | |
| 223 | come | |
| 224 | comic | |
| 225 | comma | |
| 226 | cone | |
| 227 | cope | |
| 228 | copy | |
| 229 | coral | |
| 230 | cork | |
| 231 | cost | |
| 232 | cot | |
| 233 | couch | |
| 234 | cough | |
| 235 | cover | |
| 236 | cozy | |
| 237 | craft | |
| 238 | cramp | |
| 239 | crane | |
| 240 | crank | |
| 241 | crate | |
| 242 | crave | |
| 243 | crawl | |
| 244 | crazy | |
| 245 | creme | |
| 246 | crepe | |
| 247 | crept | |
| 248 | crib | |
| 249 | cried | |
| 250 | crisp | |
| 251 | crook | |
| 252 | crop | |
| 253 | cross | |
| 254 | crowd | |
| 255 | crown | |
| 256 | crumb | |
| 257 | crush | |
| 258 | crust | |
| 259 | cub | |
| 260 | cult | |
| 261 | cupid | |
| 262 | cure | |
| 263 | curl | |
| 264 | curry | |
| 265 | curse | |
| 266 | curve | |
| 267 | curvy | |
| 268 | cushy | |
| 269 | cut | |
| 270 | cycle | |
| 271 | dab | |
| 272 | dad | |
| 273 | daily | |
| 274 | dairy | |
| 275 | daisy | |
| 276 | dance | |
| 277 | dandy | |
| 278 | darn | |
| 279 | dart | |
| 280 | dash | |
| 281 | data | |
| 282 | date | |
| 283 | dawn | |
| 284 | deaf | |
| 285 | deal | |
| 286 | dean | |
| 287 | debit | |
| 288 | debt | |
| 289 | debug | |
| 290 | decaf | |
| 291 | decal | |
| 292 | decay | |
| 293 | deck | |
| 294 | decor | |
| 295 | decoy | |
| 296 | deed | |
| 297 | delay | |
| 298 | denim | |
| 299 | dense | |
| 300 | dent | |
| 301 | depth | |
| 302 | derby | |
| 303 | desk | |
| 304 | dial | |
| 305 | diary | |
| 306 | dice | |
| 307 | dig | |
| 308 | dill | |
| 309 | dime | |
| 310 | dimly | |
| 311 | diner | |
| 312 | dingy | |
| 313 | disco | |
| 314 | dish | |
| 315 | disk | |
| 316 | ditch | |
| 317 | ditzy | |
| 318 | dizzy | |
| 319 | dock | |
| 320 | dodge | |
| 321 | doing | |
| 322 | doll | |
| 323 | dome | |
| 324 | donor | |
| 325 | donut | |
| 326 | dose | |
| 327 | dot | |
| 328 | dove | |
| 329 | down | |
| 330 | dowry | |
| 331 | doze | |
| 332 | drab | |
| 333 | drama | |
| 334 | drank | |
| 335 | draw | |
| 336 | dress | |
| 337 | dried | |
| 338 | drift | |
| 339 | drill | |
| 340 | drive | |
| 341 | drone | |
| 342 | droop | |
| 343 | drove | |
| 344 | drown | |
| 345 | drum | |
| 346 | dry | |
| 347 | duck | |
| 348 | duct | |
| 349 | dude | |
| 350 | dug | |
| 351 | duke | |
| 352 | duo | |
| 353 | dusk | |
| 354 | dust | |
| 355 | duty | |
| 356 | dwarf | |
| 357 | dwell | |
| 358 | eagle | |
| 359 | early | |
| 360 | earth | |
| 361 | easel | |
| 362 | east | |
| 363 | eaten | |
| 364 | eats | |
| 365 | ebay | |
| 366 | ebony | |
| 367 | ebook | |
| 368 | echo | |
| 369 | edge | |
| 370 | eel | |
| 371 | eject | |
| 372 | elbow | |
| 373 | elder | |
| 374 | elf | |
| 375 | elk | |
| 376 | elm | |
| 377 | elope | |
| 378 | elude | |
| 379 | elves | |
| 380 | ||
| 381 | emit | |
| 382 | empty | |
| 383 | emu | |
| 384 | enter | |
| 385 | entry | |
| 386 | envoy | |
| 387 | equal | |
| 388 | erase | |
| 389 | error | |
| 390 | erupt | |
| 391 | essay | |
| 392 | etch | |
| 393 | evade | |
| 394 | even | |
| 395 | evict | |
| 396 | evil | |
| 397 | evoke | |
| 398 | exact | |
| 399 | exit | |
| 400 | fable | |
| 401 | faced | |
| 402 | fact | |
| 403 | fade | |
| 404 | fall | |
| 405 | false | |
| 406 | fancy | |
| 407 | fang | |
| 408 | fax | |
| 409 | feast | |
| 410 | feed | |
| 411 | femur | |
| 412 | fence | |
| 413 | fend | |
| 414 | ferry | |
| 415 | fetal | |
| 416 | fetch | |
| 417 | fever | |
| 418 | fiber | |
| 419 | fifth | |
| 420 | fifty | |
| 421 | film | |
| 422 | filth | |
| 423 | final | |
| 424 | finch | |
| 425 | fit | |
| 426 | five | |
| 427 | flag | |
| 428 | flaky | |
| 429 | flame | |
| 430 | flap | |
| 431 | flask | |
| 432 | fled | |
| 433 | flick | |
| 434 | fling | |
| 435 | flint | |
| 436 | flip | |
| 437 | flirt | |
| 438 | float | |
| 439 | flock | |
| 440 | flop | |
| 441 | floss | |
| 442 | flyer | |
| 443 | foam | |
| 444 | foe | |
| 445 | fog | |
| 446 | foil | |
| 447 | folic | |
| 448 | folk | |
| 449 | food | |
| 450 | fool | |
| 451 | found | |
| 452 | fox | |
| 453 | foyer | |
| 454 | frail | |
| 455 | frame | |
| 456 | fray | |
| 457 | fresh | |
| 458 | fried | |
| 459 | frill | |
| 460 | frisk | |
| 461 | from | |
| 462 | front | |
| 463 | frost | |
| 464 | froth | |
| 465 | frown | |
| 466 | froze | |
| 467 | fruit | |
| 468 | gag | |
| 469 | gains | |
| 470 | gala | |
| 471 | game | |
| 472 | gap | |
| 473 | gas | |
| 474 | gave | |
| 475 | gear | |
| 476 | gecko | |
| 477 | geek | |
| 478 | gem | |
| 479 | genre | |
| 480 | gift | |
| 481 | gig | |
| 482 | gills | |
| 483 | given | |
| 484 | giver | |
| 485 | glad | |
| 486 | glass | |
| 487 | glide | |
| 488 | gloss | |
| 489 | glove | |
| 490 | glow | |
| 491 | glue | |
| 492 | goal | |
| 493 | going | |
| 494 | golf | |
| 495 | gong | |
| 496 | good | |
| 497 | gooey | |
| 498 | goofy | |
| 499 | gore | |
| 500 | gown | |
| 501 | grab | |
| 502 | grain | |
| 503 | grant | |
| 504 | grape | |
| 505 | graph | |
| 506 | grasp | |
| 507 | grass | |
| 508 | grave | |
| 509 | gravy | |
| 510 | gray | |
| 511 | green | |
| 512 | greet | |
| 513 | grew | |
| 514 | grid | |
| 515 | grief | |
| 516 | grill | |
| 517 | grip | |
| 518 | grit | |
| 519 | groom | |
| 520 | grope | |
| 521 | growl | |
| 522 | grub | |
| 523 | grunt | |
| 524 | guide | |
| 525 | gulf | |
| 526 | gulp | |
| 527 | gummy | |
| 528 | guru | |
| 529 | gush | |
| 530 | gut | |
| 531 | guy | |
| 532 | habit | |
| 533 | half | |
| 534 | halo | |
| 535 | halt | |
| 536 | happy | |
| 537 | harm | |
| 538 | hash | |
| 539 | hasty | |
| 540 | hatch | |
| 541 | hate | |
| 542 | haven | |
| 543 | hazel | |
| 544 | hazy | |
| 545 | heap | |
| 546 | heat | |
| 547 | heave | |
| 548 | hedge | |
| 549 | hefty | |
| 550 | help | |
| 551 | herbs | |
| 552 | hers | |
| 553 | hub | |
| 554 | hug | |
| 555 | hula | |
| 556 | hull | |
| 557 | human | |
| 558 | humid | |
| 559 | hump | |
| 560 | hung | |
| 561 | hunk | |
| 562 | hunt | |
| 563 | hurry | |
| 564 | hurt | |
| 565 | hush | |
| 566 | hut | |
| 567 | ice | |
| 568 | icing | |
| 569 | icon | |
| 570 | icy | |
| 571 | igloo | |
| 572 | image | |
| 573 | ion | |
| 574 | iron | |
| 575 | islam | |
| 576 | issue | |
| 577 | item | |
| 578 | ivory | |
| 579 | ivy | |
| 580 | jab | |
| 581 | jam | |
| 582 | jaws | |
| 583 | jazz | |
| 584 | jeep | |
| 585 | jelly | |
| 586 | jet | |
| 587 | jiffy | |
| 588 | job | |
| 589 | jog | |
| 590 | jolly | |
| 591 | jolt | |
| 592 | jot | |
| 593 | joy | |
| 594 | judge | |
| 595 | juice | |
| 596 | juicy | |
| 597 | july | |
| 598 | jumbo | |
| 599 | jump | |
| 600 | junky | |
| 601 | juror | |
| 602 | jury | |
| 603 | keep | |
| 604 | keg | |
| 605 | kept | |
| 606 | kick | |
| 607 | kilt | |
| 608 | king | |
| 609 | kite | |
| 610 | kitty | |
| 611 | kiwi | |
| 612 | knee | |
| 613 | knelt | |
| 614 | koala | |
| 615 | kung | |
| 616 | ladle | |
| 617 | lady | |
| 618 | lair | |
| 619 | lake | |
| 620 | lance | |
| 621 | land | |
| 622 | lapel | |
| 623 | large | |
| 624 | lash | |
| 625 | lasso | |
| 626 | last | |
| 627 | latch | |
| 628 | late | |
| 629 | lazy | |
| 630 | left | |
| 631 | legal | |
| 632 | lemon | |
| 633 | lend | |
| 634 | lens | |
| 635 | lent | |
| 636 | level | |
| 637 | lever | |
| 638 | lid | |
| 639 | life | |
| 640 | lift | |
| 641 | lilac | |
| 642 | lily | |
| 643 | limb | |
| 644 | limes | |
| 645 | line | |
| 646 | lint | |
| 647 | lion | |
| 648 | lip | |
| 649 | list | |
| 650 | lived | |
| 651 | liver | |
| 652 | lunar | |
| 653 | lunch | |
| 654 | lung | |
| 655 | lurch | |
| 656 | lure | |
| 657 | lurk | |
| 658 | lying | |
| 659 | lyric | |
| 660 | mace | |
| 661 | maker | |
| 662 | malt | |
| 663 | mama | |
| 664 | mango | |
| 665 | manor | |
| 666 | many | |
| 667 | map | |
| 668 | march | |
| 669 | mardi | |
| 670 | marry | |
| 671 | mash | |
| 672 | match | |
| 673 | mate | |
| 674 | math | |
| 675 | moan | |
| 676 | mocha | |
| 677 | moist | |
| 678 | mold | |
| 679 | mom | |
| 680 | moody | |
| 681 | mop | |
| 682 | morse | |
| 683 | most | |
| 684 | motor | |
| 685 | motto | |
| 686 | mount | |
| 687 | mouse | |
| 688 | mousy | |
| 689 | mouth | |
| 690 | move | |
| 691 | movie | |
| 692 | mower | |
| 693 | mud | |
| 694 | mug | |
| 695 | mulch | |
| 696 | mule | |
| 697 | mull | |
| 698 | mumbo | |
| 699 | mummy | |
| 700 | mural | |
| 701 | muse | |
| 702 | music | |
| 703 | musky | |
| 704 | mute | |
| 705 | nacho | |
| 706 | nag | |
| 707 | nail | |
| 708 | name | |
| 709 | nanny | |
| 710 | nap | |
| 711 | navy | |
| 712 | near | |
| 713 | neat | |
| 714 | neon | |
| 715 | nerd | |
| 716 | nest | |
| 717 | net | |
| 718 | next | |
| 719 | niece | |
| 720 | ninth | |
| 721 | nutty | |
| 722 | oak | |
| 723 | oasis | |
| 724 | oat | |
| 725 | ocean | |
| 726 | oil | |
| 727 | old | |
| 728 | olive | |
| 729 | omen | |
| 730 | onion | |
| 731 | only | |
| 732 | ooze | |
| 733 | opal | |
| 734 | open | |
| 735 | opera | |
| 736 | opt | |
| 737 | otter | |
| 738 | ouch | |
| 739 | ounce | |
| 740 | outer | |
| 741 | oval | |
| 742 | oven | |
| 743 | owl | |
| 744 | ozone | |
| 745 | pace | |
| 746 | pagan | |
| 747 | pager | |
| 748 | palm | |
| 749 | panda | |
| 750 | panic | |
| 751 | pants | |
| 752 | panty | |
| 753 | paper | |
| 754 | park | |
| 755 | party | |
| 756 | pasta | |
| 757 | patch | |
| 758 | path | |
| 759 | patio | |
| 760 | payer | |
| 761 | pecan | |
| 762 | penny | |
| 763 | pep | |
| 764 | perch | |
| 765 | perky | |
| 766 | perm | |
| 767 | pest | |
| 768 | petal | |
| 769 | petri | |
| 770 | petty | |
| 771 | photo | |
| 772 | plank | |
| 773 | plant | |
| 774 | plaza | |
| 775 | plead | |
| 776 | plot | |
| 777 | plow | |
| 778 | pluck | |
| 779 | plug | |
| 780 | plus | |
| 781 | poach | |
| 782 | pod | |
| 783 | poem | |
| 784 | poet | |
| 785 | pogo | |
| 786 | point | |
| 787 | poise | |
| 788 | poker | |
| 789 | polar | |
| 790 | polio | |
| 791 | polka | |
| 792 | polo | |
| 793 | pond | |
| 794 | pony | |
| 795 | poppy | |
| 796 | pork | |
| 797 | poser | |
| 798 | pouch | |
| 799 | pound | |
| 800 | pout | |
| 801 | power | |
| 802 | prank | |
| 803 | press | |
| 804 | ||
| 805 | prior | |
| 806 | prism | |
| 807 | prize | |
| 808 | probe | |
| 809 | prong | |
| 810 | proof | |
| 811 | props | |
| 812 | prude | |
| 813 | prune | |
| 814 | pry | |
| 815 | pug | |
| 816 | pull | |
| 817 | pulp | |
| 818 | pulse | |
| 819 | puma | |
| 820 | punch | |
| 821 | punk | |
| 822 | pupil | |
| 823 | puppy | |
| 824 | purr | |
| 825 | purse | |
| 826 | push | |
| 827 | putt | |
| 828 | quack | |
| 829 | quake | |
| 830 | query | |
| 831 | quiet | |
| 832 | quill | |
| 833 | quilt | |
| 834 | quit | |
| 835 | quota | |
| 836 | quote | |
| 837 | rabid | |
| 838 | race | |
| 839 | rack | |
| 840 | radar | |
| 841 | radio | |
| 842 | raft | |
| 843 | rage | |
| 844 | raid | |
| 845 | rail | |
| 846 | rake | |
| 847 | rally | |
| 848 | ramp | |
| 849 | ranch | |
| 850 | range | |
| 851 | rank | |
| 852 | rant | |
| 853 | rash | |
| 854 | raven | |
| 855 | reach | |
| 856 | react | |
| 857 | ream | |
| 858 | rebel | |
| 859 | recap | |
| 860 | relax | |
| 861 | relay | |
| 862 | relic | |
| 863 | remix | |
| 864 | repay | |
| 865 | repel | |
| 866 | reply | |
| 867 | rerun | |
| 868 | reset | |
| 869 | rhyme | |
| 870 | rice | |
| 871 | rich | |
| 872 | ride | |
| 873 | rigid | |
| 874 | rigor | |
| 875 | rinse | |
| 876 | riot | |
| 877 | ripen | |
| 878 | rise | |
| 879 | risk | |
| 880 | ritzy | |
| 881 | rival | |
| 882 | river | |
| 883 | roast | |
| 884 | robe | |
| 885 | robin | |
| 886 | rock | |
| 887 | rogue | |
| 888 | roman | |
| 889 | romp | |
| 890 | rope | |
| 891 | rover | |
| 892 | royal | |
| 893 | ruby | |
| 894 | rug | |
| 895 | ruin | |
| 896 | rule | |
| 897 | runny | |
| 898 | rush | |
| 899 | rust | |
| 900 | rut | |
| 901 | sadly | |
| 902 | sage | |
| 903 | said | |
| 904 | saint | |
| 905 | salad | |
| 906 | salon | |
| 907 | salsa | |
| 908 | salt | |
| 909 | same | |
| 910 | sandy | |
| 911 | santa | |
| 912 | satin | |
| 913 | sauna | |
| 914 | saved | |
| 915 | savor | |
| 916 | sax | |
| 917 | say | |
| 918 | scale | |
| 919 | scam | |
| 920 | scan | |
| 921 | scare | |
| 922 | scarf | |
| 923 | scary | |
| 924 | scoff | |
| 925 | scold | |
| 926 | scoop | |
| 927 | scoot | |
| 928 | scope | |
| 929 | score | |
| 930 | scorn | |
| 931 | scout | |
| 932 | scowl | |
| 933 | scrap | |
| 934 | scrub | |
| 935 | scuba | |
| 936 | scuff | |
| 937 | sect | |
| 938 | sedan | |
| 939 | self | |
| 940 | send | |
| 941 | sepia | |
| 942 | serve | |
| 943 | set | |
| 944 | seven | |
| 945 | shack | |
| 946 | shade | |
| 947 | shady | |
| 948 | shaft | |
| 949 | shaky | |
| 950 | sham | |
| 951 | shape | |
| 952 | share | |
| 953 | sharp | |
| 954 | shed | |
| 955 | sheep | |
| 956 | sheet | |
| 957 | shelf | |
| 958 | shell | |
| 959 | shine | |
| 960 | shiny | |
| 961 | ship | |
| 962 | shirt | |
| 963 | shock | |
| 964 | shop | |
| 965 | shore | |
| 966 | shout | |
| 967 | shove | |
| 968 | shown | |
| 969 | showy | |
| 970 | shred | |
| 971 | shrug | |
| 972 | shun | |
| 973 | shush | |
| 974 | shut | |
| 975 | shy | |
| 976 | sift | |
| 977 | silk | |
| 978 | silly | |
| 979 | silo | |
| 980 | sip | |
| 981 | siren | |
| 982 | sixth | |
| 983 | size | |
| 984 | skate | |
| 985 | skew | |
| 986 | skid | |
| 987 | skier | |
| 988 | skies | |
| 989 | skip | |
| 990 | skirt | |
| 991 | skit | |
| 992 | sky | |
| 993 | slab | |
| 994 | slack | |
| 995 | slain | |
| 996 | slam | |
| 997 | slang | |
| 998 | slash | |
| 999 | slate | |
| 1000 | slaw | |
| 1001 | sled | |
| 1002 | sleek | |
| 1003 | sleep | |
| 1004 | sleet | |
| 1005 | slept | |
| 1006 | slice | |
| 1007 | slick | |
| 1008 | slimy | |
| 1009 | sling | |
| 1010 | slip | |
| 1011 | slit | |
| 1012 | slob | |
| 1013 | slot | |
| 1014 | slug | |
| 1015 | slum | |
| 1016 | slurp | |
| 1017 | slush | |
| 1018 | small | |
| 1019 | smash | |
| 1020 | smell | |
| 1021 | smile | |
| 1022 | smirk | |
| 1023 | smog | |
| 1024 | snack | |
| 1025 | snap | |
| 1026 | snare | |
| 1027 | snarl | |
| 1028 | sneak | |
| 1029 | sneer | |
| 1030 | sniff | |
| 1031 | snore | |
| 1032 | snort | |
| 1033 | snout | |
| 1034 | snowy | |
| 1035 | snub | |
| 1036 | snuff | |
| 1037 | speak | |
| 1038 | speed | |
| 1039 | spend | |
| 1040 | spent | |
| 1041 | spew | |
| 1042 | spied | |
| 1043 | spill | |
| 1044 | spiny | |
| 1045 | spoil | |
| 1046 | spoke | |
| 1047 | spoof | |
| 1048 | spool | |
| 1049 | spoon | |
| 1050 | sport | |
| 1051 | spot | |
| 1052 | spout | |
| 1053 | spray | |
| 1054 | spree | |
| 1055 | spur | |
| 1056 | squad | |
| 1057 | squat | |
| 1058 | squid | |
| 1059 | stack | |
| 1060 | staff | |
| 1061 | stage | |
| 1062 | stain | |
| 1063 | stall | |
| 1064 | stamp | |
| 1065 | stand | |
| 1066 | stank | |
| 1067 | stark | |
| 1068 | start | |
| 1069 | stash | |
| 1070 | state | |
| 1071 | stays | |
| 1072 | steam | |
| 1073 | steep | |
| 1074 | stem | |
| 1075 | step | |
| 1076 | stew | |
| 1077 | stick | |
| 1078 | sting | |
| 1079 | stir | |
| 1080 | stock | |
| 1081 | stole | |
| 1082 | stomp | |
| 1083 | stony | |
| 1084 | stood | |
| 1085 | stool | |
| 1086 | stoop | |
| 1087 | stop | |
| 1088 | storm | |
| 1089 | stout | |
| 1090 | stove | |
| 1091 | straw | |
| 1092 | stray | |
| 1093 | strut | |
| 1094 | stuck | |
| 1095 | stud | |
| 1096 | stuff | |
| 1097 | stump | |
| 1098 | stung | |
| 1099 | stunt | |
| 1100 | suds | |
| 1101 | sugar | |
| 1102 | sulk | |
| 1103 | surf | |
| 1104 | sushi | |
| 1105 | swab | |
| 1106 | swan | |
| 1107 | swarm | |
| 1108 | sway | |
| 1109 | swear | |
| 1110 | sweat | |
| 1111 | sweep | |
| 1112 | swell | |
| 1113 | swept | |
| 1114 | swim | |
| 1115 | swing | |
| 1116 | swipe | |
| 1117 | swirl | |
| 1118 | swoop | |
| 1119 | swore | |
| 1120 | syrup | |
| 1121 | tacky | |
| 1122 | taco | |
| 1123 | tag | |
| 1124 | take | |
| 1125 | tall | |
| 1126 | talon | |
| 1127 | tamer | |
| 1128 | tank | |
| 1129 | taper | |
| 1130 | taps | |
| 1131 | tarot | |
| 1132 | tart | |
| 1133 | task | |
| 1134 | taste | |
| 1135 | tasty | |
| 1136 | taunt | |
| 1137 | thank | |
| 1138 | thaw | |
| 1139 | theft | |
| 1140 | theme | |
| 1141 | thigh | |
| 1142 | thing | |
| 1143 | think | |
| 1144 | thong | |
| 1145 | thorn | |
| 1146 | those | |
| 1147 | throb | |
| 1148 | thud | |
| 1149 | thumb | |
| 1150 | thump | |
| 1151 | thus | |
| 1152 | tiara | |
| 1153 | tidal | |
| 1154 | tidy | |
| 1155 | tiger | |
| 1156 | tile | |
| 1157 | tilt | |
| 1158 | tint | |
| 1159 | tiny | |
| 1160 | trace | |
| 1161 | track | |
| 1162 | trade | |
| 1163 | train | |
| 1164 | trait | |
| 1165 | trap | |
| 1166 | trash | |
| 1167 | tray | |
| 1168 | treat | |
| 1169 | tree | |
| 1170 | trek | |
| 1171 | trend | |
| 1172 | trial | |
| 1173 | tribe | |
| 1174 | trick | |
| 1175 | trio | |
| 1176 | trout | |
| 1177 | truce | |
| 1178 | truck | |
| 1179 | trump | |
| 1180 | trunk | |
| 1181 | try | |
| 1182 | tug | |
| 1183 | tulip | |
| 1184 | tummy | |
| 1185 | turf | |
| 1186 | tusk | |
| 1187 | tutor | |
| 1188 | tutu | |
| 1189 | tux | |
| 1190 | tweak | |
| 1191 | tweet | |
| 1192 | twice | |
| 1193 | twine | |
| 1194 | twins | |
| 1195 | twirl | |
| 1196 | twist | |
| 1197 | uncle | |
| 1198 | uncut | |
| 1199 | undo | |
| 1200 | unify | |
| 1201 | union | |
| 1202 | unit | |
| 1203 | untie | |
| 1204 | upon | |
| 1205 | upper | |
| 1206 | urban | |
| 1207 | used | |
| 1208 | user | |
| 1209 | usher | |
| 1210 | utter | |
| 1211 | value | |
| 1212 | vapor | |
| 1213 | vegan | |
| 1214 | venue | |
| 1215 | verse | |
| 1216 | vest | |
| 1217 | veto | |
| 1218 | vice | |
| 1219 | video | |
| 1220 | view | |
| 1221 | viral | |
| 1222 | virus | |
| 1223 | visa | |
| 1224 | visor | |
| 1225 | vixen | |
| 1226 | vocal | |
| 1227 | voice | |
| 1228 | void | |
| 1229 | volt | |
| 1230 | voter | |
| 1231 | vowel | |
| 1232 | wad | |
| 1233 | wafer | |
| 1234 | wager | |
| 1235 | wages | |
| 1236 | wagon | |
| 1237 | wake | |
| 1238 | walk | |
| 1239 | wand | |
| 1240 | wasp | |
| 1241 | watch | |
| 1242 | water | |
| 1243 | wavy | |
| 1244 | wheat | |
| 1245 | whiff | |
| 1246 | whole | |
| 1247 | whoop | |
| 1248 | wick | |
| 1249 | widen | |
| 1250 | widow | |
| 1251 | width | |
| 1252 | wife | |
| 1253 | wifi | |
| 1254 | wilt | |
| 1255 | wimp | |
| 1256 | wind | |
| 1257 | wing | |
| 1258 | wink | |
| 1259 | wipe | |
| 1260 | wired | |
| 1261 | wiry | |
| 1262 | wise | |
| 1263 | wish | |
| 1264 | wispy | |
| 1265 | wok | |
| 1266 | wolf | |
| 1267 | womb | |
| 1268 | wool | |
| 1269 | woozy | |
| 1270 | word | |
| 1271 | work | |
| 1272 | worry | |
| 1273 | wound | |
| 1274 | woven | |
| 1275 | wrath | |
| 1276 | wreck | |
| 1277 | wrist | |
| 1278 | xerox | |
| 1279 | yahoo | |
| 1280 | yam | |
| 1281 | yard | |
| 1282 | year | |
| 1283 | yeast | |
| 1284 | yelp | |
| 1285 | yield | |
| 1286 | yarn | |
| 1287 | yodel | |
| 1288 | yoga | |
| 1289 | yoyo | |
| 1290 | yummy | |
| 1291 | zebra | |
| 1292 | zero | |
| 1293 | zesty | |
| 1294 | zippy | |
| 1295 | zone | |
| 1296 | zoom |
crates/notebook/tests/live_share.rs+23-8| ... | ... | @@ -37,6 +37,12 @@ fn relay(config: relay::server::Config) -> String { |
| 37 | 37 | url |
| 38 | 38 | } |
| 39 | 39 | |
| 40 | /// Another secret than `secret`, as a guess makes one. | |
| 41 | fn mistaken(secret: &str) -> String { | |
| 42 | let first = if secret.starts_with('A') { 'B' } else { 'A' }; | |
| 43 | format!("{first}{}", &secret[1..]) | |
| 44 | } | |
| 45 | ||
| 40 | 46 | fn hello(name: &str) -> Hello { |
| 41 | 47 | Hello::new(name.into(), None).unwrap() |
| 42 | 48 | } |
| ... | ... | @@ -338,13 +344,24 @@ fn wrong_codes_are_refused_and_counted() { |
| 338 | 344 | let sharing = Sharing::new("").unwrap(); |
| 339 | 345 | let host = host(&folder, &directory.path().join("host"), &sharing, &url); |
| 340 | 346 | let code = code(&host); |
| 341 | let number = code.split('-').next().unwrap(); | |
| 342 | let wrong = format!("{number}-violet-ocelot"); | |
| 347 | let (number, secret) = notebook::live::code::parse(&code).unwrap(); | |
| 348 | let wrong = notebook::live::code::format(number, &mistaken(&secret)).unwrap(); | |
| 343 | 349 | let join = |code: &str| share::join(hello("Mallory"), code, "", None, Some(&url)); |
| 344 | 350 | assert_eq!(join("not a code").unwrap_err(), Refusal::Malformed); |
| 351 | // A symbol mistyped fails its check here, spending none of the two tries before a burn. | |
| 352 | let typo = format!( | |
| 353 | "{}{}", | |
| 354 | if code.starts_with('7') { '8' } else { '7' }, | |
| 355 | &code[1..] | |
| 356 | ); | |
| 357 | assert_eq!(join(&typo).unwrap_err(), Refusal::Malformed); | |
| 358 | assert_eq!( | |
| 359 | join(&code.to_lowercase().replace('-', " ")).map(|_| ()), | |
| 360 | Ok(()) | |
| 361 | ); | |
| 345 | 362 | assert_eq!(join(&wrong).unwrap_err(), Refusal::Wrong); |
| 346 | 363 | assert_eq!(join(&wrong).unwrap_err(), Refusal::Wrong); |
| 347 | // The code burned, and the host shares new words, under a number of their own. | |
| 364 | // The code burned, and the host shares a new secret, under a number of its own. | |
| 348 | 365 | until("the code never changed", || { |
| 349 | 366 | host.code() |
| 350 | 367 | .is_some_and(|now| now != code && share::code(&now).is_some()) |
| ... | ... | @@ -354,11 +371,9 @@ fn wrong_codes_are_refused_and_counted() { |
| 354 | 371 | join(&code).unwrap_err(), |
| 355 | 372 | Refusal::Expired | Refusal::NoOne |
| 356 | 373 | )); |
| 357 | let number = fresh.split('-').next().unwrap(); | |
| 358 | assert_eq!( | |
| 359 | join(&format!("{number}-violet-ocelot")).unwrap_err(), | |
| 360 | Refusal::Wrong | |
| 361 | ); | |
| 374 | let (number, secret) = notebook::live::code::parse(&fresh).unwrap(); | |
| 375 | let wrong = notebook::live::code::format(number, &mistaken(&secret)).unwrap(); | |
| 376 | assert_eq!(join(&wrong).unwrap_err(), Refusal::Wrong); | |
| 362 | 377 | // A third wrong code in a minute locks this network out, even from the right code. |
| 363 | 378 | assert!(matches!( |
| 364 | 379 | join(&fresh).unwrap_err(), |
crates/snowbound/src/share.rs+2-2| ... | ... | @@ -61,7 +61,7 @@ enum Status { |
| 61 | 61 | /// What to tell someone whose code didn't open a notebook. |
| 62 | 62 | fn refusal(refusal: &Refusal) -> String { |
| 63 | 63 | match refusal { |
| 64 | Refusal::Malformed => "Enter the code as it was given, like 412-violet-otter.".into(), | |
| 64 | Refusal::Malformed => "Check the code. It looks like 7KQ-4MZ-9XR.".into(), | |
| 65 | 65 | Refusal::Wrong => "That code or password doesn’t open a notebook. Check it with the \ |
| 66 | 66 | person sharing." |
| 67 | 67 | .into(), |
| ... | ... | @@ -449,7 +449,7 @@ impl State { |
| 449 | 449 | text(ui, "what", "Enter the code from the person sharing.", false); |
| 450 | 450 | labelled(ui, "Code:", |ui| { |
| 451 | 451 | let spec = field_spec(ui); |
| 452 | ui::text_field(ui, code_field(), &mut dialog.code, "412-violet-otter", spec); | |
| 452 | ui::text_field(ui, code_field(), &mut dialog.code, "7KQ-4MZ-9XR", spec); | |
| 453 | 453 | if let Some(node) = ui.access(code_field()) { |
| 454 | 454 | node.set_label("Code"); |
| 455 | 455 | } |