Skip to content

Commit

Permalink
modernize, format, raise max errors, add tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
orangecms committed Feb 23, 2023
1 parent 9dcd971 commit adf2f45
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 80 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ license = "MIT"
description = "An implementation of the XMODEM file-transfer protocol."

[dependencies]
log = "^0.3"
log = "^0.4.17"
crc16 = "^0.3"

[dev-dependencies]
Expand Down
187 changes: 108 additions & 79 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
extern crate log;
extern crate crc16;

use std::io::{self, Read, Write};
use std::convert::From;
use std::io::{self, Read, Write};

// TODO: Send CAN byte after too many errors
// TODO: Handle CAN bytes while sending
Expand Down Expand Up @@ -73,7 +73,7 @@ impl Xmodem {
/// Creates the XMODEM config with default parameters.
pub fn new() -> Self {
Xmodem {
max_errors: 16,
max_errors: 32,
pad_byte: 0x1a,
block_length: BlockLength::Standard,
checksum_mode: Checksum::Standard,
Expand All @@ -95,11 +95,11 @@ impl Xmodem {
self.errors = 0;

debug!("Starting XMODEM transfer");
try!(self.start_send(dev));
self.start_send(dev)?;
debug!("First byte received. Sending stream.");
try!(self.send_stream(dev, stream));
self.send_stream(dev, stream)?;
debug!("Sending EOT");
try!(self.finish_send(dev));
self.finish_send(dev)?;

Ok(())
}
Expand All @@ -116,135 +116,153 @@ impl Xmodem {
/// to set the timeout of the device before calling this method. Timeouts on receiving
/// bytes will be counted against `max_errors`, but timeouts on transmitting bytes
/// will be considered a fatal error.
pub fn recv<D: Read + Write, W: Write + ?Sized>(&mut self,
dev: &mut D,
outstream: &mut W,
checksum : Checksum) -> Result<()> {
pub fn recv<D: Read + Write, W: Write + ?Sized>(
&mut self,
dev: &mut D,
outstream: &mut W,
checksum: Checksum,
) -> Result<()> {
self.errors = 0;
self.checksum_mode = checksum;
let ncg = match self.checksum_mode {
Checksum::Standard => NAK,
Checksum::CRC16 => CRC };
Checksum::CRC16 => CRC,
};
debug!("Starting XMODEM receive");
try!(dev.write(&[ncg]));
// FIXME: handle written amount
let _ = dev.write(&[ncg])?;
debug!("NCG sent. Receiving stream.");
let mut packet_num : u8 = 1;
let mut packet_num: u8 = 1;
loop {
match try!(get_byte_timeout(dev)) {
bt @ Some(SOH) | bt @ Some(STX) => { // Handle next packet
match get_byte_timeout(dev)? {
bt @ Some(SOH) | bt @ Some(STX) => {
// Handle next packet
let data_size = match bt {
Some(SOH) => 128,
Some(STX) => 1024,
_ => 0, // Why does the compiler need this?
};
let mut data : Vec<u8> = Vec::new();
let mut data: Vec<u8> = Vec::new();
let packet_size = data_size
+ 2
+ match self.checksum_mode {
Checksum::Standard => 1,
Checksum::CRC16 => 2,
};
data.resize(packet_size,0);
data.resize(packet_size, 0);
match dev.read_exact(&mut data) {
Ok(_) => {
// Check packet number
let pnum = data[0];
let pnum_1c = data[1];
if (packet_num != pnum) || (255-pnum != pnum_1c) {
if (packet_num != pnum) || (255 - pnum != pnum_1c) {
// We've lost data; cancel this transmission
warn!("Cancelling transmission.");
try!(dev.write(&[CAN]));
try!(dev.write(&[CAN]));
return Err(Error::Canceled)
let n = dev.write(&[CAN])?;
debug!("CAN sent, {n} bytes");
let n = dev.write(&[CAN])?;
debug!("CAN sent, {n} bytes");
return Err(Error::Canceled);
}
// Check checksum
let check_ok = match self.checksum_mode {
Checksum::Standard => {
let recv_checksum = data[packet_size-1];
calc_checksum(&data[2..packet_size-1]) == recv_checksum },
let recv_checksum = data[packet_size - 1];
calc_checksum(&data[2..packet_size - 1]) == recv_checksum
}
Checksum::CRC16 => {
let recv_checksum =
((data[packet_size-2] as u16) << 8) +
(data[packet_size-1] as u16);
calc_crc(&data[2..packet_size-2]) == recv_checksum },
let recv_checksum = ((data[packet_size - 2] as u16) << 8)
+ (data[packet_size - 1] as u16);
calc_crc(&data[2..packet_size - 2]) == recv_checksum
}
};
// Response
if check_ok {
packet_num = packet_num.wrapping_add(1);
try!(dev.write(&[ACK]));
try!(outstream.write_all(&data[2..data_size+2]));
let n = dev.write(&[ACK])?;
debug!("ACK sent, {n} bytes");
outstream.write_all(&data[2..data_size + 2])?;
} else {
info!("Packet failed.");
try!(dev.write(&[NAK]));
let n = dev.write(&[NAK])?;
debug!("NAK sent, {n} bytes");
self.errors += 1;
}
},
}
Err(e) => {
error!("Error is {}",e);
error!("Error is {}", e);
// Timeout
self.errors += 1;
warn!("Timeout in packet! Resending NAK.");
try!(dev.write(&[NAK]));
},
let _ = dev.write(&[NAK])?;
}
}
},
Some(EOT) => { // End of file
try!(dev.write(&[ACK]));
break
},
}
Some(EOT) => {
// End of file
let _ = dev.write(&[ACK])?;
break;
}
Some(x) => {
warn!("Unrecognized symbol {:X}!",x);
},
warn!("Unrecognized symbol {:X}!", x);
}
None => {
self.errors += 1;
warn!("Timeout! Resending NAK.");
try!(dev.write(&[NAK]));
},
let _ = dev.write(&[NAK])?;
}
}
if self.errors >= self.max_errors {
error!("Exhausted max retries ({}) while waiting for ACK for EOT", self.max_errors);
error!(
"Exhausted max retries ({}) while waiting for ACK for EOT",
self.max_errors
);
return Err(Error::ExhaustedRetries);
}
}
Ok(())
}

