Forks lexxvir/zmodem

This is commit acdc761522679de2c52e0a7fa2640d48f7bd0ab5 on GitHub.

The project is not actively maintained and requires substantial change
to port to no_std and kata_io traits, making copying more appealing than
tracking upstream.

This change adds the unmodified files as a local diffbase.

Change-Id: I8846f4842d75d01f07b2857998819115a9c449ba
GitOrigin-RevId: 5cb637810ddfa5a35d73d680c16c36855f1b70ef
This commit is contained in:
Matt Harvey
2021-09-14 18:44:49 -07:00
committed by Sam Leffler
parent c0df9a3b95
commit 6e9addf6bf
14 changed files with 1367 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
target/
**/*.rs.bk
Cargo.lock
*.sw[po]

View File

@@ -0,0 +1,17 @@
[package]
authors = ["aarbuzov"]
name = "zmodem"
license = "MIT OR Apache-2.0"
version = "0.1.0"
[dependencies]
clap = "2.21.1"
crc = "1.4.0"
env_logger = "0.4.2"
hex = "0.2.0"
hexdump = "0.1.0"
log = "0.3.7"
[dev-dependencies]
lazy_static = "1"
rand = "0.3.15"

View File

@@ -0,0 +1,28 @@
extern crate zmodem;
extern crate log;
extern crate env_logger;
extern crate clap;
mod stdinout;
use std::fs::File;
use std::path::Path;
use clap::{Arg, App};
fn main() {
env_logger::init().unwrap();
let matches = App::new("Pure Rust implementation of rz utility")
.arg(Arg::with_name("file")
.required(false)
.index(1))
.get_matches();
let fileopt = matches.value_of("file").unwrap_or("rz-out");
let filename = Path::new(fileopt).file_name().unwrap().clone();
let file = File::create(filename).expect(&format!("Cannot create file {:?}:", filename));
let inout = stdinout::CombinedStdInOut::new();
zmodem::recv::recv(inout, file).unwrap();
}

View File

@@ -0,0 +1,33 @@
use std::io::*;
pub struct CombinedStdInOut {
stdin: Stdin,
stdout: Stdout,
}
impl CombinedStdInOut {
pub fn new() -> CombinedStdInOut {
CombinedStdInOut {
stdin: stdin(),
stdout: stdout(),
}
}
}
impl Read for CombinedStdInOut {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.stdin.read(buf)
}
}
impl Write for CombinedStdInOut {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let r = self.stdout.write(buf)?;
self.stdout.flush()?;
Ok(r)
}
fn flush(&mut self) -> Result<()> {
self.stdout.flush()
}
}

View File

@@ -0,0 +1,31 @@
extern crate zmodem;
extern crate log;
extern crate env_logger;
extern crate clap;
mod stdinout;
use std::fs::File;
use std::path::Path;
use clap::{Arg, App};
fn main() {
env_logger::init().unwrap();
let matches = App::new("Pure Rust implementation of sz utility")
.arg(Arg::with_name("file")
.required(true)
.index(1))
.get_matches();
let fileopt = matches.value_of("file").unwrap();
let mut file = File::open(fileopt).unwrap();
let filename = Path::new(fileopt).file_name().unwrap().clone();
let size = file.metadata().map(|x| x.len() as u32).ok();
let inout = stdinout::CombinedStdInOut::new();
zmodem::send::send(inout, &mut file, filename.to_str().unwrap(), size).unwrap();
}

View File

@@ -0,0 +1,39 @@
pub const ZPAD: u8 = b'*';
pub const ZLDE: u8 = 0x18;
pub const ZLDEE: u8 = 0x58;
pub const ZBIN: u8 = b'A'; // 0x41
pub const ZHEX: u8 = b'B'; // 0x42
pub const ZBIN32: u8 = b'C'; // 0x43
pub const ESC_FF: u8 = b'm';
pub const ESC_7F: u8 = b'l';
/// Frame types
pub const ZRQINIT: u8 = 0; /* Request receive init */
pub const ZRINIT: u8 = 1; /* Receive init */
pub const ZSINIT: u8 = 2; /* Send init sequence (optional) */
pub const ZACK: u8 = 3; /* ACK to above */
pub const ZFILE: u8 = 4; /* File name from sender */
pub const ZSKIP: u8 = 5; /* To sender: skip this file */
pub const ZNAK: u8 = 6; /* Last packet was garbled */
pub const ZABORT: u8 = 7; /* Abort batch transfers */
pub const ZFIN: u8 = 8; /* Finish session */
pub const ZRPOS: u8 = 9; /* Resume data trans at this position */
pub const ZDATA: u8 = 10; /* Data packet(s) follow */
pub const ZEOF: u8 = 11; /* End of file */
pub const ZFERR: u8 = 12; /* Fatal Read or Write error Detected */
pub const ZCRC: u8 = 13; /* Request for file CRC and response */
pub const ZCHALLENGE: u8 = 14; /* Receiver's Challenge */
pub const ZCOMPL: u8 = 15; /* Request is complete */
pub const ZCAN: u8 = 16; /* Other end canned session with CAN*5 */
pub const ZFREECNT: u8 = 17; /* Request for free bytes on filesystem */
pub const ZCOMMAND: u8 = 18; /* Command from sending program */
pub const ZSTDERR: u8 = 19; /* Output to standard error, data follows */
/* ZDLE sequences */
pub const ZCRCE: u8 = b'h'; /* CRC next, frame ends, header packet follows */
pub const ZCRCG: u8 = b'i'; /* CRC next, frame continues nonstop */
pub const ZCRCQ: u8 = b'j'; /* CRC next, frame continues, ZACK expected */
pub const ZCRCW: u8 = b'k'; /* CRC next, ZACK expected, end of frame */
pub const XON: u8 = 0x11;

