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 aFrame
enumerated type in it with two variants,ZstandardFrame
andSkippableFrame
.
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 frame0x184D2A5?
for a Skippable frame, where?
stands for any hexadecimal digit from0
toF
.
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, anddata
which represents the unparsed data corresponding to the skippable frame as a byte slice. Make theFrame::SkippableFrame
variant take aSkippableFrame
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 validFrame::SkippableFrame
withinOk
if a skippable frame is found,todo!()
(for the time being) if a Zstandard frame is found, or an errorError::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))
));
}