diff --git a/apps/system/components/DebugConsole/zmodem/.gitignore b/apps/system/components/DebugConsole/zmodem/.gitignore new file mode 100644 index 0000000..ff915e9 --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/.gitignore @@ -0,0 +1,4 @@ +target/ +**/*.rs.bk +Cargo.lock +*.sw[po] diff --git a/apps/system/components/DebugConsole/zmodem/Cargo.toml b/apps/system/components/DebugConsole/zmodem/Cargo.toml new file mode 100644 index 0000000..d2261ec --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/Cargo.toml @@ -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" diff --git a/apps/system/components/DebugConsole/zmodem/src/bin/rzm.rs b/apps/system/components/DebugConsole/zmodem/src/bin/rzm.rs new file mode 100644 index 0000000..f952934 --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/bin/rzm.rs @@ -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(); +} diff --git a/apps/system/components/DebugConsole/zmodem/src/bin/stdinout/mod.rs b/apps/system/components/DebugConsole/zmodem/src/bin/stdinout/mod.rs new file mode 100644 index 0000000..129c18e --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/bin/stdinout/mod.rs @@ -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 { + self.stdin.read(buf) + } +} + +impl Write for CombinedStdInOut { + fn write(&mut self, buf: &[u8]) -> Result { + let r = self.stdout.write(buf)?; + self.stdout.flush()?; + Ok(r) + } + + fn flush(&mut self) -> Result<()> { + self.stdout.flush() + } +} diff --git a/apps/system/components/DebugConsole/zmodem/src/bin/szm.rs b/apps/system/components/DebugConsole/zmodem/src/bin/szm.rs new file mode 100644 index 0000000..f5b5d9d --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/bin/szm.rs @@ -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(); +} diff --git a/apps/system/components/DebugConsole/zmodem/src/consts.rs b/apps/system/components/DebugConsole/zmodem/src/consts.rs new file mode 100644 index 0000000..afeae0e --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/consts.rs @@ -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; diff --git a/apps/system/components/DebugConsole/zmodem/src/crc.rs b/apps/system/components/DebugConsole/zmodem/src/crc.rs new file mode 100644 index 0000000..b910693 --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/crc.rs @@ -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; 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; 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, + ] +} diff --git a/apps/system/components/DebugConsole/zmodem/src/frame.rs b/apps/system/components/DebugConsole/zmodem/src/frame.rs new file mode 100644 index 0000000..4936dce --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/frame.rs @@ -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 { + 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::>().to_hex(); + out.extend_from_slice(&hex.as_bytes()); + } + + let tmp = out.drain(3..).collect::>(); + 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 { + 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]); +} diff --git a/apps/system/components/DebugConsole/zmodem/src/lib.rs b/apps/system/components/DebugConsole/zmodem/src/lib.rs new file mode 100644 index 0000000..9e4c864 --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/lib.rs @@ -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; diff --git a/apps/system/components/DebugConsole/zmodem/src/proto.rs b/apps/system/components/DebugConsole/zmodem/src/proto.rs new file mode 100644 index 0000000..8c6bc07 --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/proto.rs @@ -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: &mut R) -> io::Result + 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> + 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(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: ZLDE ZCRC* +/// Unescapes sequencies such as 'ZLDE ' +/// If Ok returns in buf and ZCRC* byte as return value +pub fn recv_zlde_frame(header: u8, r: &mut R, buf: &mut Vec) -> io::Result> + 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(header: u8, count: &mut u32, rw: &mut RW, out: &mut OUT) -> io::Result + 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: &mut R) -> io::Result + 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: &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: &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: &mut W, filename: &str, filesize: Option) -> 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: &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: &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: &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: &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: &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: &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: &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: &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: &mut W) -> io::Result<()> + where W: io::Write +{ + w.write_all("OO".as_bytes()) +} + + +pub fn escape_buf(src: &[u8], dst: &mut Vec) { + 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]); + } +} diff --git a/apps/system/components/DebugConsole/zmodem/src/recv.rs b/apps/system/components/DebugConsole/zmodem/src/recv.rs new file mode 100644 index 0000000..0538752 --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/recv.rs @@ -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: RW, mut w: W) -> Result + 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: &mut W, state: &State, count: u32) -> Result<()> + where W: Write +{ + // TODO: flush input + + match *state { + State::ReceivingData => write_zrpos(w, count), + _ => write_znak(w), + } +} + diff --git a/apps/system/components/DebugConsole/zmodem/src/rwlog.rs b/apps/system/components/DebugConsole/zmodem/src/rwlog.rs new file mode 100644 index 0000000..c6629b6 --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/rwlog.rs @@ -0,0 +1,67 @@ +use std::io::*; +use log::LogLevel::*; +use hexdump::*; + +pub struct ReadWriteLog { + inner: BufReader, +} + +impl ReadWriteLog { + pub fn new(rw: RW) -> ReadWriteLog { + ReadWriteLog { + inner: BufReader::new(rw), + } + } +} + +impl Read for ReadWriteLog { + fn read(&mut self, buf: &mut [u8]) -> Result { + let r = self.inner.read(buf)?; + + if log_enabled!(Debug) { + debug!("In:"); + for x in hexdump_iter(&buf[..r]) { + debug!("{}", x); + } + } + + Ok(r) + } +} + +impl BufRead for ReadWriteLog { + 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 Write for ReadWriteLog { + fn write(&mut self, buf: &[u8]) -> Result { + 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() + } +} + diff --git a/apps/system/components/DebugConsole/zmodem/src/send.rs b/apps/system/components/DebugConsole/zmodem/src/send.rs new file mode 100644 index 0000000..6a0c752 --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/src/send.rs @@ -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: RW, r: &mut R, filename: &str, filesize: Option) -> 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(()) +} + diff --git a/apps/system/components/DebugConsole/zmodem/tests/lib.rs b/apps/system/components/DebugConsole/zmodem/tests/lib.rs new file mode 100644 index 0000000..25e5871 --- /dev/null +++ b/apps/system/components/DebugConsole/zmodem/tests/lib.rs @@ -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: R, + w: W, +} + +impl InOut { + pub fn new(r: R, w: W) -> InOut { + InOut { r, w } + } +} + +impl Read for InOut { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.r.read(buf) + } +} + +impl Write for InOut { + fn write(&mut self, buf: &[u8]) -> Result { + 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 = { + 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(©); + + 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()); +}