fn start_send<D: Read + Write>(&mut self, dev: &mut D) -> Result<()> {
let mut cancels = 0u32;
loop {
match try!(get_byte_timeout(dev)) {
Some(c) => {
match c {
NAK => {
debug!("Standard checksum requested");
self.checksum_mode = Checksum::Standard;
return Ok(());
}
CRC => {
debug!("16-bit CRC requested");
self.checksum_mode = Checksum::CRC16;
return Ok(());
}
CAN => {
warn!("Cancel (CAN) byte received");
cancels += 1;
},
c => warn!("Unknown byte received at start of XMODEM transfer: {}", c),
debug!("Send...");
match get_byte_timeout(dev)? {
Some(c) => match c {
NAK => {
debug!("Standard checksum requested");
self.checksum_mode = Checksum::Standard;
return Ok(());
}
CRC => {
debug!("16-bit CRC requested");
self.checksum_mode = Checksum::CRC16;
return Ok(());
}
CAN => {
warn!("Cancel (CAN) byte received");
cancels += 1;
}
b => warn!("Unknown byte received at start of XMODEM transfer: {b:x}",),
},
None => warn!("Timed out waiting for start of XMODEM transfer."),
}

self.errors += 1;

if cancels >= 2 {
error!("Transmission canceled: received two cancel (CAN) bytes \
at start of XMODEM transfer");
error!(
"Transmission canceled: received two cancel (CAN) bytes \
at start of XMODEM transfer"
);
return Err(Error::Canceled);
}

if self.errors >= self.max_errors {
error!("Exhausted max retries ({}) at start of XMODEM transfer.", self.max_errors);
error!(
"Exhausted max retries ({}) at start of XMODEM transfer.",
self.max_errors
);
if let Err(err) = dev.write_all(&[CAN]) {
warn!("Error sending CAN byte: {}", err);
}
Expand All @@ -257,7 +275,7 @@ impl Xmodem {
let mut block_num = 0u32;
loop {
let mut buff = vec![self.pad_byte; self.block_length as usize + 3];
let n = try!(stream.read(&mut buff[3..]));
let n = stream.read(&mut buff[3..])?;
if n == 0 {
debug!("Reached EOF");
return Ok(());
Expand All @@ -275,7 +293,7 @@ impl Xmodem {
Checksum::Standard => {
let checksum = calc_checksum(&buff[3..]);
buff.push(checksum);
},
}
Checksum::CRC16 => {
let crc = calc_crc(&buff[3..]);
buff.push(((crc >> 8) & 0xFF) as u8);
Expand All @@ -284,51 +302,56 @@ impl Xmodem {
}

debug!("Sending block {}", block_num);
try!(dev.write_all(&buff));
dev.write_all(&buff)?;

match try!(get_byte_timeout(dev)) {
match get_byte_timeout(dev)? {
Some(c) => {
if c == ACK {
debug!("Received ACK for block {}", block_num);
continue
continue;
} else {
warn!("Expected ACK, got {}", c);
}
// TODO handle CAN bytes
},
}
None => warn!("Timeout waiting for ACK for block {}", block_num),
}

self.errors += 1;

if self.errors >= self.max_errors {
error!("Exhausted max retries ({}) while sending block {} in XMODEM transfer",
self.max_errors, block_num);
error!(
"Exhausted max retries ({}) while sending block {} in XMODEM transfer",
self.max_errors, block_num
);
return Err(Error::ExhaustedRetries);
}
}
}

fn finish_send<D: Read + Write>(&mut self, dev: &mut D) -> Result<()> {
loop {
try!(dev.write_all(&[EOT]));
dev.write_all(&[EOT])?;

match try!(get_byte_timeout(dev)) {
match get_byte_timeout(dev)? {
Some(c) => {
if c == ACK {
info!("XMODEM transmission successful");
return Ok(());
} else {
warn!("Expected ACK, got {}", c);
}
},
}
None => warn!("Timeout waiting for ACK for EOT"),
}

self.errors += 1;

if self.errors >= self.max_errors {
error!("Exhausted max retries ({}) while waiting for ACK for EOT", self.max_errors);
error!(
"Exhausted max retries ({}) while waiting for ACK for EOT",
self.max_errors
);
return Err(Error::ExhaustedRetries);
}
}
Expand All @@ -345,15 +368,21 @@ fn calc_crc(data: &[u8]) -> u16 {

fn get_byte<R: Read>(reader: &mut R) -> std::io::Result<u8> {
let mut buff = [0];
try!(reader.read_exact(&mut buff));
trace!("Read a byte...");
reader.read_exact(&mut buff)?;
Ok(buff[0])
}

/// Turns timeout errors into `Ok(None)`
fn get_byte_timeout<R: Read>(reader: &mut R) -> std::io::Result<Option<u8>> {
trace!("Get a byte...");
match get_byte(reader) {
Ok(c) => Ok(Some(c)),
Ok(c) => {
trace!("Got a byte.");
Ok(Some(c))
}
Err(err) => {
trace!("Got no byte.");
if err.kind() == io::ErrorKind::TimedOut {
Ok(None)
} else {
Expand Down

0 comments on commit adf2f45

Please sign in to comment.