Parsing some frames

Now that you know how to read bytes, integers, and slices from the input, you can think about parsing frames.

There exist two kinds of frames in the Zstandard RFC:

  • Zstandard frames: they contain compressed data that must be decompressed.
  • Skippable frames: they contain extra sections not related to compressed data and serve no purpose while decompressing. However they must be parsed in order to be skipped and removed from the input.

Adding a frame type

✅ Create a frame module, and a Frame enumerated type in it with two variants, ZstandardFrame and SkippableFrame.

The content of those variants will be filled later on.

Parsing a frame

Each frame starts with a magic number, which can be, in little-endian format:

  • 0xFD2FB528 for a Zstandard frame
  • 0x184D2A5? for a Skippable frame, where ? stands for any hexadecimal digit from 0 to F.

Any other value is an error.

Skippable frames

A skippable frame is easy to extract from the input: right after the magic number, a 32 bit little-endian value gives the length of the frame data, then the data follows.

✅ Write a new struct type SkippableFrame to represent a skippable frame with two fields: magic which is the found magic number, and data which represents the unparsed data corresponding to the skippable frame as a byte slice. Make the Frame::SkippableFrame variant take a SkippableFrame parameter.

As a SkippableFrame will reference data using a byte slice, it will necessarily involve a lifetime generic parameter. Since the Frame enumeration has a SkippableFrame in its Frame::SkippableFrame variant, it will itself need this lifetime generic parameter.

✅ Write a Frame::parse() constructor that returns a frame: a valid Frame::SkippableFrame within Ok if a skippable frame is found, todo!() (for the time being) if a Zstandard frame is found, or an error Error::UnrecognizedMagic(magic) otherwise.

This requires creating a local Error type, and a Result type alias. Notice that the Error type will need a variant to encapsulate a parsing::Error, as the parsing method will use ForwardByteParser methods to extract data.

use crate::parsing::ForwardByteParser;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    // …
}

type Result<T, E = Error> = std::result::Result<T, Error>;

impl<'a> Frame<'a> {
    pub fn parse(input: &mut ForwardByteParser<'a>) -> Result<Self> {
        todo!()
    }
}

✅ Write tests to check that skippable frames are decoded correctly.

Here is an example for starting tests/frame.rs. You must complete this with tests ensuring that a proper error is returned if the input is truncated, similar to the one checking the error if the magic is not recognized.

use net7212::frame::{self, Frame};
use net7212::parsing::ForwardByteParser;

#[test]
fn parse_skippable_frame() {
    let mut parser = ForwardByteParser::new(&[
        // Skippable frame with magic 0x184d2a53, length 3, content 0x10 0x20 0x30
        // and an extra byte at the end.
        0x53, 0x2a, 0x4d, 0x18, 0x03, 0x00, 0x00, 0x00, 0x10, 0x20, 0x30, 0x40,
		//^--- magic (LE) ----^ ^------ 3 (LE) -------^ ^--- content ---^ ^-- extra
    ]);
    let Frame::SkippableFrame(skippable) = Frame::parse(&mut parser).unwrap() else {
        panic!("unexpected frame type")
    };
    assert_eq!(0x184d2a53, skippable.magic);
    assert_eq!(&[0x10, 0x20, 0x30], skippable.data);
    assert_eq!(1, parser.len());
}

#[test]
fn error_on_unknown_frame() {
    let mut parser = ForwardByteParser::new(&[0x10, 0x20, 0x30, 0x40]);
    assert!(matches!(
        Frame::parse(&mut parser),
        Err(frame::Error::UnrecognizedMagic(0x40302010))
    ));
}