great implemented transmutable (with u8
) enum
for the frame type of the #ZMODEM transfer protocol header: https://github.com/jarkkojs/zmodem2/commit/c76316c2ecae097be03506e8ce19d61287e6468a
I can use this as a reference model for refactoring rest of the code base.
Next, I’ll replace encoding: u8
with a similar enum Encoding
.
Interestingly enough for CRC16 byte array is construct reverse, i.e. big-endian so the final refurnished functions that pass the tests are:
use crc32::{Crc, CRC_16_XMODEM, CRC_32_ISO_HDLC};
const CRC16: Crc<u16> = Crc::<u16>::new(&CRC_16_XMODEM);
const CRC32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
pub fn get_crc16(buf: &[u8], maybe_zcrc: Option<u8>) -> [u8; 2] {
let mut digest = CRC16.digest();
digest.update(buf);
if let Some(zcrc) = maybe_zcrc {
digest.update(&[zcrc]);
}
digest.finalize().to_be_bytes()
// [(crc >> 8) as u8, (crc & 0xff) as u8]
}
pub fn get_crc32(buf: &[u8], maybe_zcrc: Option<u8>) -> [u8; 4] {
let mut digest = CRC32.digest();
digest.update(buf);
if let Some(zcrc) = maybe_zcrc {
digest.update(&[zcrc]);
}
// Assuming little-endian byte order, given that ZMODEM used to work on
// VAX, which was a little-endian computer architecture:
digest.finalize().to_le_bytes()
}
The manual conversion matches this so I guess this is right. No idea how it ended up like this in the history. Hooray, now I an finally open-code these convoluting functions by exporting CRC16
and CRC32
and using them directly in the call sites, mostly by just calling .checksum()
(there was only one call site where you really need to use .update()
and .finalize()
.
For 16-bit it was heck a lot of easier to pick the right one. There was CRC_16_XMODEM
, and yes it did work.
As a bug fix: https://github.com/jarkkojs/zmodem2/commit/ba5c5bbb7d521fc6df6177d1dec2825ea137da14
This abandoned looking zmodem
crate is fully working and has als lrzsz
based test suite, so kudos to the author for making this initial version, even if it since has been forgotten. Working code is always better than clean code…
So my crate is called zmodem2
and I’m purely focusing right now to make it work best possible way for my serial tool but I might want to go “beyond zmodem” later on. Since there’s not over-crowded supply of zmodem tools, I’ll add subcommands send
and receive
directly to the command-line so that terminal session is not necessity to do a file transfer.
After trial and error, i.e. brute force going through crc
crate algorithms, I can say that CRC32_32_ISO_HDLC
is the correct variant for ZMODEM 32-bit transfers, i.e. cipher can be acquired by:
const CRC32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
It is better to declare like this so that it gets compiled into .rodata
and not initialized at run-time.
Pretty easy way to implement this strategy: avoid using Vec
to the extremes. It is quite common in Rust programs that there’s tons of Vec
instances, while in reality most of them are fixed arrays on their usage patterns. In addition heapless
provides pretty nice set of structures for common tasks with a fixed amount data space.
I really like this heapless
. It sort of helps to implement my strategy for developing Rust programs:
1.Maximize no_std
surface. 2 Minimize heap allocations.
It is easier to see then the hot zones where the program actually dynamically grows for a good reason, similarly as with unsafe
blocks it is easy to the red zones for memory errors. This helps a lot with availability and protection against denial-of-service (DoS) attacks.
So to summarize I don’t split Rust program in my mind just to “unsafe” and “safe” but instead I split it “unsafe”, “static” and “dynamic”, or along the lines.
really like this fsmetry
crate (also no_std
):
fsmentry::dsl! {
#[derive(Debug)]
pub Mode {
WaitingInput -> WaitingCommand -> WaitingInput;
WaitingCommand -> SendingFile -> WaitingInput;
WaitingCommand -> ReceivingFile -> WaitingInput;
WaitingCommand -> Exit;
}
}
I use it manage life-cycle in my small serial port tool tior
. I also have some #zmodem code together but it is apparently much bigger leap to implement the #cli interface than it is to implement the protocol. I had to take some time to refactor existing code (e.g. to put FSM in place) and now I’m doing file path auto-completing interface for sending and receiving files with zmodem.
For the text input I’m going to use inquire.
I guess the definition of feature complete for 0.1 version is fully working zmodem transfers and known bugs have been fixed. Right now there is a single known bug: https://github.com/jarkkojs/tior/issues/1.