1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! **An enrollment, between the control that opened it and the symbol it ends
//! at** (yog's `docs/REMOTE.md` §8.4; DESIGN §3).
//!
//! Two things this holds that nothing else in the window holds, and both are
//! the reason it is its own type.
//!
//! # It holds a secret, and holding it is the whole of what the seat does
//!
//! [`Shown`] carries a **private key for a box that does not exist yet**. The
//! seat never writes it: not to its state root, not to a cache, not to a log
//! line. It lives here while a symbol is on screen and is dropped with the
//! pane — [`Model::close_enrollment`](super::Model::close_enrollment) is what
//! drops it, and the operator reaches that through a control.
//!
//! # The symbol is cached because a frame may not compute one
//!
//! Every other thing in this model is a value a frame reads and paints.
//! Encoding a symbol is Reed-Solomon over about sixteen hundred bytes and eight
//! masked candidates scored four ways — microseconds, but microseconds *per
//! frame*, forever, on a window that repaints on a beat. So it is computed once
//! where the answer is filed and held beside the material it came from. That is
//! not a second representation of one fact: it is a derivation whose only
//! alternative is doing it again sixty times a second, which is the one thing
//! `crate::ui`'s own rule forbids a frame to do.
use crate::qr::Symbol;
use crate::reply::enrolled::Enrolled;
use super::Aim;
/// **Which grade a new box's leaf is minted at** (REMOTE §4.2).
///
/// Two arms rather than a typed string, because this is a *choice a control
/// offers* — the window paints one button per arm, and an arm added is a button
/// added. The wire word is [`Grade::word`], which is the one place either
/// spelling is written.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Grade {
/// A seat: it asks, acts and paints. The default, because it is the grade
/// an operator enrolling their own next device wants.
#[default]
Operator,
/// A tool host: it executes, and reads nothing (REMOTE §5.4).
Foot,
}
impl Grade {
/// The wire's own word for it.
pub fn word(self) -> String {
match self {
Self::Operator => "operator",
Self::Foot => "foot",
}
.to_owned()
}
/// Both, in the order the control paints them.
pub fn both() -> Vec<Self> {
vec![Self::Operator, Self::Foot]
}
}
/// **The material, and the picture of it.** What the pane paints once the
/// engine has answered.
#[derive(Debug, Clone, PartialEq)]
pub struct Shown {
/// The three fields that are not secret, as one line.
pub caption: String,
/// The symbol carrying all six.
pub symbol: Symbol,
}
impl Shown {
/// Draw the material, or say why it will not fit one symbol.
///
/// The ceiling is version 40 at correction level M and REMOTE §8.4 measures
/// the envelope well inside it, so the `Err` is a recipe that moved — an
/// RSA key, a longer chain — rather than a picture that could have been
/// drawn smaller.
pub fn of(material: &Enrolled) -> Result<Self, String> {
Symbol::encode(material.envelope().as_bytes())
.map(|symbol| Self {
caption: material.caption(),
symbol,
})
.map_err(|too_long| {
format!("the engine's material will not fit one symbol: {too_long}")
})
}
}
/// **An enrollment in flight**: what it is aimed at, what the operator has
/// chosen, and the answer once there is one.
#[derive(Debug, Clone, PartialEq)]
pub struct Enrolling {
/// The wall it enrolls into — the address a gesture must carry, which is
/// what makes the pane composable without a second lookup.
pub aim: Aim,
/// The new box's name, as typed. The engine is the authority on whether it
/// is a legal one (REMOTE §4.1: one path component, never `local`) and
/// refuses in band, so nothing here judges it — a seat that judged it would
/// be a second authority for a rule it does not own.
pub name: String,
/// The grade its leaf is minted at.
pub grade: Grade,
/// **Whether the act has been spent**, which nothing else can answer: the
/// outbox a frame composes into is drained by another thread within the
/// beat, so a pane reading it back would see the gesture for one frame and
/// then see nothing. It is the record that the operator has already asked,
/// and it is what stops a second click minting a second box.
pub posted: bool,
/// The material and its picture, once the engine has answered.
pub shown: Option<Shown>,
}
impl Enrolling {
/// A fresh enrollment aimed at one wall, with nothing chosen but the
/// default grade.
pub fn at(aim: Aim) -> Self {
Self {
aim,
name: String::new(),
grade: Grade::default(),
posted: false,
shown: None,
}
}
/// **Whether the act can be spent yet.** A name is the one thing the
/// operator has to supply, and an empty one would spend a round trip to
/// learn what this end already knows. Spent once, it is not ready again:
/// the engine refuses a second enrollment under one name, but the honest
/// place to stop a double click is the control that made it.
pub fn ready(&self) -> bool {
!self.name.trim().is_empty() && !self.posted && self.shown.is_none()
}
/// Whether the engine has been asked and has not yet answered.
pub fn minting(&self) -> bool {
self.posted && self.shown.is_none()
}
/// The gesture this enrollment composes, built from the same row
/// `lernie enroll` spends.
///
/// **It states no address**, so the engine's own is what the new box is
/// handed. The pane has no field for one and does not need a placeholder:
/// an operator sitting at a window is on a box that dialled this engine
/// successfully, so an enrolment they compose here is for a device whose
/// route they can state at the command line if it differs
/// (`lernie enroll … --at <host>:<port>`, bl-971c). Adding the field is a
/// control this pane owes when a ball asks for it, not a `None` to fill in.
pub fn gesture(&self) -> serde_json::Value {
crate::verbs::enroll(
self.aim.address.clone(),
self.name.trim().to_owned(),
self.grade.word(),
None,
)
}
}