View File

@@ -0,0 +1,88 @@
use crc32::{crc32};
use crc32::crc32::IEEE_TABLE;
use crc32::crc32::update;
/* crctab calculated by Mark G. Mendel, Network Systems Corporation */
static CRCTAB: [u16; 256] = [
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
];
/*
* updcrc macro derived from article Copyright (C) 1986 Stephen Satchell.
* NOTE: First srgument must be in range 0 to 255.
* Second argument is referenced twice.
*
* Programmers may incorporate any or all code into their programs,
* giving proper credit within the source. Publication of the
* source routines is permitted so long as proper credit is given
* to Stephen Satchell, Satchell Evaluations and Chuck Forsberg,
* Omen Technology.
*/
fn updcrc(cp: u8, crc: u16) -> u16 {
let idx = ((crc >> 8) & 255) as usize ;
CRCTAB[idx] ^ (crc << 8) ^ (cp as u16)
}
pub fn get_crc16(buf: &[u8], zcrc: Option<u8>) -> [u8; 2] {
let mut crc = 0;
for x in buf {
crc = updcrc(*x, crc);
}
if let Some(x) = zcrc {
crc = updcrc(x, crc);
}
crc = updcrc(0, updcrc(0, crc));
[
(crc >> 8) as u8,
(crc & 0xff) as u8,
]
}
pub fn get_crc32(buf: &[u8], zcrc: Option<u8>) -> [u8; 4] {
let mut crc = crc32::checksum_ieee(buf);
if let Some(x) = zcrc {
crc = update(crc, &IEEE_TABLE, &[x]);
}
[
(crc & 0xff) as u8,
(crc >> 8) as u8,
(crc >> 16) as u8,
(crc >> 24) as u8,
]
}

View File

