1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum RescueAction {
27 None,
29 TriggerDfu,
38}
39
40#[derive(Debug)]
55pub struct TouchlessResetWatcher {
56 baud: u32,
57 dtr: bool,
58 fired: bool,
59}
60
61impl Default for TouchlessResetWatcher {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl TouchlessResetWatcher {
68 pub const fn new() -> Self {
71 Self {
72 baud: 115_200,
73 dtr: false,
74 fired: false,
75 }
76 }
77
78 pub fn on_line_coding(&mut self, baud: u32) -> RescueAction {
80 self.baud = baud;
81 RescueAction::None
82 }
83
84 pub fn on_control_line_state(&mut self, dtr: bool, _rts: bool) -> RescueAction {
89 let was_high = self.dtr;
90 self.dtr = dtr;
91 if was_high && !dtr && self.baud == 1_200 && !self.fired {
92 self.fired = true;
93 return RescueAction::TriggerDfu;
94 }
95 RescueAction::None
96 }
97
98 pub fn fired(&self) -> bool {
99 self.fired
100 }
101
102 pub fn reset(&mut self) {
103 self.fired = false;
104 }
105}
106
107#[derive(Debug)]
117pub struct EscapeWatcher {
118 state: EscapeState,
119 fired: bool,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123enum EscapeState {
124 Idle,
125 Ctrl1,
126 Ctrl2,
127 Armed,
128 GotD,
129 GotDf,
130 GotDfu,
131}
132
133const CTRL_C: u8 = 0x03;
134
135impl Default for EscapeWatcher {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141impl EscapeWatcher {
142 pub const fn new() -> Self {
143 Self {
144 state: EscapeState::Idle,
145 fired: false,
146 }
147 }
148
149 pub fn observe(&mut self, byte: u8) -> RescueAction {
153 if self.fired {
154 return RescueAction::None;
155 }
156
157 self.state = match (self.state, byte) {
158 (EscapeState::Idle, CTRL_C) => EscapeState::Ctrl1,
160 (EscapeState::Ctrl1, CTRL_C) => EscapeState::Ctrl2,
161 (EscapeState::Ctrl2, CTRL_C) => EscapeState::Armed,
162
163 (EscapeState::Armed, b'd') => EscapeState::GotD,
165 (EscapeState::GotD, b'f') => EscapeState::GotDf,
166 (EscapeState::GotDf, b'u') => EscapeState::GotDfu,
167
168 (EscapeState::GotDfu, b'\r') | (EscapeState::GotDfu, b'\n') => {
170 self.fired = true;
171 self.state = EscapeState::Idle;
172 return RescueAction::TriggerDfu;
173 }
174
175 (_, CTRL_C) => EscapeState::Ctrl1,
179 _ => EscapeState::Idle,
180 };
181 RescueAction::None
182 }
183
184 pub fn observe_slice(&mut self, bytes: &[u8]) -> RescueAction {
186 for &b in bytes {
187 if let RescueAction::TriggerDfu = self.observe(b) {
188 return RescueAction::TriggerDfu;
189 }
190 }
191 RescueAction::None
192 }
193
194 pub fn fired(&self) -> bool {
195 self.fired
196 }
197
198 pub fn reset(&mut self) {
199 self.state = EscapeState::Idle;
200 self.fired = false;
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 fn open(w: &mut TouchlessResetWatcher, baud: u32) {
209 w.on_line_coding(baud);
210 w.on_control_line_state(true, true);
211 }
212
213 fn close(w: &mut TouchlessResetWatcher) -> RescueAction {
214 w.on_control_line_state(false, false)
215 }
216
217 #[test]
218 fn default_does_not_fire() {
219 let w = TouchlessResetWatcher::new();
220 assert!(!w.fired());
221 }
222
223 #[test]
224 fn normal_open_close_does_not_fire() {
225 let mut w = TouchlessResetWatcher::new();
226 open(&mut w, 115_200);
227 assert_eq!(close(&mut w), RescueAction::None);
228 assert!(!w.fired());
229 }
230
231 #[test]
232 fn open_at_1200_then_close_fires() {
233 let mut w = TouchlessResetWatcher::new();
234 open(&mut w, 1_200);
235 assert_eq!(close(&mut w), RescueAction::TriggerDfu);
236 assert!(w.fired());
237 }
238
239 #[test]
240 fn change_to_1200_then_close_fires() {
241 let mut w = TouchlessResetWatcher::new();
243 open(&mut w, 115_200);
244 w.on_line_coding(1_200);
245 assert_eq!(close(&mut w), RescueAction::TriggerDfu);
246 }
247
248 #[test]
249 fn change_away_from_1200_before_close_does_not_fire() {
250 let mut w = TouchlessResetWatcher::new();
252 open(&mut w, 1_200);
253 w.on_line_coding(115_200);
254 assert_eq!(close(&mut w), RescueAction::None);
255 }
256
257 #[test]
258 fn dtr_drop_without_prior_assertion_does_not_fire() {
259 let mut w = TouchlessResetWatcher::new();
263 w.on_line_coding(1_200);
264 assert_eq!(close(&mut w), RescueAction::None);
265 assert!(!w.fired());
266 }
267
268 #[test]
269 fn fires_only_once_until_reset() {
270 let mut w = TouchlessResetWatcher::new();
271 open(&mut w, 1_200);
272 assert_eq!(close(&mut w), RescueAction::TriggerDfu);
273 open(&mut w, 1_200);
275 assert_eq!(close(&mut w), RescueAction::None);
276
277 w.reset();
278 open(&mut w, 1_200);
279 assert_eq!(close(&mut w), RescueAction::TriggerDfu);
280 }
281
282 #[test]
283 fn dtr_high_to_high_is_not_a_close() {
284 let mut w = TouchlessResetWatcher::new();
287 w.on_line_coding(1_200);
288 w.on_control_line_state(true, false);
289 assert_eq!(w.on_control_line_state(true, true), RescueAction::None);
290 assert!(!w.fired());
291 }
292
293 #[test]
294 fn rts_changes_are_ignored() {
295 let mut w = TouchlessResetWatcher::new();
297 w.on_line_coding(1_200);
298 w.on_control_line_state(true, false);
299 assert_eq!(w.on_control_line_state(true, true), RescueAction::None);
300 assert_eq!(w.on_control_line_state(true, false), RescueAction::None);
301 }
302
303 const MAGIC: &[u8] = b"\x03\x03\x03dfu\r";
306
307 #[test]
308 fn escape_default_does_not_fire() {
309 let w = EscapeWatcher::new();
310 assert!(!w.fired());
311 }
312
313 #[test]
314 fn escape_magic_sequence_fires() {
315 let mut w = EscapeWatcher::new();
316 assert_eq!(w.observe_slice(MAGIC), RescueAction::TriggerDfu);
317 assert!(w.fired());
318 }
319
320 #[test]
321 fn escape_magic_sequence_with_lf_fires() {
322 let mut w = EscapeWatcher::new();
323 assert_eq!(
324 w.observe_slice(b"\x03\x03\x03dfu\n"),
325 RescueAction::TriggerDfu
326 );
327 }
328
329 #[test]
330 fn escape_random_bytes_do_not_fire() {
331 let mut w = EscapeWatcher::new();
332 assert_eq!(w.observe_slice(b"hello world\r\n"), RescueAction::None);
333 assert!(!w.fired());
334 }
335
336 #[test]
337 fn escape_two_ctrl_c_then_other_resets() {
338 let mut w = EscapeWatcher::new();
339 w.observe_slice(b"\x03\x03x");
340 assert_eq!(w.observe_slice(MAGIC), RescueAction::TriggerDfu);
342 }
343
344 #[test]
345 fn escape_three_ctrl_c_then_wrong_command_resets() {
346 let mut w = EscapeWatcher::new();
347 assert_eq!(w.observe_slice(b"\x03\x03\x03nope\r"), RescueAction::None);
348 assert_eq!(w.observe_slice(MAGIC), RescueAction::TriggerDfu);
350 }
351
352 #[test]
353 fn escape_is_case_sensitive() {
354 let mut w = EscapeWatcher::new();
356 assert_eq!(w.observe_slice(b"\x03\x03\x03DFU\r"), RescueAction::None);
357 }
358
359 #[test]
360 fn escape_extra_ctrl_c_after_arm_restarts_prefix() {
361 let mut w = EscapeWatcher::new();
365 w.observe_slice(b"\x03\x03\x03\x03"); assert_eq!(w.observe_slice(b"\x03\x03dfu\r"), RescueAction::TriggerDfu);
369 }
370
371 #[test]
372 fn escape_magic_in_middle_of_stream_fires() {
373 let mut w = EscapeWatcher::new();
374 let stream = b"some other text\x03\x03\x03dfu\r more stuff";
375 assert_eq!(w.observe_slice(stream), RescueAction::TriggerDfu);
376 }
377
378 #[test]
379 fn escape_fires_only_once_until_reset() {
380 let mut w = EscapeWatcher::new();
381 assert_eq!(w.observe_slice(MAGIC), RescueAction::TriggerDfu);
382 assert_eq!(w.observe_slice(MAGIC), RescueAction::None);
383
384 w.reset();
385 assert_eq!(w.observe_slice(MAGIC), RescueAction::TriggerDfu);
386 }
387
388 #[test]
389 fn escape_observes_individual_bytes() {
390 let mut w = EscapeWatcher::new();
391 let mut fired = false;
392 for &b in MAGIC {
393 if let RescueAction::TriggerDfu = w.observe(b) {
394 fired = true;
395 }
396 }
397 assert!(fired);
398 }
399}