1use super::{Hello, Line, Presence, code};
2use sha2::{Digest, Sha256};
3use std::{sync::Arc, time::Duration};
4
5pub(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)]
11pub 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
26impl 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.
77pub(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)]
86pub 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)]
94pub 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.
101pub 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)]
118pub 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)]
131pub 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}