@@ -0,0 +1,174 @@
use std::fmt;
use consts::*;
use crc;
use hex::*;
use proto;
#[derive(Debug, Eq, PartialEq)]
pub struct Frame {
header: u8,
ftype: u8,
flags: [u8; 4],
}
impl Frame {
pub fn new(header: u8, ftype: u8) -> Frame {
Frame {
header,
ftype,
flags: [0; 4],
}
}
pub fn flags<'b>(&'b mut self, flags: &[u8; 4]) -> &'b mut Frame {
self.flags = *flags;
self
}
pub fn count<'b>(&'b mut self, count: u32) -> &'b mut Frame {
self.flags = [
(count >> 0) as u8,
(count >> 8) as u8,
(count >> 16) as u8,
(count >> 24) as u8,
];
self
}
pub fn get_count(&self) -> u32 {
(self.flags[3] as u32) << 24
| (self.flags[2] as u32) << 16
| (self.flags[1] as u32) << 8
| (self.flags[0] as u32)
}
pub fn build(&self) -> Vec<u8> {
let mut out = Vec::new();
out.push(ZPAD);
if self.header == ZHEX {
out.push(ZPAD);
}
out.push(ZLDE);
out.push(self.header);
out.push(self.ftype);
out.extend_from_slice(&self.flags);
let mut crc = get_crc(self.header, &out);
out.append(&mut crc);
if self.header == ZHEX {
let hex = out.drain(4..).collect::<Vec<u8>>().to_hex();
out.extend_from_slice(&hex.as_bytes());
}
let tmp = out.drain(3..).collect::<Vec<_>>();
let mut tmp2 = Vec::new();
proto::escape_buf(&tmp, &mut tmp2);
out.extend_from_slice(&tmp2);
if self.header == ZHEX {
out.extend_from_slice(b"\r\n");
if self.ftype != ZACK && self.ftype != ZFIN {
out.push(XON);
}
}
out
}
pub fn get_frame_type(&self) -> u8 {
self.ftype
}
pub fn get_header(&self) -> u8 {
self.header
}
}
fn get_crc(header: u8, buf: &[u8]) -> Vec<u8> {
let offset = match header {
ZHEX => 4,
_ => 3,
};
match header {
ZBIN32 => crc::get_crc32(&buf[offset..], None).to_vec(),
_ => crc::get_crc16(&buf[offset..], None).to_vec(),
}
}
impl fmt::Display for Frame {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let hdr = match self.header {
ZHEX => "ZHEX",
ZBIN => "ZBIN",
ZBIN32 => "ZBIN32",
_ => "???",
};
let ft = match self.ftype {
ZRQINIT => "ZRQINIT",
ZRINIT => "ZRINIT",
ZSINIT => "ZSINIT",
ZACK => "ZACK",
ZFILE => "ZFILE",
ZSKIP => "ZSKIP",
ZNAK => "ZNAK",
ZABORT => "ZABORT",
ZFIN => "ZFIN",
ZRPOS => "ZRPOS",
ZDATA => "ZDATA",
ZEOF => "ZEOF",
ZFERR => "ZFERR",
ZCRC => "ZCRC",
ZCHALLENGE => "ZCHALLENGE",
ZCOMPL => "ZCOMPL",
ZCAN => "ZCAN",
ZFREECNT => "ZFREECNT",
ZCOMMAND => "ZCOMMAND",
ZSTDERR => "ZSTDERR",
_ => "???",
};
write!(f, "{}({})", hdr, ft)
}
}
#[test]
fn test_frame() {
assert_eq!(
Frame::new(ZBIN, 0).build(),
vec![ZPAD, ZLDE, ZBIN, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(
Frame::new(ZBIN32, 0).build(),
vec![ZPAD, ZLDE, ZBIN32, 0, 0, 0, 0, 0, 29, 247, 34, 198]);
assert_eq!(
Frame::new(ZBIN, 0)
.flags(&[1; 4])
.build(),
vec![ZPAD, ZLDE, ZBIN, 0, 1, 1, 1, 1, 98, 148]);
assert_eq!(
Frame::new(ZBIN, 0)
.flags(&[1; 4])
.build(),
vec![ZPAD, ZLDE, ZBIN, 0, 1, 1, 1, 1, 98, 148]);
assert_eq!(
Frame::new(ZHEX, 0)
.flags(&[1; 4])
.build(),
vec![ZPAD, ZPAD, ZLDE, ZHEX,
b'0', b'0',
b'0', b'1',
b'0', b'1',
b'0', b'1',
b'0', b'1',
54, 50, 57, 52,
b'\r', b'\n', XON]);
}

View File

@@ -0,0 +1,15 @@
#[macro_use]
extern crate log;
extern crate crc as crc32;
extern crate hex;
extern crate hexdump;
mod consts;
mod frame;
mod crc;
mod proto;
mod rwlog;
pub mod recv;
pub mod send;

View File

@@ -0,0 +1,430 @@
use std::io;
use hex::*;
use log::LogLevel::{Debug};
use consts::*;
use frame::*;
use crc::*;
/// Looking for sequence: ZPAD [ZPAD] ZLDE
/// Returns true if found otherwise false
pub fn find_zpad<R>(r: &mut R) -> io::Result<bool>
where R: io::Read {
// looking for first ZPAD
if read_byte(r)? != ZPAD {
return Ok(false);
}
// get next byte
let mut b = read_byte(r)?;
// skip second ZPAD
if b == ZPAD {
b = read_byte(r)?;
}
// expect ZLDE
if b != ZLDE {
return Ok(false);
}
Ok(true)
}
pub fn parse_header<'a, R>(mut r: R) -> io::Result<Option<Frame>>
where R: io::Read {
let header = read_byte(&mut r)?;
match header {
ZBIN32 | ZBIN | ZHEX => (),
_ => {
error!("unexpected header byte!");
return Ok(None)
},
};
let len = 1 + 4; // frame type + flags
let len = if header == ZBIN32 { 4 } else { 2 } + len;
let len = if header == ZHEX { len * 2 } else { len };
let mut v = vec![0; len];
read_exact_unescaped(r, &mut v)?;
if header == ZHEX {
v = match FromHex::from_hex(&v) {
Ok(x) => x,
_ => {
error!("from_hex error");
return Ok(None);
},
}
}
let crc1 = v[5..].to_vec();
let crc2 = match header {
ZBIN32 => get_crc32(&v[..5], None).to_vec(),
_ => get_crc16(&v[..5], None).to_vec(),
};
if crc1 != crc2 {
error!("crc mismatch: {:?} != {:?}", crc1, crc2);
return Ok(None);
}
let mut frame = Frame::new(header, v[0]);
frame.flags(&[v[1], v[2], v[3], v[4]]);
if log_enabled!(Debug) {
debug!("Got frame: {}", frame);
match frame.get_frame_type() {
ZACK | ZRPOS => debug!(" offset = {}", frame.get_count()),
_ => (),
}
}
Ok(Some(frame))
}
/// Read out up to len bytes and remove escaped ones
fn read_exact_unescaped<R>(mut r: R, buf: &mut [u8]) -> io::Result<()>
where R: io::Read {
for x in buf {
*x = match read_byte(&mut r)? {
ZLDE => unescape(read_byte(&mut r)?),
y => y,
};
}
Ok(())
}
/// Receives sequence: <escaped data> ZLDE ZCRC* <CRC bytes>
/// Unescapes sequencies such as 'ZLDE <escaped byte>'
/// If Ok returns <unescaped data> in buf and ZCRC* byte as return value
pub fn recv_zlde_frame<R>(header: u8, r: &mut R, buf: &mut Vec<u8>) -> io::Result<Option<u8>>
where R: io::BufRead {
loop {
r.read_until(ZLDE, buf)?;
let b = read_byte(r)?;
if !is_escaped(b) {
*buf.last_mut().unwrap() = b; // replace ZLDE by ZCRC* byte
break;
}
*buf.last_mut().unwrap() = unescape(b);
}
let crc_len = if header == ZBIN32 { 4 } else { 2 };
let mut crc1 = vec![0; crc_len];
read_exact_unescaped(r, &mut crc1)?;
let crc2 = match header {
ZBIN32 => get_crc32(buf, None).to_vec(),
_ => get_crc16(buf, None).to_vec(),
};
if crc1 != crc2 {
error!("crc mismatch: {:?} != {:?}", crc1, crc2);
return Ok(None);
}
Ok(buf.pop()) // pop ZCRC* byte
}
pub fn recv_data<RW, OUT>(header: u8, count: &mut u32, rw: &mut RW, out: &mut OUT) -> io::Result<bool>
where RW: io::Write + io::BufRead,
OUT: io::Write {
let mut buf = Vec::new();
loop {
buf.clear();
let zcrc = match recv_zlde_frame(header, rw, &mut buf)? {
Some(x) => x,
None => return Ok(false),
};
out.write_all(&buf)?;
*count += buf.len() as u32;
match zcrc {
ZCRCW => {
debug!("ZCRCW: CRC next, ZACK expected, end of frame");
write_zack(rw, *count)?;
return Ok(true);
},
ZCRCE => {
debug!("ZCRCE: CRC next, frame ends, header packet follows");
return Ok(true);
},
ZCRCQ => {
debug!("ZCRCQ: CRC next, frame continues, ZACK expected");
write_zack(rw, *count)?
},
ZCRCG => {
debug!("CCRCG: CRC next, frame continues nonstop");
},
_ => {
panic!(format!("unexpected ZCRC byte: {:02X}", zcrc));
},
}
}
}
/// Converts escaped byte to unescaped one
fn unescape(escaped_byte: u8) -> u8 {
match escaped_byte {
ESC_FF => 0xFF,
ESC_7F => 0x7F,
x => if x & 0x60 != 0 { x ^ 0x40 } else { x },
}
}
fn is_escaped(byte: u8) -> bool {
match byte {
ZCRCE | ZCRCG | ZCRCQ | ZCRCW => false,
_ => true,
}
}
/// Reads out one byte
fn read_byte<R>(r: &mut R) -> io::Result<u8>
where R: io::Read {
let mut b = [0; 1];
r.read_exact(&mut b).map(|_| b[0])
}
/// Writes ZRINIT frame
pub fn write_zrinit<W>(w: &mut W) -> io::Result<()>
where W: io::Write {
debug!("write ZRINIT");
w.write_all(&Frame::new(ZHEX, ZRINIT).flags(&[0, 0, 0, 0x23]).build())
}
/// Writes ZRQINIT frame
pub fn write_zrqinit<W>(w: &mut W) -> io::Result<()>
where W: io::Write {
debug!("write ZRQINIT");
w.write_all(&Frame::new(ZHEX, ZRQINIT).build())
}
/// Writes ZFILE frame
pub fn write_zfile<W>(w: &mut W, filename: &str, filesize: Option<u32>) -> io::Result<()>
where W: io::Write {
debug!("write ZFILE");
w.write_all(&Frame::new(ZBIN32, ZFILE).build())?;
let mut zfile_data = format!("{}\0", filename);
if let Some(size) = filesize {
zfile_data += &format!(" {}", size);
}
zfile_data += &format!("\0");
debug!("ZFILE supplied data: {}", zfile_data);
write_zlde_data(w, ZCRCW, zfile_data.as_bytes())
}
/// Writes ZACK frame
pub fn write_zack<W>(w: &mut W, count: u32) -> io::Result<()>
where W: io::Write {
debug!("write ZACK bytes={}", count);
w.write_all(&Frame::new(ZHEX, ZACK).count(count).build())
}
/// Writes ZFIN frame
pub fn write_zfin<W>(w: &mut W) -> io::Result<()>
where W: io::Write {
debug!("write ZFIN");
w.write_all(&Frame::new(ZHEX, ZFIN).build())
}
/// Writes ZNAK frame
pub fn write_znak<W>(w: &mut W) -> io::Result<()>
where W: io::Write {
debug!("write ZNAK");
w.write_all(&Frame::new(ZHEX, ZNAK).build())
}
/// Writes ZRPOS frame
pub fn write_zrpos<W>(w: &mut W, count: u32) -> io::Result<()>
where W: io::Write {
debug!("write ZRPOS bytes={}", count);
w.write_all(&Frame::new(ZHEX, ZRPOS).count(count).build())
}
/// Writes ZDATA frame
pub fn write_zdata<W>(w: &mut W, offset: u32) -> io::Result<()>
where W: io::Write {
debug!("write ZDATA offset={}", offset);
w.write_all(&Frame::new(ZBIN32, ZDATA).count(offset).build())
}
/// Writes ZEOF frame
pub fn write_zeof<W>(w: &mut W, offset: u32) -> io::Result<()>
where W: io::Write {
debug!("write ZEOF offset={}", offset);
w.write_all(&Frame::new(ZBIN32, ZEOF).count(offset).build())
}
pub fn write_zlde_data<W>(w: &mut W, zcrc_byte: u8, data: &[u8]) -> io::Result<()>
where W: io::Write {
if log_enabled!(Debug) {
debug!(" ZCRC{} subpacket, size = {}",
match zcrc_byte {
ZCRCE => "E",
ZCRCG => "G",
ZCRCQ => "Q",
ZCRCW => "W",
_ => "?",
},
data.len());
}
let crc = get_crc32(data, Some(zcrc_byte));
write_escape(w, data)?;
w.write(&[ZLDE, zcrc_byte])?;
write_escape(w, &crc)?;
Ok(())
}
fn write_escape<W>(w: &mut W, data: &[u8]) -> io::Result<()>
where W: io::Write {
//let mut w = io::BufWriter::new(w);
let mut esc_data = Vec::with_capacity(data.len() + data.len()/10);
escape_buf(data, &mut esc_data);
w.write_all(&esc_data)
}
/// Writes "Over & Out"
pub fn write_over_and_out<W>(w: &mut W) -> io::Result<()>
where W: io::Write
{
w.write_all("OO".as_bytes())
}
pub fn escape_buf(src: &[u8], dst: &mut Vec<u8>) {
for x in src {
match *x {
0xFF => dst.extend_from_slice(&[ZLDE, ESC_FF]),
0x7F => dst.extend_from_slice(&[ZLDE, ESC_7F]),
0x10 | 0x90 | 0x11 | 0x91 | 0x13 | 0x93
=> dst.extend_from_slice(&[ZLDE, x ^ 0x40]),
ZLDE => dst.extend_from_slice(&[ZLDE, ZLDEE]),
x => dst.push(x),
};
}
}
mod tests {
#![allow(unused_imports)]
use consts::*;
use frame::*;
use super::*;
#[test]
fn test_find_zpad() {
let v = vec![ZPAD, ZLDE];
assert!(find_zpad(&mut v.as_slice()).unwrap());
let v = vec![ZPAD, ZPAD, ZLDE];
assert!(find_zpad(&mut v.as_slice()).unwrap());
let v = vec![ZLDE];
assert!(!find_zpad(&mut v.as_slice()).unwrap());
let v = vec![];
assert!(find_zpad(&mut v.as_slice()).is_err());
let v = vec![0; 100];
assert!(!find_zpad(&mut v.as_slice()).unwrap());
}
#[test]
fn test_read_exact_unescaped() {
let i = [0; 32];
let mut o = [0; 32];
read_exact_unescaped(&i[..], &mut o).unwrap();
assert_eq!(i, o);
let i = [ZLDE, b'm', ZLDE, b'l', ZLDE, 0x6f];
let mut o = [0; 3];
read_exact_unescaped(&i[..], &mut o).unwrap();
assert_eq!(o, [0xff, 0x7f, 0x2f]);
let i = [ZLDE, b'm', 0, 2, ZLDE, b'l'];
let mut o = [0; 4];
read_exact_unescaped(&i[..], &mut o).unwrap();
assert_eq!(o, [0xff, 0, 2, 0x7f]);
}
#[test]
fn test_parse_header() {
let i = [ZHEX, b'0', b'1', b'0', b'1', b'0', b'2', b'0', b'3', b'0', b'4', b'a', b'7', b'5', b'2'];
assert_eq!(
&mut parse_header(&i[..]).unwrap().unwrap(),
Frame::new(ZHEX, 1).flags(&[0x1, 0x2, 0x3, 0x4]));
let frame = 1;
let i = [ZBIN, frame, 0xa, 0xb, 0xc, 0xd, 0xa6, 0xcb];
assert_eq!(
&mut parse_header(&i[..]).unwrap().unwrap(),
Frame::new(ZBIN, frame).flags(&[0xa, 0xb, 0xc, 0xd]));
let frame = 1;
let i = [ZBIN32, frame, 0xa, 0xb, 0xc, 0xd, 0x99, 0xe2, 0xae, 0x4a];
assert_eq!(
&mut parse_header(&i[..]).unwrap().unwrap(),
Frame::new(ZBIN32, frame).flags(&[0xa, 0xb, 0xc, 0xd]));
let frame = 1;
let i = [ZBIN, frame, 0xa, ZLDE, b'l', 0xd, ZLDE, b'm', 0x5e, 0x6f];
assert_eq!(
&mut parse_header(&i[..]).unwrap().unwrap(),
Frame::new(ZBIN, frame).flags(&[0xa, 0x7f, 0xd, 0xff]));
let frame = 1;
let i = [0xaa, frame, 0xa, 0xb, 0xc, 0xd, 0xf, 0xf];
assert_eq!(parse_header(&i[..]).unwrap(), None);
}
#[test]
fn test_recv_zlde_frame() {
let i = vec![ZLDE, ZCRCE, 237, 174];
let mut v = vec![];
assert_eq!(recv_zlde_frame(ZBIN, &mut i.as_slice(), &mut v).unwrap(), Some(ZCRCE));
assert_eq!(&v[..], []);
let i = vec![ZLDE, 0x00, ZLDE, ZCRCW, 221, 205];
let mut v = vec![];
assert_eq!(recv_zlde_frame(ZBIN, &mut i.as_slice(), &mut v).unwrap(), Some(ZCRCW));
assert_eq!(&v[..], [0x00]);
let i = vec![0, 1, 2, 3, 4, ZLDE, 0x60, ZLDE, 0x60, ZLDE, ZCRCQ, 85, 114, 241, 70];
let mut v = vec![];
assert_eq!(recv_zlde_frame(ZBIN32, &mut i.as_slice(), &mut v).unwrap(), Some(ZCRCQ));
assert_eq!(&v[..], [0, 1, 2, 3, 4, 0x20, 0x20]);
}
}

View File

@@ -0,0 +1,135 @@
use std::{thread, time};
use std::io::{Read, Write, Result};
use std::str::from_utf8;
use consts::*;
use proto::*;
use rwlog;
use frame::*;
#[derive(Debug, PartialEq)]
enum State {
/// Sending ZRINIT
SendingZRINIT,
/// Processing ZFILE supplementary data
ProcessingZFILE,
/// Receiving file's content
ReceivingData,
/// Checking length of received data
CheckingData,
/// All works done, exiting
Done,
}
impl State {
fn new() -> State {
State::SendingZRINIT
}
fn next(self, frame: &Frame) -> State {
match (self, frame.get_frame_type()) {
(State::SendingZRINIT, ZFILE) => State::ProcessingZFILE,
(State::SendingZRINIT, _) => State::SendingZRINIT,
(State::ProcessingZFILE, ZDATA) => State::ReceivingData,
(State::ProcessingZFILE, _) => State::ProcessingZFILE,
(State::ReceivingData, ZDATA) => State::ReceivingData,
(State::ReceivingData, ZEOF) => State::CheckingData,
(State::CheckingData, ZDATA) => State::ReceivingData,
(State::CheckingData, ZFIN) => State::Done,
(s, _) => {
error!("Unexpected (state, frame) combination: {:#?} {}", s, frame);
s // don't change current state
},
}
}
}
/// Receives data by Z-Modem protocol
pub fn recv<RW, W>(rw: RW, mut w: W) -> Result<usize>
where RW: Read + Write,
W: Write
{
let mut rw_log = rwlog::ReadWriteLog::new(rw);
let mut count = 0;
let mut state = State::new();
write_zrinit(&mut rw_log)?;
while state != State::Done {
if !find_zpad(&mut rw_log)? {
continue;
}
let frame = match parse_header(&mut rw_log)? {
Some(x) => x,
None => { recv_error(&mut rw_log, &state, count)?; continue },
};
state = state.next(&frame);
debug!("State: {:?}", state);
// do things according new state
match state {
State::SendingZRINIT => {
write_zrinit(&mut rw_log)?;
},
State::ProcessingZFILE => {
let mut buf = Vec::new();
if recv_zlde_frame(frame.get_header(), &mut rw_log, &mut buf)?.is_none() {
write_znak(&mut rw_log)?;
}
else {
write_zrpos(&mut rw_log, count)?;
// TODO: process supplied data
if let Ok(s) = from_utf8(&buf) {
debug!(target: "proto", "ZFILE supplied data: {}", s);
}
}
},
State::ReceivingData => {
if frame.get_count() != count ||
!recv_data(frame.get_header(), &mut count, &mut rw_log, &mut w)? {
write_zrpos(&mut rw_log, count)?;
}
},
State::CheckingData => {
if frame.get_count() != count {
error!("ZEOF offset mismatch: frame({}) != recv({})", frame.get_count(), count);
// receiver ignores the ZEOF because a new zdata is coming
}
else {
write_zrinit(&mut rw_log)?;
}
},
State::Done => {
write_zfin(&mut rw_log)?;
thread::sleep(time::Duration::from_millis(10)); // sleep a bit
},
}
}
Ok(count as usize)
}
fn recv_error<W>(w: &mut W, state: &State, count: u32) -> Result<()>
where W: Write
{
// TODO: flush input
match *state {
State::ReceivingData => write_zrpos(w, count),
_ => write_znak(w),
}
}

View File

@@ -0,0 +1,67 @@
use std::io::*;
use log::LogLevel::*;
use hexdump::*;
pub struct ReadWriteLog<RW> {
inner: BufReader<RW>,
}
impl<RW: Read + Write> ReadWriteLog<RW> {
pub fn new(rw: RW) -> ReadWriteLog<RW> {
ReadWriteLog {
inner: BufReader::new(rw),
}
}
}
impl<R: Read> Read for ReadWriteLog<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let r = self.inner.read(buf)?;
if log_enabled!(Debug) {
debug!("In:");
for x in hexdump_iter(&buf[..r]) {
debug!("{}", x);
}
}
Ok(r)
}
}
impl<R: Read> BufRead for ReadWriteLog<R> {
fn fill_buf(&mut self) -> Result<&[u8]> {
let r = self.inner.fill_buf()?;
if log_enabled!(Debug) {
debug!("In:");
for x in hexdump_iter(r) {
debug!("{}", x);
}
}
Ok(r)
}
fn consume(&mut self, amt: usize) {
self.inner.consume(amt)
}
}
impl<RW: Write + Read> Write for ReadWriteLog<RW> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
if log_enabled!(Debug) {
debug!("Out:");
for x in hexdump_iter(buf) {
debug!("{}", x);
}
}
self.inner.get_mut().write(buf)
}
fn flush(&mut self) -> Result<()> {
self.inner.get_mut().flush()
}
}

