Skip to main content

badterm_core/
kermit.rs

1//! Kermit file transfers, as C-Kermit, G-Kermit and Kermit-95 speak them,
2//! in both directions.
3//!
4//! Packets are `SOH LEN SEQ TYPE DATA CHECK CR`, every field but the mark
5//! made printable (`tochar(x) = x + 32`). The two sides trade their
6//! parameters in the Send-Init (`S`) packet and its acknowledgement; this
7//! side offers long packets (up to [`MAX_LONG`] bytes), CRC-16 block checks
8//! (type 3), `~` run-length compression, file attributes (for the size) and
9//! sliding windows of up to [`WINDOW`] packets, and takes whatever the other
10//! side can do.
11//!
12//! With a window, a sender keeps sending data packets while earlier ones
13//! wait for their ACKs, which keeps a slow or distant link busy. The
14//! receiver acknowledges each packet as it comes, keeps ones that arrive
15//! after a damaged one until it is sent again, and writes them in order.
16//! Packets other than data go one at a time, as without a window.
17//!
18//! A download starts by itself when the BBS's Kermit sends its `S` packet
19//! (see [`find_send_init`]); an upload is started by the user once the
20//! BBS's Kermit is receiving.
21
22use std::collections::{HashMap, VecDeque};
23use std::fs::{self, File};
24use std::io::{BufWriter, Read, Write};
25use std::path::PathBuf;
26use std::time::{Duration, Instant};
27
28use anyhow::{Result, bail};
29
30use crate::transfer::{self, Common, Direction, Outcome, Progress};
31
32const SOH: u8 = 0x01;
33const CR: u8 = 0x0D;
34/// The longest packet offered (its data and check).
35pub const MAX_LONG: usize = 4000;
36/// How long to wait for a packet before resending (or NAKing).
37const TIMEOUT: Duration = Duration::from_secs(8);
38const MAX_RETRIES: u32 = 10;
39/// A receiver waiting for the sender's Send-Init gives up after this many
40/// timeouts.
41const START_TRIES: u32 = 15;
42
43/// The most packets offered in flight at once (Kermit allows 31).
44pub const WINDOW: usize = 16;
45
46/// Capability bits.
47const CAP_LONG: u8 = 2;
48const CAP_WINDOWS: u8 = 4;
49const CAP_ATTRS: u8 = 8;
50
51fn tochar(x: u8) -> u8 {
52    x + 32
53}
54
55fn unchar(c: u8) -> u8 {
56    c.wrapping_sub(32)
57}
58
59fn ctl(c: u8) -> u8 {
60    c ^ 64
61}
62
63/// The Kermit block checks: 1 (6-bit sum), 2 (12-bit sum), 3 (CRC-16).
64fn block_check(kind: u8, data: &[u8]) -> Vec<u8> {
65    let sum: u32 = data.iter().map(|&b| b as u32).sum();
66    match kind {
67        2 => vec![tochar(((sum >> 6) & 63) as u8), tochar((sum & 63) as u8)],
68        3 => {
69            let crc = crc16_kermit(data);
70            vec![tochar(((crc >> 12) & 0x0F) as u8), tochar(((crc >> 6) & 0x3F) as u8), tochar((crc & 0x3F) as u8)]
71        }
72        _ => vec![tochar(((sum + ((sum & 192) >> 6)) & 63) as u8)],
73    }
74}
75
76/// CRC-16/KERMIT: CCITT, reflected (0x8408), starting at 0.
77pub fn crc16_kermit(data: &[u8]) -> u16 {
78    let mut crc: u16 = 0;
79    for &b in data {
80        crc ^= b as u16;
81        for _ in 0..8 {
82            crc = if crc & 1 != 0 { (crc >> 1) ^ 0x8408 } else { crc >> 1 };
83        }
84    }
85    crc
86}
87
88/// Where a Send-Init packet starts in `data`, if there is one: what a BBS's
89/// Kermit sends when told to send a file.
90pub fn find_send_init(data: &[u8]) -> Option<usize> {
91    data.windows(4).position(|w| w[0] == SOH && (b' '..=b'~').contains(&w[1]) && w[2] == b' ' && w[3] == b'S')
92}
93
94/// The parameters one side announces in its Send-Init.
95#[derive(Clone, Debug)]
96struct Params {
97    /// The longest packet it can take, short (up to 94) and long.
98    maxl: usize,
99    maxlx: Option<usize>,
100    /// Seconds before it wants to be timed out.
101    time: u8,
102    npad: u8,
103    padc: u8,
104    eol: u8,
105    qctl: u8,
106    /// 'Y' (will if asked), 'N' (won't), or the prefix it wants.
107    qbin: u8,
108    chkt: u8,
109    /// The repeat prefix, or ' ' for none.
110    rept: u8,
111    capas: u8,
112    /// The sliding window's size, if it can do them.
113    window: usize,
114}
115
116impl Params {
117    fn ours() -> Params {
118        Params {
119            maxl: 94,
120            maxlx: Some(MAX_LONG),
121            time: TIMEOUT.as_secs() as u8,
122            npad: 0,
123            padc: 0,
124            eol: CR,
125            qctl: b'#',
126            qbin: b'Y',
127            chkt: 3,
128            rept: b'~',
129            capas: CAP_LONG | CAP_WINDOWS | CAP_ATTRS,
130            window: WINDOW,
131        }
132    }
133
134    fn encode(&self) -> Vec<u8> {
135        let maxlx = self.maxlx.unwrap_or(0);
136        vec![
137            tochar(self.maxl as u8),
138            tochar(self.time),
139            tochar(self.npad),
140            ctl(self.padc),
141            tochar(self.eol),
142            self.qctl,
143            self.qbin,
144            b'0' + self.chkt,
145            self.rept,
146            tochar(self.capas),
147            tochar(self.window as u8),
148            tochar((maxlx / 95) as u8),
149            tochar((maxlx % 95) as u8),
150        ]
151    }
152
153    /// Read the other side's; fields it leaves out take their defaults.
154    fn decode(d: &[u8]) -> Params {
155        let at = |i: usize| d.get(i).copied();
156        let mut p = Params {
157            maxl: at(0).map_or(80, |c| unchar(c).clamp(10, 94) as usize),
158            maxlx: None,
159            time: at(1).map_or(5, unchar),
160            npad: at(2).map_or(0, unchar),
161            padc: at(3).map_or(0, ctl),
162            eol: at(4).map(unchar).filter(|&c| c > 0).unwrap_or(CR),
163            qctl: at(5).filter(|&c| c != b' ').unwrap_or(b'#'),
164            qbin: at(6).filter(|&c| c != b' ').unwrap_or(b'N'),
165            chkt: at(7).map(|c| c.wrapping_sub(b'0')).filter(|c| (1..=3).contains(c)).unwrap_or(1),
166            rept: at(8).unwrap_or(b' '),
167            capas: 0,
168            window: 1,
169        };
170        // Capability bytes go on while their low bit is set.
171        let mut i = 9;
172        while let Some(c) = at(i) {
173            if i == 9 {
174                p.capas = unchar(c);
175            }
176            i += 1;
177            if unchar(c) & 1 == 0 {
178                break;
179            }
180        }
181        // Then the window size, and the long packet length.
182        if p.capas & CAP_WINDOWS != 0 {
183            p.window = at(i).map_or(1, |c| (unchar(c) as usize).clamp(1, 31));
184        }
185        if p.capas & CAP_LONG != 0 {
186            let len = match (at(i + 1), at(i + 2)) {
187                (Some(a), Some(b)) => unchar(a) as usize * 95 + unchar(b) as usize,
188                _ => 500,
189            };
190            p.maxlx = Some(if len == 0 { 500 } else { len });
191        }
192        p
193    }
194}
195
196/// What both sides agreed on, from one side's point of view.
197#[derive(Clone, Debug)]
198struct Link {
199    /// The longest packet the other side takes from us.
200    send_max: usize,
201    long: bool,
202    chkt: u8,
203    /// The prefixes: ours for sending, theirs for reading what they send.
204    our_qctl: u8,
205    their_qctl: u8,
206    qbin: Option<u8>,
207    rept: Option<u8>,
208    attrs: bool,
209    /// How many packets may be unacknowledged at once.
210    window: usize,
211    eol: u8,
212    npad: u8,
213    padc: u8,
214}
215
216impl Link {
217    /// Before the Send-Init exchange: the basics.
218    fn initial() -> Link {
219        Link {
220            send_max: 80,
221            long: false,
222            chkt: 1,
223            our_qctl: b'#',
224            their_qctl: b'#',
225            qbin: None,
226            rept: None,
227            attrs: false,
228            window: 1,
229            eol: CR,
230            npad: 0,
231            padc: 0,
232        }
233    }
234
235    fn agree(ours: &Params, theirs: &Params) -> Link {
236        let long = ours.capas & theirs.capas & CAP_LONG != 0;
237        let qbin = match (ours.qbin, theirs.qbin) {
238            (b'Y', c) | (c, b'Y') if c != b'Y' && c != b'N' && is_prefix(c) => Some(c),
239            (a, b) if a == b && is_prefix(a) => Some(a),
240            _ => None,
241        };
242        let rept = (ours.rept == theirs.rept && is_prefix(ours.rept)).then_some(ours.rept);
243        Link {
244            send_max: if long { theirs.maxlx.unwrap_or(500).min(MAX_LONG) } else { theirs.maxl },
245            long,
246            chkt: if ours.chkt == theirs.chkt { ours.chkt } else { 1 },
247            our_qctl: ours.qctl,
248            their_qctl: theirs.qctl,
249            qbin,
250            rept,
251            attrs: ours.capas & theirs.capas & CAP_ATTRS != 0,
252            window: if ours.capas & theirs.capas & CAP_WINDOWS != 0 { ours.window.min(theirs.window) } else { 1 },
253            eol: theirs.eol,
254            npad: theirs.npad,
255            padc: theirs.padc,
256        }
257    }
258
259    /// A packet on the wire. Send-Init packets and their ACKs are always
260    /// checked with type 1.
261    fn packet(&self, seq: u8, kind: u8, data: &[u8], chkt: u8) -> Vec<u8> {
262        let chk_len = chkt as usize;
263        let mut body = Vec::with_capacity(data.len() + 10);
264        let short_len = data.len() + 2 + chk_len;
265        if short_len <= 94 {
266            body.push(tochar(short_len as u8));
267            body.push(tochar(seq));
268            body.push(kind);
269        } else {
270            let ext = data.len() + chk_len;
271            body.extend_from_slice(&[tochar(0), tochar(seq), kind, tochar((ext / 95) as u8), tochar((ext % 95) as u8)]);
272            body.extend(block_check(1, &body));
273        }
274        body.extend_from_slice(data);
275        let chk = block_check(chkt, &body);
276        let mut out = vec![self.padc; self.npad as usize];
277        out.push(SOH);
278        out.extend(body);
279        out.extend(chk);
280        out.push(self.eol);
281        out
282    }
283
284    /// How much data fits in one packet for the other side.
285    fn room(&self) -> usize {
286        if self.long { self.send_max - self.chkt as usize - 7 } else { self.send_max - 2 - self.chkt as usize }
287    }
288
289    /// Encode as much of `src` as fits in `room` bytes; returns the encoded
290    /// data and how many bytes of `src` it holds.
291    fn encode(&self, src: &[u8], room: usize) -> (Vec<u8>, usize) {
292        let mut out = Vec::with_capacity(room);
293        let mut i = 0;
294        while i < src.len() {
295            let c = src[i];
296            let mut run = 1;
297            if self.rept.is_some() {
298                while i + run < src.len() && src[i + run] == c && run < 94 {
299                    run += 1;
300                }
301            }
302            let one = self.encode_byte(c);
303            let unit = match self.rept {
304                Some(r) if run >= 3 => {
305                    let mut u = vec![r, tochar(run as u8)];
306                    u.extend_from_slice(&one);
307                    u
308                }
309                _ => {
310                    run = 1;
311                    one
312                }
313            };
314            if out.len() + unit.len() > room {
315                break;
316            }
317            out.extend(unit);
318            i += run;
319        }
320        (out, i)
321    }
322
323    fn encode_byte(&self, mut c: u8) -> Vec<u8> {
324        let mut out = Vec::with_capacity(3);
325        if let Some(q) = self.qbin
326            && c & 0x80 != 0
327        {
328            out.push(q);
329            c &= 0x7F;
330        }
331        let low = c & 0x7F;
332        if low < 32 || low == 127 {
333            out.extend_from_slice(&[self.our_qctl, ctl(c)]);
334        } else if low == self.our_qctl || Some(low) == self.qbin || Some(low) == self.rept {
335            out.extend_from_slice(&[self.our_qctl, c]);
336        } else {
337            out.push(c);
338        }
339        out
340    }
341
342    /// Decode a packet's data from the other side.
343    fn decode(&self, data: &[u8]) -> Vec<u8> {
344        let mut out = Vec::with_capacity(data.len());
345        let mut i = 0;
346        while i < data.len() {
347            let mut count = 1;
348            if let Some(r) = self.rept
349                && data[i] == r
350                && i + 1 < data.len()
351            {
352                count = unchar(data[i + 1]) as usize;
353                i += 2;
354            }
355            let mut high = 0;
356            if let Some(q) = self.qbin
357                && data.get(i) == Some(&q)
358            {
359                high = 0x80;
360                i += 1;
361            }
362            let Some(&b) = data.get(i) else { break };
363            i += 1;
364            let c = if b == self.their_qctl {
365                let Some(&q) = data.get(i) else { break };
366                i += 1;
367                if (0x3F..=0x5F).contains(&(q & 0x7F)) { ctl(q) } else { q }
368            } else {
369                b
370            };
371            out.extend(std::iter::repeat_n(c | high, count));
372        }
373        out
374    }
375}
376
377/// A prefix character Kermit allows (`!` to `>`, or `` ` `` to `~`).
378fn is_prefix(c: u8) -> bool {
379    (33..=62).contains(&c) || (96..=126).contains(&c)
380}
381
382/// A packet read from the other side.
383#[derive(Debug, PartialEq, Eq)]
384struct Packet {
385    seq: u8,
386    kind: u8,
387    data: Vec<u8>,
388}
389
390/// Take the next packet from `buf`: Ok(None) until a whole one is there,
391/// Err with its sequence number for a damaged one. Noise before it goes.
392fn read_packet(buf: &mut Vec<u8>, chkt: u8) -> std::result::Result<Option<Packet>, Option<u8>> {
393    let Some(start) = buf.iter().position(|&b| b == SOH) else {
394        buf.clear();
395        return Ok(None);
396    };
397    buf.drain(..start);
398    if buf.len() < 4 {
399        return Ok(None);
400    }
401    let len = unchar(buf[1]) as usize;
402    let (seq, kind) = (unchar(buf[2]), buf[3]);
403    // Send-Init packets and ACKs to them are type 1.
404    let chk_len = if kind == b'S' { 1 } else { chkt as usize };
405    let (header, total) = if len == 0 {
406        if buf.len() < 7 {
407            return Ok(None);
408        }
409        if block_check(1, &buf[1..6]) != buf[6..7] {
410            buf.drain(..1);
411            return Err(Some(seq & 63));
412        }
413        (7, 7 + unchar(buf[4]) as usize * 95 + unchar(buf[5]) as usize)
414    } else if len < 3 {
415        buf.drain(..1);
416        return Err(None);
417    } else {
418        (4, 2 + len)
419    };
420    if total > MAX_LONG + 16 || total < header + chk_len {
421        buf.drain(..1);
422        return Err(None);
423    }
424    if buf.len() < total {
425        return Ok(None);
426    }
427    let (body, chk) = buf[1..total].split_at(total - 1 - chk_len);
428    if block_check(chk_len as u8, body) != chk {
429        // Only its mark goes: a good packet may follow inside it.
430        buf.drain(..1);
431        return Err(Some(seq & 63));
432    }
433    let raw: Vec<u8> = buf.drain(..total).collect();
434    Ok(Some(Packet { seq: seq & 63, kind, data: raw[header..total - chk_len].to_vec() }))
435}
436
437#[derive(Clone, Copy, Debug, PartialEq, Eq)]
438enum State {
439    /// Receiving: waiting for the Send-Init. Sending: waiting for its ACK.
440    Init,
441    /// Receiving: waiting for a file header or the end of the batch.
442    File,
443    /// Receiving: the file's attributes or data. Sending: data under way.
444    Data,
445    /// Sending: the header (F), attributes (A), end of file (Z), or end of
446    /// batch (B) is out, waiting for its ACK.
447    Header,
448    Attrs,
449    Eof,
450    Break,
451    Done,
452}
453
454/// A data packet sent and not yet acknowledged.
455struct Flight {
456    seq: u8,
457    packet: Vec<u8>,
458    acked: bool,
459    retries: u32,
460}
461
462/// How far `b` is after `a`, counting sequence numbers round from 63 to 0.
463fn seq_after(a: u8, b: u8) -> usize {
464    (b.wrapping_sub(a) & 63) as usize
465}
466
467enum Role {
468    Receive { dir: PathBuf, file: Option<(BufWriter<File>, PathBuf)>, saved: Vec<PathBuf> },
469    Send { queue: VecDeque<PathBuf>, file: Option<(File, PathBuf)>, sent: Vec<PathBuf>, eof: bool, skip: bool },
470}
471
472/// A Kermit transfer, either way.
473pub struct Kermit {
474    role: Role,
475    state: State,
476    link: Link,
477    seq: u8,
478    /// The last packet sent, for resending.
479    last: Vec<u8>,
480    /// Read but not yet sent (sending), as packets go out.
481    pending_data: Vec<u8>,
482    /// Sending with a window: the data packets out, oldest first.
483    flight: VecDeque<Flight>,
484    /// Receiving: packets that came ahead of one still missing.
485    early: HashMap<u8, Packet>,
486    /// Receiving: the ACK last sent for each sequence number, to send
487    /// again if the packet comes again (empty: none yet).
488    acks: Vec<Vec<u8>>,
489    retries: u32,
490    last_heard: Instant,
491    pub(crate) c: Common,
492}
493
494impl Kermit {
495    /// Receive files into `dir`. Feed it the BBS's Send-Init packet (or
496    /// wait for it).
497    pub fn receive(dir: PathBuf) -> Kermit {
498        Kermit::new(Role::Receive { dir, file: None, saved: Vec::new() }, Direction::Download)
499    }
500
501    /// Send files to a Kermit that is ready to receive.
502    pub fn send(paths: Vec<PathBuf>) -> Result<Kermit> {
503        if paths.is_empty() {
504            bail!("no files to upload");
505        }
506        for p in &paths {
507            if !p.is_file() {
508                bail!("not a file: {}", p.display());
509            }
510        }
511        let mut k = Kermit::new(
512            Role::Send { queue: paths.into(), file: None, sent: Vec::new(), eof: false, skip: false },
513            Direction::Upload,
514        );
515        k.c.progress.files_queued = k.queued();
516        Ok(k)
517    }
518
519    fn new(role: Role, direction: Direction) -> Kermit {
520        Kermit {
521            role,
522            state: State::Init,
523            link: Link::initial(),
524            seq: 0,
525            last: Vec::new(),
526            pending_data: Vec::new(),
527            flight: VecDeque::new(),
528            early: HashMap::new(),
529            acks: vec![Vec::new(); 64],
530            retries: 0,
531            last_heard: Instant::now(),
532            c: Common::new(direction),
533        }
534    }
535
536    pub(crate) fn progress(&self) -> &Progress {
537        &self.c.progress
538    }
539
540    pub fn enqueue(&mut self, path: PathBuf) -> Result<()> {
541        match &mut self.role {
542            Role::Send { queue, .. } => {
543                queue.push_back(path);
544                self.c.progress.files_queued = self.queued();
545                Ok(())
546            }
547            Role::Receive { .. } => bail!("a download is in progress"),
548        }
549    }
550
551    fn queued(&self) -> usize {
552        match &self.role {
553            Role::Send { queue, .. } => queue.len(),
554            Role::Receive { .. } => 0,
555        }
556    }
557
558    fn sending(&self) -> bool {
559        matches!(self.role, Role::Send { .. })
560    }
561
562    /// The check type in use for the packets of the current state.
563    fn chkt(&self) -> u8 {
564        if self.state == State::Init { 1 } else { self.link.chkt }
565    }
566
567    fn put(&mut self, kind: u8, data: &[u8], out: &mut Vec<u8>) {
568        let chkt = if kind == b'S' || self.state == State::Init { 1 } else { self.link.chkt };
569        self.last = self.link.packet(self.seq, kind, data, chkt);
570        out.extend_from_slice(&self.last);
571        self.retries = 0;
572        self.last_heard = Instant::now();
573    }
574
575    /// Acknowledge packet `seq` (receiving), unless that was done when it
576    /// came ahead of its turn.
577    fn ack(&mut self, seq: u8, data: &[u8], out: &mut Vec<u8>) {
578        self.seq = seq;
579        if self.acks[seq as usize].is_empty() {
580            self.put(b'Y', data, out);
581            self.acks[seq as usize] = self.last.clone();
582        } else {
583            self.last = self.acks[seq as usize].clone();
584            self.retries = 0;
585            self.last_heard = Instant::now();
586        }
587    }
588
589    /// Sending data with a window.
590    fn windowed(&self) -> bool {
591        self.sending() && self.state == State::Data && self.link.window > 1
592    }
593
594    /// Send again what the other side is most likely missing.
595    fn resend(&mut self, out: &mut Vec<u8>) {
596        if self.windowed() {
597            if let Some(f) = self.flight.iter_mut().find(|f| !f.acked) {
598                f.retries += 1;
599                if f.retries > MAX_RETRIES {
600                    return self.fail("too many retries", out);
601                }
602                out.extend_from_slice(&f.packet);
603            }
604        } else {
605            out.extend_from_slice(&self.last);
606        }
607    }
608
609    fn fail(&mut self, msg: impl Into<String>, out: &mut Vec<u8>) {
610        if self.c.outcome.is_some() {
611            return;
612        }
613        let msg = msg.into();
614        let data = self.link.encode(msg.as_bytes(), 80).0;
615        let p = self.link.packet(self.seq, b'E', &data, self.chkt());
616        out.extend_from_slice(&p);
617        self.drop_partial();
618        self.c.outcome = Some(Outcome::Failed(msg));
619    }
620
621    fn drop_partial(&mut self) {
622        if let Role::Receive { file, .. } = &mut self.role
623            && let Some((_, path)) = file.take()
624        {
625            let _ = fs::remove_file(path);
626        }
627    }
628
629    pub(crate) fn cancel(&mut self, out: &mut Vec<u8>) {
630        self.c.cancelled = true;
631        if self.c.outcome.is_none() {
632            let p = self.link.packet(self.seq, b'E', b"Cancelled by the user", self.chkt());
633            out.extend_from_slice(&p);
634        }
635        self.drop_partial();
636        self.c.outcome.get_or_insert(Outcome::Cancelled);
637    }
638
639    /// The sender starts by itself: call once, before the first feed.
640    pub fn start(&mut self, out: &mut Vec<u8>) {
641        if self.sending() && self.state == State::Init && self.last.is_empty() {
642            let data = Params::ours().encode();
643            self.put(b'S', &data, out);
644        }
645    }
646
647    pub(crate) fn tick(&mut self, now: Instant, out: &mut Vec<u8>) {
648        if self.c.outcome.is_some() || self.state == State::Done {
649            return;
650        }
651        if now.duration_since(self.last_heard) < TIMEOUT {
652            return;
653        }
654        self.last_heard = now;
655        self.retries += 1;
656        let limit = if !self.sending() && self.state == State::Init { START_TRIES } else { MAX_RETRIES };
657        if self.retries > limit {
658            return self.fail("the other side stopped answering", out);
659        }
660        if self.sending() {
661            self.resend(out);
662        } else if self.link.window > 1 && self.state != State::Init {
663            // Ask for the first packet still missing.
664            let p = self.link.packet((self.seq + 1) & 63, b'N', &[], self.chkt());
665            out.extend_from_slice(&p);
666        } else if !self.last.is_empty() {
667            out.extend_from_slice(&self.last);
668        } else {
669            // A receiver that has heard nothing NAKs packet 0.
670            let p = self.link.packet(0, b'N', &[], 1);
671            out.extend_from_slice(&p);
672        }
673    }
674
675    pub(crate) fn pump(&mut self, out: &mut Vec<u8>) {
676        while self.c.outcome.is_none() && !self.c.pending.is_empty() {
677            let chkt = self.chkt();
678            match read_packet(&mut self.c.pending, chkt) {
679                Ok(None) => break,
680                Ok(Some(p)) => {
681                    self.last_heard = Instant::now();
682                    if p.kind == b'E' {
683                        let msg = String::from_utf8_lossy(&self.link.decode(&p.data)).into_owned();
684                        self.drop_partial();
685                        self.c.outcome = Some(Outcome::Failed(format!("the other side stopped: {msg}")));
686                        break;
687                    }
688                    if self.sending() { self.sender_got(p, out) } else { self.receiver_got(p, out) }
689                }
690                Err(seq) => {
691                    // Damaged: ask again (receiving), or resend (sending).
692                    if self.sending() {
693                        self.resend(out);
694                    } else {
695                        let p = self.link.packet(seq.unwrap_or(self.seq.wrapping_add(1) & 63), b'N', &[], chkt);
696                        out.extend_from_slice(&p);
697                    }
698                }
699            }
700        }
701    }
702
703    fn receiver_got(&mut self, p: Packet, out: &mut Vec<u8>) {
704        if self.state == State::Init {
705            if p.seq == 0 {
706                self.take_in_order(p, out);
707            } else if p.seq == self.seq && !self.last.is_empty() {
708                out.extend_from_slice(&self.last);
709            }
710            return;
711        }
712        let window = self.link.window;
713        let next = (self.seq + 1) & 63;
714        let ahead = seq_after(next, p.seq);
715        if ahead >= window {
716            // One already taken, sent again: its ACK must have been lost.
717            if seq_after(p.seq, self.seq) < window {
718                out.extend_from_slice(&self.acks[p.seq as usize]);
719            }
720            return;
721        }
722        if self.early.contains_key(&p.seq) {
723            out.extend_from_slice(&self.acks[p.seq as usize]);
724            return;
725        }
726        // New: whatever was acknowledged under this number is past.
727        self.acks[p.seq as usize].clear();
728        if ahead > 0 {
729            // Ask again for the ones newly missing before it (those before
730            // an earlier early one were asked for then), and keep it; data
731            // is acknowledged now so the sender's window moves on.
732            let asked = self.early.keys().map(|&k| seq_after(next, k) + 1).max().unwrap_or(0);
733            for n in asked..ahead {
734                let seq = (next as usize + n) as u8 & 63;
735                if !self.early.contains_key(&seq) {
736                    let nak = self.link.packet(seq, b'N', &[], self.link.chkt);
737                    out.extend_from_slice(&nak);
738                }
739            }
740            if p.kind == b'D' {
741                let ack = self.link.packet(p.seq, b'Y', &[], self.link.chkt);
742                out.extend_from_slice(&ack);
743                self.acks[p.seq as usize] = ack;
744            }
745            self.early.insert(p.seq, p);
746            return;
747        }
748        self.take_in_order(p, out);
749        while self.c.outcome.is_none()
750            && let Some(p) = self.early.remove(&((self.seq + 1) & 63))
751        {
752            self.take_in_order(p, out);
753        }
754    }
755
756    /// The next packet in order.
757    fn take_in_order(&mut self, p: Packet, out: &mut Vec<u8>) {
758        match (self.state, p.kind) {
759            (State::Init, b'S') => {
760                let theirs = Params::decode(&p.data);
761                let ours = Params::ours();
762                self.ack(p.seq, &ours.encode(), out);
763                self.link = Link::agree(&ours, &theirs);
764                log::info!("Kermit: window {}, packets of {}", self.link.window, self.link.send_max);
765                self.state = State::File;
766            }
767            (State::File, b'F') => {
768                let wire = self.link.decode(&p.data);
769                let name = transfer::sanitize_file_name(&wire);
770                match self.open(&name) {
771                    Ok(path) => {
772                        let used = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or(name);
773                        let data = self.link.encode(used.as_bytes(), self.link.room()).0;
774                        self.ack(p.seq, &data, out);
775                        self.state = State::Data;
776                    }
777                    Err(e) => self.fail(format!("saving {name}: {e}"), out),
778                }
779            }
780            // A text header (`X`), to show rather than save: not shown.
781            (State::File, b'X') => self.ack(p.seq, &[], out),
782            (State::File, b'B') => {
783                self.ack(p.seq, &[], out);
784                self.state = State::Done;
785                if let Role::Receive { saved, .. } = &self.role {
786                    self.c.outcome = Some(Outcome::Completed(saved.clone()));
787                }
788            }
789            (State::Data, b'A') => {
790                self.c.progress.file_size = attribute_size(&p.data);
791                self.ack(p.seq, b"Y", out);
792            }
793            (State::Data, b'D') => {
794                let data = self.link.decode(&p.data);
795                if let Role::Receive { file: Some((f, path)), .. } = &mut self.role
796                    && let Err(e) = f.write_all(&data)
797                {
798                    let msg = format!("writing {}: {e}", path.display());
799                    return self.fail(msg, out);
800                }
801                self.c.progress.file_bytes += data.len() as u64;
802                self.ack(p.seq, &[], out);
803            }
804            (State::Data, b'Z') => {
805                // "D" in the data: the sender discarded the file.
806                let discard = self.link.decode(&p.data).first() == Some(&b'D');
807                if let Role::Receive { file, saved, .. } = &mut self.role
808                    && let Some((mut f, path)) = file.take()
809                {
810                    if discard {
811                        drop(f);
812                        let _ = fs::remove_file(&path);
813                    } else if let Err(e) = f.flush() {
814                        let msg = format!("writing {}: {e}", path.display());
815                        return self.fail(msg, out);
816                    } else {
817                        saved.push(path);
818                        self.c.progress.files_done += 1;
819                    }
820                }
821                self.ack(p.seq, &[], out);
822                self.state = State::File;
823            }
824            (_, kind) => self.fail(format!("unexpected Kermit packet '{}'", kind as char), out),
825        }
826    }
827
828    fn open(&mut self, name: &str) -> std::io::Result<PathBuf> {
829        let Role::Receive { dir, file, .. } = &mut self.role else { unreachable!("receiving") };
830        fs::create_dir_all(&*dir)?;
831        let path = transfer::unique_path(dir, name)?;
832        *file = Some((BufWriter::new(File::create(&path)?), path.clone()));
833        let p = &mut self.c.progress;
834        p.file_name = name.to_string();
835        p.file_size = None;
836        p.file_bytes = 0;
837        p.file_started = Instant::now();
838        Ok(path)
839    }
840
841    fn sender_got(&mut self, p: Packet, out: &mut Vec<u8>) {
842        if self.windowed() {
843            return self.window_got(p, out);
844        }
845        // A NAK for the next packet acknowledges this one.
846        let acked = match p.kind {
847            b'Y' => p.seq == self.seq,
848            b'N' => p.seq == (self.seq + 1) & 63,
849            _ => false,
850        };
851        if !acked {
852            if p.kind == b'N' && p.seq == self.seq {
853                self.retries += 1;
854                if self.retries > MAX_RETRIES {
855                    return self.fail("too many retries", out);
856                }
857                out.extend_from_slice(&self.last);
858            }
859            return;
860        }
861        let reply = if p.kind == b'Y' { p.data } else { Vec::new() };
862        self.seq = (self.seq + 1) & 63;
863        match self.state {
864            State::Init => {
865                self.link = Link::agree(&Params::ours(), &Params::decode(&reply));
866                log::info!("Kermit: window {}, packets of {}", self.link.window, self.link.send_max);
867                self.next_file(out);
868            }
869            State::Header if self.link.attrs => {
870                let size = self.file_size();
871                let digits = size.to_string();
872                let mut data = vec![b'1', tochar(digits.len() as u8)];
873                data.extend_from_slice(digits.as_bytes());
874                self.state = State::Attrs;
875                self.put(b'A', &data, out);
876            }
877            State::Header => self.start_data(out),
878            State::Attrs => {
879                // "N" in the reply: the receiver does not want the file.
880                if reply.first() == Some(&b'N') {
881                    self.skip_file(out);
882                } else {
883                    self.start_data(out);
884                }
885            }
886            State::Data => {
887                // "X" stops this file, "Z" the whole batch.
888                match reply.first() {
889                    Some(b'X') => self.skip_file(out),
890                    Some(b'Z') => {
891                        if let Role::Send { queue, .. } = &mut self.role {
892                            queue.clear();
893                        }
894                        self.skip_file(out);
895                    }
896                    _ => self.send_data(out),
897                }
898            }
899            State::Eof => {
900                if let Role::Send { file, sent, skip, .. } = &mut self.role
901                    && let Some((_, path)) = file.take()
902                {
903                    if !*skip {
904                        sent.push(path);
905                        self.c.progress.files_done += 1;
906                    }
907                    *skip = false;
908                }
909                self.next_file(out);
910            }
911            State::Break => {
912                self.state = State::Done;
913                if let Role::Send { sent, .. } = &self.role {
914                    self.c.outcome = Some(Outcome::Completed(sent.clone()));
915                }
916            }
917            State::File | State::Done => {}
918        }
919    }
920
921    fn file_size(&self) -> u64 {
922        match &self.role {
923            Role::Send { file: Some((f, _)), .. } => f.metadata().map(|m| m.len()).unwrap_or(0),
924            _ => 0,
925        }
926    }
927
928    /// Open the next file and send its header, or end the batch.
929    fn next_file(&mut self, out: &mut Vec<u8>) {
930        let Role::Send { queue, file, eof, .. } = &mut self.role else { return };
931        let Some(path) = queue.pop_front() else {
932            self.state = State::Break;
933            return self.put(b'B', &[], out);
934        };
935        let f = match File::open(&path) {
936            Ok(f) => f,
937            Err(e) => return self.fail(format!("reading {}: {e}", path.display()), out),
938        };
939        let name = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_else(|| "file".into());
940        let size = f.metadata().map(|m| m.len()).ok();
941        *file = Some((f, path));
942        *eof = false;
943        self.pending_data.clear();
944        let p = &mut self.c.progress;
945        p.file_name = name.clone();
946        p.file_size = size;
947        p.file_bytes = 0;
948        p.file_started = Instant::now();
949        p.files_queued = queue.len();
950        let data = self.link.encode(name.as_bytes(), self.link.room()).0;
951        self.state = State::Header;
952        self.put(b'F', &data, out);
953    }
954
955    fn start_data(&mut self, out: &mut Vec<u8>) {
956        self.state = State::Data;
957        if self.link.window > 1 {
958            // `fill_window` numbers from the last packet sent, the one
959            // just acknowledged.
960            self.seq = self.seq.wrapping_sub(1) & 63;
961            self.flight.clear();
962            self.fill_window(out);
963        } else {
964            self.send_data(out);
965        }
966    }
967
968    /// An ACK or NAK while data goes out with a window.
969    fn window_got(&mut self, p: Packet, out: &mut Vec<u8>) {
970        let i = self.flight.iter().position(|f| f.seq == p.seq);
971        match (p.kind, i) {
972            (b'Y', Some(i)) => {
973                self.flight[i].acked = true;
974                // "X" stops this file, "Z" the whole batch.
975                if let Some(&c @ (b'X' | b'Z')) = p.data.first() {
976                    self.stop_file(c == b'Z');
977                }
978            }
979            (b'N', Some(i)) if !self.flight[i].acked => {
980                let f = &mut self.flight[i];
981                f.retries += 1;
982                if f.retries > MAX_RETRIES {
983                    return self.fail("too many retries", out);
984                }
985                out.extend_from_slice(&f.packet);
986            }
987            // A NAK for the packet after all those out: they all arrived.
988            (b'N', None) if p.seq == (self.seq + 1) & 63 => self.flight.iter_mut().for_each(|f| f.acked = true),
989            _ => return,
990        }
991        self.last_heard = Instant::now();
992        self.retries = 0;
993        while self.flight.front().is_some_and(|f| f.acked) {
994            self.flight.pop_front();
995        }
996        self.fill_window(out);
997    }
998
999    /// Stop reading the file being sent (the receiver asked); its end goes
1000    /// out, saying to discard it, once the packets out are acknowledged.
1001    fn stop_file(&mut self, whole_batch: bool) {
1002        if let Role::Send { queue, eof, skip, .. } = &mut self.role {
1003            *eof = true;
1004            *skip = true;
1005            if whole_batch {
1006                queue.clear();
1007            }
1008        }
1009        self.pending_data.clear();
1010    }
1011
1012    /// Send data packets until the window is full; at the end of the file,
1013    /// once they are all acknowledged, its end.
1014    fn fill_window(&mut self, out: &mut Vec<u8>) {
1015        while self.flight.len() < self.link.window {
1016            let Some(data) = self.next_chunk(out) else { return };
1017            if data.is_empty() {
1018                break;
1019            }
1020            self.seq = (self.seq + 1) & 63;
1021            let packet = self.link.packet(self.seq, b'D', &data, self.link.chkt);
1022            out.extend_from_slice(&packet);
1023            self.flight.push_back(Flight { seq: self.seq, packet, acked: false, retries: 0 });
1024            self.last_heard = Instant::now();
1025        }
1026        let at_end = matches!(self.role, Role::Send { eof: true, .. }) && self.pending_data.is_empty();
1027        if self.flight.is_empty() && at_end {
1028            let skip = matches!(self.role, Role::Send { skip: true, .. });
1029            self.seq = (self.seq + 1) & 63;
1030            self.state = State::Eof;
1031            self.put(b'Z', if skip { b"D" } else { b"" }, out);
1032        }
1033    }
1034
1035    /// The next data packet's data, empty at the end of the file; None
1036    /// when the file could not be read (and the transfer failed).
1037    fn next_chunk(&mut self, out: &mut Vec<u8>) -> Option<Vec<u8>> {
1038        let room = self.link.room();
1039        loop {
1040            // Keep enough read ahead to fill a packet even with no
1041            // compression.
1042            if let Role::Send { file: Some((f, path)), eof, .. } = &mut self.role
1043                && !*eof
1044                && self.pending_data.len() < room * 2
1045            {
1046                let mut buf = vec![0u8; room * 4];
1047                match f.read(&mut buf) {
1048                    Ok(0) => *eof = true,
1049                    Ok(n) => self.pending_data.extend_from_slice(&buf[..n]),
1050                    Err(e) => {
1051                        let msg = format!("reading {}: {e}", path.display());
1052                        self.fail(msg, out);
1053                        return None;
1054                    }
1055                }
1056            }
1057            if !self.pending_data.is_empty() {
1058                break;
1059            }
1060            if matches!(self.role, Role::Send { eof: true, .. } | Role::Send { file: None, .. }) {
1061                return Some(Vec::new());
1062            }
1063        }
1064        let (data, used) = self.link.encode(&self.pending_data, room);
1065        self.pending_data.drain(..used);
1066        self.c.progress.file_bytes += used as u64;
1067        Some(data)
1068    }
1069
1070    /// Send the next data packet, or the end of the file.
1071    fn send_data(&mut self, out: &mut Vec<u8>) {
1072        match self.next_chunk(out) {
1073            None => {}
1074            Some(data) if data.is_empty() => {
1075                self.state = State::Eof;
1076                self.put(b'Z', &[], out);
1077            }
1078            Some(data) => self.put(b'D', &data, out),
1079        }
1080    }
1081
1082    /// Stop sending this file: an end of file that says "discard".
1083    fn skip_file(&mut self, out: &mut Vec<u8>) {
1084        if let Role::Send { skip, .. } = &mut self.role {
1085            *skip = true;
1086        }
1087        self.pending_data.clear();
1088        self.state = State::Eof;
1089        self.put(b'Z', b"D", out);
1090    }
1091}
1092
1093/// The file size from an attribute packet: `1` (bytes) or `!` (KiB).
1094fn attribute_size(data: &[u8]) -> Option<u64> {
1095    let mut i = 0;
1096    let mut size = None;
1097    while i + 1 < data.len() {
1098        let (tag, len) = (data[i], unchar(data[i + 1]) as usize);
1099        let value = data.get(i + 2..i + 2 + len)?;
1100        let n = std::str::from_utf8(value).ok().and_then(|s| s.trim().parse::<u64>().ok());
1101        match tag {
1102            b'1' => size = n.or(size),
1103            b'!' if size.is_none() => size = n.map(|k| k * 1024),
1104            _ => {}
1105        }
1106        i += 2 + len;
1107    }
1108    size
1109}
1110
1111#[cfg(test)]
1112mod tests {
1113    use super::*;
1114
1115    fn temp_dir(name: &str) -> PathBuf {
1116        let d = std::env::temp_dir().join(format!("badterm-kermit-{name}-{}", std::process::id()));
1117        let _ = fs::remove_dir_all(&d);
1118        fs::create_dir_all(&d).unwrap();
1119        d
1120    }
1121
1122    /// Run a sender against a receiver in memory, optionally damaging
1123    /// every `damage`-th chunk on the way to the receiver.
1124    fn run(tx: &mut Kermit, rx: &mut Kermit, damage: Option<usize>) {
1125        run_lossy(tx, rx, damage, None);
1126    }
1127
1128    /// Drop every `nth` packet, from the start of each packet.
1129    fn drop_packets(data: Vec<u8>, nth: usize, count: &mut usize) -> Vec<u8> {
1130        let mut out = Vec::new();
1131        for packet in data.split_inclusive(|&b| b == CR) {
1132            *count += 1;
1133            if !count.is_multiple_of(nth) {
1134                out.extend_from_slice(packet);
1135            }
1136        }
1137        out
1138    }
1139
1140    /// [`run`], also losing every `lose`-th packet each way.
1141    fn run_lossy(tx: &mut Kermit, rx: &mut Kermit, damage: Option<usize>, lose: Option<usize>) {
1142        let (mut to_rx, mut to_tx) = (Vec::new(), Vec::new());
1143        let (mut sent, mut answered) = (0, 0);
1144        tx.start(&mut to_rx);
1145        for n in 0..200_000 {
1146            if tx.c.outcome.is_some() && rx.c.outcome.is_some() {
1147                return;
1148            }
1149            let mut data = std::mem::take(&mut to_rx);
1150            if damage.is_some_and(|d| n % d == d - 1) && data.len() > 8 {
1151                let i = data.len() / 2;
1152                data[i] ^= 0x01;
1153            }
1154            if let Some(nth) = lose {
1155                data = drop_packets(data, nth, &mut sent);
1156            }
1157            rx.c.input(&data);
1158            rx.pump(&mut to_tx);
1159            let mut data = std::mem::take(&mut to_tx);
1160            if let Some(nth) = lose {
1161                data = drop_packets(data, nth + 2, &mut answered);
1162            }
1163            tx.c.input(&data);
1164            tx.pump(&mut to_rx);
1165            if to_rx.is_empty() && to_tx.is_empty() {
1166                // Lost to damage: what a timeout would do, on either side.
1167                tx.last_heard = Instant::now() - TIMEOUT;
1168                tx.tick(Instant::now(), &mut to_rx);
1169                if n % 2 == 1 {
1170                    rx.last_heard = Instant::now() - TIMEOUT;
1171                    rx.tick(Instant::now(), &mut to_tx);
1172                }
1173            }
1174        }
1175        panic!("transfer did not finish");
1176    }
1177
1178    #[test]
1179    fn checks_and_prefixes() {
1180        assert_eq!(crc16_kermit(b"123456789"), 0x2189);
1181        let link = Link { qbin: Some(b'&'), rept: Some(b'~'), ..Link::agree(&Params::ours(), &Params::ours()) };
1182        let src = b"ab\x01#\x80&~cccccc\xff\r\n".to_vec();
1183        let (enc, used) = link.encode(&src, 1000);
1184        assert_eq!(used, src.len());
1185        assert!(enc.iter().all(|&b| (32..127).contains(&b)), "{enc:?}");
1186        assert_eq!(link.decode(&enc), src);
1187        // A short room ends on a whole unit.
1188        let (part, n) = link.encode(&src, 5);
1189        assert_eq!(link.decode(&part), src[..n]);
1190        assert_eq!(attribute_size(b"#(20251031!\"12"), Some(12 * 1024));
1191        assert_eq!(attribute_size(b"1#300!\"12"), Some(300));
1192        assert_eq!(find_send_init(b"junk\x01) S~"), Some(4));
1193    }
1194
1195    #[test]
1196    fn round_trip_two_files() {
1197        let (src, dst) = (temp_dir("src"), temp_dir("dst"));
1198        let mut big = Vec::new();
1199        for i in 0..150_000u32 {
1200            big.push((i.wrapping_mul(2_654_435_761) >> 13) as u8);
1201        }
1202        big.extend_from_slice(&[0; 500]);
1203        fs::write(src.join("big.bin"), &big).unwrap();
1204        fs::write(src.join("empty.txt"), b"").unwrap();
1205        let mut tx = Kermit::send(vec![src.join("big.bin"), src.join("empty.txt")]).unwrap();
1206        let mut rx = Kermit::receive(dst.clone());
1207        run(&mut tx, &mut rx, None);
1208        assert_eq!(tx.c.outcome, Some(Outcome::Completed(vec![src.join("big.bin"), src.join("empty.txt")])));
1209        assert_eq!(rx.c.outcome, Some(Outcome::Completed(vec![dst.join("big.bin"), dst.join("empty.txt")])));
1210        assert_eq!(fs::read(dst.join("big.bin")).unwrap(), big);
1211        assert_eq!(fs::read(dst.join("empty.txt")).unwrap(), b"");
1212        // Long packets with CRCs were agreed.
1213        assert!(tx.link.long && tx.link.chkt == 3 && tx.link.rept == Some(b'~'));
1214    }
1215
1216    #[test]
1217    fn damaged_packets_are_resent() {
1218        let (src, dst) = (temp_dir("dsrc"), temp_dir("ddst"));
1219        let data: Vec<u8> = (0..40_000u32).map(|i| (i % 251) as u8).collect();
1220        fs::write(src.join("a.dat"), &data).unwrap();
1221        let mut tx = Kermit::send(vec![src.join("a.dat")]).unwrap();
1222        let mut rx = Kermit::receive(dst.clone());
1223        run(&mut tx, &mut rx, Some(7));
1224        assert!(matches!(rx.c.outcome, Some(Outcome::Completed(_))), "{:?}", rx.c.outcome);
1225        assert_eq!(fs::read(dst.join("a.dat")).unwrap(), data);
1226    }
1227
1228    #[test]
1229    fn sliding_windows_survive_lost_packets() {
1230        let (src, dst) = (temp_dir("wsrc"), temp_dir("wdst"));
1231        let data: Vec<u8> = (0..300_000u32).map(|i| (i.wrapping_mul(2_654_435_761) >> 9) as u8).collect();
1232        fs::write(src.join("w.dat"), &data).unwrap();
1233        fs::write(src.join("x.dat"), &data[..5000]).unwrap();
1234        // Packets go out ahead of their ACKs.
1235        let mut tx = Kermit::send(vec![src.join("w.dat")]).unwrap();
1236        let mut rx = Kermit::receive(dst.clone());
1237        let mut out = Vec::new();
1238        tx.start(&mut out);
1239        rx.c.input(&out);
1240        let mut back = Vec::new();
1241        rx.pump(&mut back);
1242        tx.c.input(&back);
1243        let mut out = Vec::new();
1244        tx.pump(&mut out);
1245        while tx.state != State::Data {
1246            let mut back = Vec::new();
1247            rx.c.input(&std::mem::take(&mut out));
1248            rx.pump(&mut back);
1249            tx.c.input(&back);
1250            tx.pump(&mut out);
1251        }
1252        assert_eq!((tx.link.window, tx.flight.len()), (WINDOW, WINDOW));
1253        assert_eq!(out.iter().filter(|&&b| b == SOH).count(), WINDOW);
1254        // The whole way, losing packets both ways, and damaging some.
1255        for (lose, damage) in [(Some(5), None), (Some(13), Some(9)), (Some(3), None)] {
1256            let dst = temp_dir("wdst2");
1257            let mut tx = Kermit::send(vec![src.join("w.dat"), src.join("x.dat")]).unwrap();
1258            let mut rx = Kermit::receive(dst.clone());
1259            run_lossy(&mut tx, &mut rx, damage, lose);
1260            assert!(matches!(rx.c.outcome, Some(Outcome::Completed(_))), "{lose:?}: {:?}", rx.c.outcome);
1261            assert!(matches!(tx.c.outcome, Some(Outcome::Completed(_))), "{lose:?}: {:?}", tx.c.outcome);
1262            assert_eq!(fs::read(dst.join("w.dat")).unwrap(), data, "{lose:?}");
1263            assert_eq!(fs::read(dst.join("x.dat")).unwrap(), &data[..5000]);
1264        }
1265        // A Kermit without windows gets one packet at a time.
1266        let mut old = Params::ours();
1267        old.capas &= !CAP_WINDOWS;
1268        let theirs = Params::decode(&old.encode());
1269        assert_eq!(Link::agree(&Params::ours(), &theirs).window, 1);
1270        assert_eq!(Params::decode(&Params::ours().encode()).window, WINDOW);
1271    }
1272
1273    #[test]
1274    fn cancel_sends_an_error_packet() {
1275        let mut rx = Kermit::receive(temp_dir("cancel"));
1276        let mut out = Vec::new();
1277        rx.cancel(&mut out);
1278        assert_eq!(out[0], SOH);
1279        assert_eq!(out[3], b'E');
1280        assert_eq!(rx.c.outcome, Some(Outcome::Cancelled));
1281    }
1282}