1use 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;
34pub const MAX_LONG: usize = 4000;
36const TIMEOUT: Duration = Duration::from_secs(8);
38const MAX_RETRIES: u32 = 10;
39const START_TRIES: u32 = 15;
42
43pub const WINDOW: usize = 16;
45
46const 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
63fn 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
76pub 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
88pub 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#[derive(Clone, Debug)]
96struct Params {
97 maxl: usize,
99 maxlx: Option<usize>,
100 time: u8,
102 npad: u8,
103 padc: u8,
104 eol: u8,
105 qctl: u8,
106 qbin: u8,
108 chkt: u8,
109 rept: u8,
111 capas: u8,
112 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 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 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 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#[derive(Clone, Debug)]
198struct Link {
199 send_max: usize,
201 long: bool,
202 chkt: u8,
203 our_qctl: u8,
205 their_qctl: u8,
206 qbin: Option<u8>,
207 rept: Option<u8>,
208 attrs: bool,
209 window: usize,
211 eol: u8,
212 npad: u8,
213 padc: u8,
214}
215
216impl Link {
217 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 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 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 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 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
377fn is_prefix(c: u8) -> bool {
379 (33..=62).contains(&c) || (96..=126).contains(&c)
380}
381
382#[derive(Debug, PartialEq, Eq)]
384struct Packet {
385 seq: u8,
386 kind: u8,
387 data: Vec<u8>,
388}
389
390fn 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 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 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 Init,
441 File,
443 Data,
445 Header,
448 Attrs,
449 Eof,
450 Break,
451 Done,
452}
453
454struct Flight {
456 seq: u8,
457 packet: Vec<u8>,
458 acked: bool,
459 retries: u32,
460}
461
462fn 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
472pub struct Kermit {
474 role: Role,
475 state: State,
476 link: Link,
477 seq: u8,
478 last: Vec<u8>,
480 pending_data: Vec<u8>,
482 flight: VecDeque<Flight>,
484 early: HashMap<u8, Packet>,
486 acks: Vec<Vec<u8>>,
489 retries: u32,
490 last_heard: Instant,
491 pub(crate) c: Common,
492}
493
494impl Kermit {
495 pub fn receive(dir: PathBuf) -> Kermit {
498 Kermit::new(Role::Receive { dir, file: None, saved: Vec::new() }, Direction::Download)
499 }
500
501 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 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 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 fn windowed(&self) -> bool {
591 self.sending() && self.state == State::Data && self.link.window > 1
592 }
593
594 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 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 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 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 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 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 self.acks[p.seq as usize].clear();
728 if ahead > 0 {
729 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 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 (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 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 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 if reply.first() == Some(&b'N') {
881 self.skip_file(out);
882 } else {
883 self.start_data(out);
884 }
885 }
886 State::Data => {
887 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 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 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 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 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 (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 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 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 fn next_chunk(&mut self, out: &mut Vec<u8>) -> Option<Vec<u8>> {
1038 let room = self.link.room();
1039 loop {
1040 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 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 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
1093fn 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 fn run(tx: &mut Kermit, rx: &mut Kermit, damage: Option<usize>) {
1125 run_lossy(tx, rx, damage, None);
1126 }
1127
1128 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 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 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 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 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 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 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 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}