View File

@@ -0,0 +1,146 @@
use std::io::{Read, Write, Result, Seek, SeekFrom};
use consts::*;
use proto::*;
use rwlog;
use frame::*;
const SUBPACKET_SIZE: usize = 1024 * 8;
const SUBPACKET_PER_ACK: usize = 10;
#[derive(Debug, PartialEq)]
enum State {
/// Waiting ZRINIT invite (do nothing)
WaitingInit,
/// Sending ZRQINIT
SendingZRQINIT,
/// Sending ZFILE frame
SendingZFILE,
/// Do nothing, just waiting for ZPOS
WaitingZPOS,
/// Sending ZDATA & subpackets
SendingData,
/// Sending ZFIN
SendingZFIN,
/// All works done, exiting
Done,
}
impl State {
fn new() -> State {
State::WaitingInit
}
fn next(self, frame: &Frame) -> State {
match (self, frame.get_frame_type()) {
(State::WaitingInit, ZRINIT) => State::SendingZFILE,
(State::WaitingInit, _) => State::SendingZRQINIT,
(State::SendingZRQINIT, ZRINIT) => State::SendingZFILE,
(State::SendingZFILE, ZRPOS) => State::SendingData,
(State::SendingZFILE, ZRINIT) => State::WaitingZPOS,
(State::WaitingZPOS, ZRPOS) => State::SendingData,
(State::SendingData, ZACK) => State::SendingData,
(State::SendingData, ZRPOS) => State::SendingData,
(State::SendingData, ZRINIT) => State::SendingZFIN,
(State::SendingZFIN, ZFIN) => State::Done,
(s, _) => {
error!("Unexpected (state, frame) combination: {:#?} {}", s, frame);
s // don't change current state
},
}
}
}
pub fn send<RW, R>(rw: RW, r: &mut R, filename: &str, filesize: Option<u32>) -> Result<()>
where RW: Read + Write,
R: Read + Seek
{
let mut rw_log = rwlog::ReadWriteLog::new(rw);
let mut data = [0; SUBPACKET_SIZE];
let mut offset: u32;
write_zrqinit(&mut rw_log)?;
let mut state = State::new();
while state != State::Done {
rw_log.flush()?;
if !find_zpad(&mut rw_log)? {
continue;
}
let frame = match parse_header(&mut rw_log)? {
Some(x) => x,
None => { write_znak(&mut rw_log)?; continue },
};
state = state.next(&frame);
debug!("State: {:?}", state);
// do things according new state
match state {
State::SendingZRQINIT => {
write_zrqinit(&mut rw_log)?;
},
State::SendingZFILE => {
write_zfile(&mut rw_log, filename, filesize)?;
},
State::SendingData => {
offset = frame.get_count();
r.seek(SeekFrom::Start(offset as u64))?;
let num = r.read(&mut data)?;
if num == 0 {
write_zeof(&mut rw_log, offset)?;
}
else {
// ZBIN32|ZDATA
// ZCRCG - best perf
// ZCRCQ - mid perf
// ZCRCW - worst perf
// ZCRCE - send at end
write_zdata(&mut rw_log, offset)?;
let mut i = 0;
loop {
i += 1;
write_zlde_data(&mut rw_log, ZCRCG, &data[..num])?;
offset += num as u32;
let num = r.read(&mut data)?;
if num < data.len() || i >= SUBPACKET_PER_ACK {
write_zlde_data(&mut rw_log, ZCRCW, &data[..num])?;
break;
}
}
}
},
State::SendingZFIN => {
write_zfin(&mut rw_log)?;
},
State::Done => {
write_over_and_out(&mut rw_log)?;
},
_ => (),
}
}
Ok(())
}

