Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The main program

You are now able to parse skippable frames. Let’s write a program which displays, for each skippable frame, its magic number and content. Also, the program will check that the file is a well-formed zstd file.

Command-line arguments

We want our program to act like this:

$ cargo run -- --help
Usage: net7212 [OPTIONS] <FILE_NAME>

Arguments:
  <FILE_NAME>  File name to decompress

Options:
  -i, --info     Dump information about frames instead of outputting the result
  -h, --help     Print help
  -V, --version  Print version
  
$ cargo run -- --info skippables.zst
SkippableFrame(
    SkippableFrame {
        magic: 0x184d2a53,
        data: [
            0x10,
            0x20,
            0x30,
        ],
    },
)
SkippableFrame(
    SkippableFrame {
        magic: 0x184d2a51,
        data: [
            0x42,
        ],
    },
)

(you can download skippables.zst)

Create the main program

✅ Create the src/main.rs file containing a main function.

✅ Use clap with its derive feature to implement command-line arguments handling: declare a struct describing the arguments, derive clap::Parser on it, and call Args::parse() in main().

You can read a file using std::fs::read(). Do not print anything yet, we will do it step by step.

Decoding a frame

As previously explained, a decoding phase must happen after the parsing of a frame when --info is not used as an argument to the main program.

✅ Implement a decode(self) method on a Frame returning the decoded data in a Vec<u8>.

Note that self means that the Frame object no longer exists after decode() has been called. This is expected, as some decoding operations will consume frame data.

So, at this time:

  • You can decode a SkippableFrame as it decodes to nothing (it is skipped after all).
  • You may use todo!() while decoding a ZstandardFrame as not even the parsing is implemented.

Iterating over the frames

Since all frames must be parsed in sequence, one can use an iterator to transform a forward byte parser into a succession of Frame objects.

✅ Add a FrameIterator in the frame module, which encapsulates a ForwardByteParser.

✅ Implement the Iterator trait for FrameIterator, the Item being Result<Frame>.

The iterator’s next() method will:

  • Return None if the parser has no content left to indicate the end of the iterator.
  • Return Some(Frame::parse(&mut self.parser)) otherwise.

Note that this will automatically signal an error if extra characters are present at the end of an otherwise valid file, as the next iteration will attempt to parse another frame which will be invalid.

Writing the main program body

✅ Complete the main program so that it can either print information or decode frames from a zstd file in sequence.

Of course you must stop if the frame iterator returns an error.

Do not forget that:

  • You can derive the Debug trait automatically on many types.
  • You can display something implementing the Debug trait with the {:?} construct.
  • You can use an alternate representation with the {:#?} construct (for example indented structures).
  • You can use an alternate representation and print numbers in hexadecimal with {:#x?}.

Also, when --info is not used, the decoded data (even though there is nothing to display yet) must be sent to the standard output. You can use the write_all() method to write on std::io::stdout() after locking it.

If you use the color_eyre crate as suggested in the general principles, make your main() function return a color_eyre::eyre::Result<()>, call color_eyre::install()? at its beginning, and let color_eyre display a pretty error report if there is one.

Adding tracing information

Since your program will use the standard output when decoding a file, you must not use it yourself to print debugging information. Use the tracing crate to add traces where you need them (tracing::debug!(), tracing::trace!(), …), and install a tracing_subscriber in main() with tracing_subscriber::fmt::init(): traces go to the standard error, and their verbosity is controlled at run time through the RUST_LOG environment variable (e.g., RUST_LOG=trace cargo run -- file.zst).

✅ Check that you can decode a file made only of skippable frames, as well as an empty file (which is a valid zstd file with zero frames). Check that you signal an error if you try to decode an invalid file.