One thing that I had to dig up from previous #Enarx work was core::marker::PhantomData. It is not so well known but pretty important concept inside Rust.
PhantomData is a zero-sized struct that merely acts as a life-time indicator for the other parameter, which are usually pointers on where it is applied. It is used to implement many of the core structs such as Rc to name one instance.
It is pretty good lesson on how lifetime parameters interact with the Rust compiler.
I’d even say that if you understand PhantomData, then you have the basic understanding of Rust, and if not, you still have to learn a bit. It is the block that the whole core library is based on after all.
I’d actually recommend to do few exercises with just from_raw_parts and lifetime parameters before using zerocopy because that sort of gives you full tutorial on what that particular crate does internally :-) I sort of enjoy thinking of those scenarios so it also takes some fun away :-)
All the crates that #Google has done for #Rust seem to be like stuff I’ve been looking for to get better control of the memory.
Especially zerocopy is a time saver as it has all the thinkable stuff that I have used previously core::slice::from_raw_parts and spent a lot of time thinking of all the possible safety scenarios, such as this recent one:
impl<'a> From<&'a Header> for &'a [u8] {
fn from(value: &Header) -> Self {
// SAFETY: out-of-boundary is not possible, given that the size constraint
// exists in the struct definition. The lifetime parameter links the lifetime
// of the header reference to the slice.
unsafe { from_raw_parts((value as *const Header) as *const u8, size_of::<Header>()) }
}
}
Previously I’ve had to do similar consideration in the #Enarx project. You can do these by hand but it is nice to have a common crate, which is tested by many for these risky scenarios.
Other mentionable crate from Google is tinyvec, which I’m going to use in zmodem2 to remove internal heap usage.
My crate has the implementation now in 933 lines and not a lot of dependencies. It starts to be in a shape that is not a huge stretch to make it fully no_std even. Not the first priority tho but entirely possible.
After I have a similar state function for the receiver I’ll look at futures crate, which claims to be no_std compatible. I.e. idea would be to get yield once per iteration.
I’m not interested in async stuff for multi-threading. I’m interested single-threaded sequenced type of stuff, co-operative multitasking…
This should clarify how it is organized:
/// Map the previous frame type of the sender and incoming frame type of the
/// receiver to the next packet to be sent.
///
/// NOTE: ZRINIT is used here as a wait state, as the sender does not use it for
/// other purposes. Other than tat the states map to the packets that the sender
/// sends next.
const fn next_state(sender: Option<Type>, receiver: Type) -> Option<Type> {
match (sender, receiver) {
(None, Type::ZRINIT) => Some(Type::ZFILE),
(None, _) => Some(Type::ZRQINIT),
(Some(Type::ZRQINIT), Type::ZRINIT) => Some(Type::ZFILE),
(Some(Type::ZFILE), Type::ZRPOS) => Some(Type::ZDATA),
(Some(Type::ZFILE), Type::ZRINIT) => Some(Type::ZRINIT),
(Some(Type::ZRINIT), Type::ZRPOS) => Some(Type::ZDATA),
(Some(Type::ZDATA), Type::ZACK) => Some(Type::ZDATA),
(Some(Type::ZDATA), Type::ZRPOS) => Some(Type::ZDATA),
(Some(Type::ZDATA), Type::ZRINIT) => Some(Type::ZFIN),
(Some(Type::ZFIN), Type::ZFIN) => None,
(_, _) => None,
}
}
No reason to add complexity with fsmentry. This is a nice clean const function.
#zmodem2 is a nice history lesson to develop:
style: cleanup and fix cosmetic stuff
1. This inherits from original `zmodem` crate: "ZLDE" is in-fact ZDLE,
an acronym of "ZMODEM Data Link Escape" character.
2. Fine-tune use statements.
Link: https://wiki.synchro.net/ref:zmodem
Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@iki.fi>
That link in the commit message is a great source of information on #zmodem.
After looking that for some time my state diagram for “sz” is simply this:
fsmentry::dsl! {
#[derive(Debug)]
pub Mode {
WaitingForReceiver -> Starting;
Starting -> Sending;
Starting -> WaitingForPosition;
Sending -> WaitingForPosition;
WaitingForPosition -> Sending;
Sending -> Ending;
}
}
This is scales exactly of the normal serial transfer. ZCHALLENGE/ZCOMPL, “corporate zmodem” (yes it was a real concept back in the say) and running commands by the request of the sender are definitely out of scope. So pretty clean and understandable in the end when you carve deep enough…