View File

@@ -0,0 +1,160 @@
extern crate zmodem;
extern crate log;
extern crate env_logger;
#[macro_use] extern crate lazy_static;
extern crate rand;
use std::process::*;
use std::fs::{File, remove_file, OpenOptions};
use std::io::*;
use std::time::*;
use std::thread::{sleep, spawn};
use std::result;
struct InOut<R: Read, W: Write> {
r: R,
w: W,
}
impl<R: Read, W: Write> InOut<R, W> {
pub fn new(r: R, w: W) -> InOut<R, W> {
InOut { r, w }
}
}
impl<R: Read, W: Write> Read for InOut<R, W> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.r.read(buf)
}
}
impl<R: Read, W: Write> Write for InOut<R, W> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.w.write(buf)
}
fn flush(&mut self) -> Result<()> {
self.w.flush()
}
}
lazy_static! {
static ref LOG_INIT: result::Result<(), log::SetLoggerError> = env_logger::init();
static ref RND_VALUES: Vec<u8> = {
use rand::Rng;
let mut rng = rand::thread_rng();
let mut buf = vec![0; 1024 * 1024 * 11];
rng.fill_bytes(&mut buf);
buf
};
}
#[test]
#[cfg(unix)]
fn recv_from_sz() {
let _ = LOG_INIT.is_ok();
let mut f = File::create("recv_from_sz").unwrap();
f.write_all(&RND_VALUES).unwrap();
let sz = Command::new("sz")
.arg("recv_from_sz")
.stdout(Stdio::piped())
.stdin(Stdio::piped())
.spawn()
.expect("sz failed to run");
let child_stdin = sz.stdin.unwrap();
let child_stdout = sz.stdout.unwrap();
let mut inout = InOut::new(child_stdout, child_stdin);
let mut c = Cursor::new(Vec::new());
zmodem::recv::recv(&mut inout, &mut c).unwrap();
sleep(Duration::from_millis(300));
remove_file("recv_from_sz").unwrap();
assert_eq!(RND_VALUES.clone(), c.into_inner());
}
#[test]
#[cfg(unix)]
fn send_to_rz() {
let _ = LOG_INIT.is_ok();
let _ = remove_file("send_to_rz");
let sz = Command::new("rz")
.stdout(Stdio::piped())
.stdin(Stdio::piped())
.spawn()
.expect("rz failed to run");
let child_stdin = sz.stdin.unwrap();
let child_stdout = sz.stdout.unwrap();
let mut inout = InOut::new(child_stdout, child_stdin);
let len = RND_VALUES.len() as u32;
let copy = RND_VALUES.clone();
let mut cur = Cursor::new(&copy);
sleep(Duration::from_millis(300));
zmodem::send::send(&mut inout, &mut cur, "send_to_rz", Some(len)).unwrap();
sleep(Duration::from_millis(300));
let mut f = File::open("send_to_rz").expect("open 'send_to_rz'");
let mut received = Vec::new();
f.read_to_end(&mut received).unwrap();
remove_file("send_to_rz").unwrap();
assert!(copy == received);
}
#[test]
#[cfg(unix)]
fn lib_send_recv() {
let _ = LOG_INIT;
let _ = remove_file("test-fifo1");
let _ = remove_file("test-fifo2");
let _ = Command::new("mkfifo")
.arg("test-fifo1")
.spawn()
.expect("mkfifo failed to run")
.wait();
let _ = Command::new("mkfifo")
.arg("test-fifo2")
.spawn()
.expect("mkfifo failed to run")
.wait();
sleep(Duration::from_millis(300));
spawn(move || {
let outf = OpenOptions::new().write(true).open("test-fifo1").unwrap();
let inf = File::open("test-fifo2").unwrap();
let mut inout = InOut::new(inf, outf);
let origin = RND_VALUES.clone();
let mut c = Cursor::new(&origin);
zmodem::send::send(&mut inout, &mut c, "test", None).unwrap();
});
let mut c = Cursor::new(Vec::new());
let inf = File::open("test-fifo1").unwrap();
let outf = OpenOptions::new().write(true).open("test-fifo2").unwrap();
let mut inout = InOut::new(inf, outf);
zmodem::recv::recv(&mut inout, &mut c).unwrap();
let _ = remove_file("test-fifo1");
let _ = remove_file("test-fifo2");
assert_eq!(RND_VALUES.clone(), c.into_inner());
}