| 1 | use super::{Hello, Line, Presence, code}; |
| 2 | use sha2::{Digest, Sha256}; |
| 3 | use std::{sync::Arc, time::Duration}; |
| 4 | |
| 5 | pub(super) fn hex(bytes: &[u8]) -> String { |
| 6 | bytes.iter().map(|byte| format!("{byte:02x}")).collect() |
| 7 | } |
| 8 | |
| 9 | /// The secret peers meet through. |
| 10 | #[derive(Clone, Debug, PartialEq, Eq)] |
| 11 | pub enum Room { |
| 12 | /// A notebook's room, or a share's: a random secret only its members hold. |
| 13 | Notebook([u8; 16]), |
| 14 | /// A code typed on both ends (`code`), with a password where one is set: its room's |
| 15 | /// number names it on the network, and its secret and password only the two people know. |
| 16 | /// Its `owner`, the end sharing it, takes the number from a relay (the one the code has, |
| 17 | /// coming back; any free one for a secret alone) or picks one where it has no relay, and |
| 18 | /// judges every try, burning the code after too many wrong ones. |
| 19 | Code { |
| 20 | code: String, |
| 21 | password: String, |
| 22 | owner: bool, |
| 23 | }, |
| 24 | } |
| 25 | |
| 26 | impl Room { |
| 27 | /// The room a code typed on this end leads to. |
| 28 | pub fn join(code: &str, password: &str) -> Self { |
| 29 | Self::Code { |
| 30 | code: code.to_owned(), |
| 31 | password: password.to_owned(), |
| 32 | owner: false, |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | /// The room of a code this end shares: its secret, or a whole code to keep its number. |
| 37 | pub fn share(code: &str, password: &str) -> Self { |
| 38 | Self::Code { |
| 39 | code: code.to_owned(), |
| 40 | password: password.to_owned(), |
| 41 | owner: true, |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | /// What names the room in the clear: a hash of a room's secret, a code's number; none for |
| 46 | /// a code without one yet. |
| 47 | pub(super) fn tag(&self) -> Option<String> { |
| 48 | match self { |
| 49 | Room::Notebook(id) => Some(hex(&Sha256::digest( |
| 50 | [&b"Snowbound room "[..], id].concat(), |
| 51 | )[..8])), |
| 52 | Room::Code { code, .. } => code_parts(code).0.map(|number| format!("code-{number}")), |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | pub(super) fn secret(&self) -> Vec<u8> { |
| 57 | match self { |
| 58 | Room::Notebook(id) => id.to_vec(), |
| 59 | Room::Code { code, password, .. } => { |
| 60 | let mut secret = code_parts(code).1.into_bytes(); |
| 61 | if !password.is_empty() { |
| 62 | secret.push(b'\n'); |
| 63 | secret.extend_from_slice(password.as_bytes()); |
| 64 | } |
| 65 | secret |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | #[cfg(not(target_arch = "wasm32"))] |
| 71 | pub(super) fn owner(&self) -> bool { |
| 72 | matches!(self, Room::Code { owner: true, .. }) |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | /// A code's room number, if it has one, and its secret: a whole code, or a secret alone. |
| 77 | pub(super) fn code_parts(code: &str) -> (Option<u32>, String) { |
| 78 | match code::parse(code) { |
| 79 | Some((number, secret)) => (Some(number), secret), |
| 80 | None => (None, code.trim().to_uppercase()), |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | /// Where peers are looked for. |
| 85 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 86 | pub enum Reach { |
| 87 | /// Every network this computer is on. |
| 88 | Network, |
| 89 | /// This computer alone, for two copies of the app side by side. |
| 90 | Loopback, |
| 91 | } |
| 92 | |
| 93 | #[derive(Clone, Debug)] |
| 94 | pub struct Peer { |
| 95 | pub hello: Arc<Hello>, |
| 96 | /// None until the peer first says where it is. |
| 97 | pub presence: Option<Presence>, |
| 98 | } |
| 99 | |
| 100 | /// What happened, as `Live::start`'s `events` hears it on a network thread. |
| 101 | pub enum Event<'a> { |
| 102 | /// The peers, their presence, the code or the relay's answer changed. |
| 103 | Changed, |
| 104 | /// A stream to a peer opened, with the line to it. |
| 105 | Met(&'a Arc<Hello>, &'a Line), |
| 106 | /// A peer's stream ended. |
| 107 | Left(&'a Arc<Hello>), |
| 108 | /// A frame of a kind presence doesn't read itself, on a stream or to the group. |
| 109 | Frame { |
| 110 | from: &'a Arc<Hello>, |
| 111 | kind: u16, |
| 112 | body: &'a [u8], |
| 113 | }, |
| 114 | } |
| 115 | |
| 116 | /// How the relay last answered. |
| 117 | #[derive(Clone, Debug, PartialEq, Eq)] |
| 118 | pub enum Relayed { |
| 119 | /// Not asked yet, or no relay. |
| 120 | Unknown, |
| 121 | /// In the room. |
| 122 | Joined, |
| 123 | /// Answered with this HTTP status, and how long it asked to wait. |
| 124 | Refused(u16, Option<Duration>), |
| 125 | /// Not reached, and why. |
| 126 | Unreachable(Trouble), |
| 127 | } |
| 128 | |
| 129 | /// Why a relay could not be reached, as a person can act on it. |
| 130 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 131 | pub enum Trouble { |
| 132 | /// The relay's name, or the proxy's, didn't resolve. |
| 133 | Dns, |
| 134 | /// Nothing answered at the relay's address: a firewall, or the relay is down. |
| 135 | Unreachable, |
| 136 | TimedOut, |
| 137 | /// The proxy itself couldn't be reached. |
| 138 | ProxyUnreachable, |
| 139 | /// The proxy asks for a name and password (407). |
| 140 | ProxyAuthentication, |
| 141 | /// The proxy refused to connect to the relay, with this status. |
| 142 | ProxyRefused(u16), |
| 143 | /// The relay's certificate wasn't one the system trusts: something on the way presents its |
| 144 | /// own. |
| 145 | Certificate, |
| 146 | /// Something on the way refused both WebSockets and plain requests to the relay. |
| 147 | Blocked, |
| 148 | Other, |
| 149 | } |