Posts
4435
Following
315
Followers
471
Linux kernel hacker and maintainer etc.

OpenPGP: 3AB05486C7752FE1

K. Ryabitsev šŸ

Little known fact: first kernel releases were shipped via the postal service.
7
51
137

Jarkko Sakkinen

I don't really understand why the original ZMODEM spec did not specify these with charts in the first place (like ASCII charts):

https://github.com/jarkkojs/zmodem2/issues/7

Super complicated, inefficient branching and no good reason, not even then:

```
pub const fn escape(value: u8) -> u8 {
match value {
0xff => 0x6d,
0x7f => 0x6c,
0x10 | 0x90 | 0x11 | 0x91 | 0x13 | 0x93 | ZDLE => value ^ 0x40,
// Telenet command escaping, which actually necessary only when preceded
// by 0x40 or 0xc0, meaning that this could be optimized a bit with the
// help of previous byte.
0x0d | 0x8d => value ^ 0x40,
_ => value,
}
}
```
0
0
0

Jarkko Sakkinen

Edited 1 year ago
This is my ultimate goal with #tior: a multiplexed #TTY and file transfer through serial. It won't happen fast I'm barely gettting #ZMODEM together and need to finish the UI for my client after that.

Here I've carved up high-level goal what I have in mind: https://github.com/de-vri-es/serial2-rs/issues/6#issuecomment-1837024452

IMHO this is totally modern world thing. When I was still working e.g. with Nokia Linux phones security, earlier that on #Symbian, serial port was used a lot. Then it sort of phased out for a number of years in my work life but today with the maker community, all sorts of #SBC's and affordable #FPGA's the stocks are rising again. Of course if you've been all the time working on embedded, serial has been around for all this time. It is pretty weird that minicom and lrzsz still rock the world in that arena.

When you work on a a power on for a new hardware device, it is the first channel that gets usually enabled. And e.g. like any FPGA design or something you put to let's say to Arduino it is simplest route to get some communications on going.

So yeah, I really think this sort of endeavour does matter.
0
0
2

Jarkko Sakkinen

Edited 1 year ago

So I did a pull request to the hex crate:

https://github.com/KokaKiwi/rust-hex/pull/83

This sort of scenario is not too uncommon in data flows especially when you want to squeeze everything bit out. E.g. for something like #ZMODEM you’d prefer the implementation to scale from microcontroller to Intel Xeon.

Usage example in my crate:

        if encoding == Encoding::ZHEX {
            hex::decode_in_slice(&mut out).or::<io::Error>(Err(ErrorKind::InvalidData.into()))?;
            out.truncate(out.len() / 2);
        }

#rustlang

0
0
0

Jarkko Sakkinen

One thing where #Rust is pretty inconvenient to use IMHO, when you want to optimize your code in a way that you want to modify data "within its space". I.e. purposely overwrite the same data chunk that you are iterating. Totally doable but not something you tend to do. Sort of thing that is against its design principles. #rustlang
1
0
1

Jarkko Sakkinen

trivial to get my #zmodem crate heapless with the `tinyvec` crate: https://github.com/jarkkojs/zmodem2/commit/6494028c3970c2c9f769643dac0e33c31a103fff

Still a few locations using heap but they are not more complicated than these sites :-)

As for "async" goes I think I just have a function to run one cycle of the state machine as it is fairly clean that way. It should be pretty easy then to wrap whatever framework is used around.

I.e. have start_send and next_send instead of just send and such and so forth. The way zmodem splits stuff that ZDATA subpackets fits well to this scheme. I should probably learn the language level async scheme but I see no point complicating this. I'm sure a framework using that can easily wrap a state machine.

#rustlang
0
0
2

Jarkko Sakkinen

Edited 1 year ago

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.

#rustlang

0
0
1

Jarkko Sakkinen

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.

1
0
1

Jarkko Sakkinen

1
0
0

Jarkko Sakkinen

#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.

#rustlang

0
0
1

Jarkko Sakkinen

next step in my zmodem2 crate is to make send and receive asynchronous so that progress can be tracked. then i’m ready to integrate this to my other small project, tior. #zmodem #rustlang

1
1
3

Jarkko Sakkinen

converted legacy hard coded test cases for frame to rstest in the #zmodem 2 crate:

#[cfg(test)]
mod tests {
    use crate::frame::*;

    #[rstest::rstest]
    #[case(Encoding::ZBIN, Type::ZRQINIT, &[ZPAD, ZLDE, Encoding::ZBIN as u8, 0, 0, 0, 0, 0, 0, 0])]
    #[case(Encoding::ZBIN32, Type::ZRQINIT, &[ZPAD, ZLDE, Encoding::ZBIN32 as u8, 0, 0, 0, 0, 0, 29, 247, 34, 198])]
    fn test_header(
        #[case] encoding: Encoding,
        #[case] frame_type: Type,
        #[case] expected: &[u8]
    ) {
        let header = Header::new(encoding, frame_type);

        let mut packet = vec![];
        new_frame(&header, &mut packet);

        assert_eq!(packet, expected);
    }
    #[rstest::rstest]
    #[case(Encoding::ZBIN, Type::ZRQINIT, &[1, 1, 1, 1], &[ZPAD, ZLDE, Encoding::ZBIN as u8, 0, 1, 1, 1, 1, 98, 148])]
    #[case(Encoding::ZHEX, Type::ZRQINIT, &[1, 1, 1, 1], &[ZPAD, ZPAD, ZLDE, Encoding::ZHEX as u8, 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])]
    fn test_header_with_flags(
        #[case] encoding: Encoding,
        #[case] frame_type: Type,
        #[case] flags: &[u8; 4],
        #[case] expected: &[u8]
    ) {
        let header = Header::new(encoding, frame_type).flags(flags);

        let mut packet = vec![];
        new_frame(&header, &mut packet);

        assert_eq!(packet, expected);
    }
}

Should be easier to refactor the legacy code now as there is less raw code that might be affected in tests.

#rustlang #rstest

0
0
1

Jarkko Sakkinen

Edited 1 year ago

I hope I got this right (safety-proprty), i.e. so that references are enforced to have equal life-time:

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>()) }
    }
}

#rustlang

1
0
0

Jarkko Sakkinen

Edited 1 year ago

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 .

#rustlang

0
0
0

Jarkko Sakkinen

Known good crates for no_std Rust:

  1. spinning (used e.g. in #Enarx)
  2. heapless

Will be updated over time.

#note #rustlang

1
0
1

Jarkko Sakkinen

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.

1
0
0

Jarkko Sakkinen

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.

1
2
3

Jarkko Sakkinen

Edited 1 year ago

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.

#tior #fsmentry #inquire #rustlang

0
0
2

Jarkko Sakkinen

in zmodem protocol there's lot of places where you could "low-barrier" modernize it and i think i'm going after that. like even though there is binary transfer the receiver responds always with hex string for ack's. so that doubles amount of bandwidth for no good.
0
0
0

Jarkko Sakkinen

Edited 1 year ago
I've started to use #himalaya as my email client. I switched from mutt to aerc year ago but it has not worked for my intuition but this actually feels a fresh take on #email.

What makes it a fresh take is in my opinion how it reduces the amount of context switching when you have email sort of integrated to the shell. In mutt you can do a lot without leaving the client but this sort of takes away the whole issue and gives full shell access.
1
0
0
Show older