Compare commits

...

6 Commits
mux ... main

Author SHA1 Message Date
Alex Bakon
189f4b824d
Upgrade some dependencies
Some checks failed
test / format-check (push) Has been cancelled
test / build-and-test (push) Has been cancelled
test / fuzz-afl (push) Has been cancelled
test / fuzz-libfuzzer (push) Has been cancelled
test / check-nightly (push) Has been cancelled
2025-03-11 13:45:48 -04:00
Alex Bakon
79703a59cd
Centralize dependency versions (#27) 2025-03-10 17:20:11 -04:00
Alex Bakon
339be16385
Bump MSRV to 1.66 (#28) 2025-03-10 17:19:57 -04:00
Jordan Rose
afa74cb65b Bump version to 0.5.3
0.5.2 was never published, but it would be more confusing to reuse the
number now.
2025-01-31 15:27:25 -08:00
Jordan Rose
7dd236d849 mp4san: Bump derive_builder dependency 2025-01-31 15:22:33 -08:00
Milan Stevanovic
28087b861d
Milan/sanitizing cumulative box sized files (#26) 2025-01-28 17:43:28 -08:00
21 changed files with 205 additions and 114 deletions

View File

@ -3,12 +3,45 @@ members = ["cli", "common", "mp4san", "mp4san-derive", "mp4san-test", "mp4san-te
resolver = "2"
[workspace.package]
version = "0.5.2"
version = "0.5.3"
edition = "2021"
rust-version = "1.61.0"
rust-version = "1.66.0"
repository = "https://github.com/privacyresearchgroup/mp4san"
license = "MIT"
exclude = ["/.github", ".*", "/test-data/"]
[workspace.dependencies]
ac-ffmpeg = "0.18.1"
afl = "0.12.16"
anyhow = "1.0.68"
assert_matches = "1.5.0"
bindgen = "0.69.4"
bitflags = "2.4.0"
bitstream-io = "1.7.0"
bytes = "1.3.0"
cc = "1.0.79"
clap = "4.0.32"
criterion = "0.5.1"
derive-where = "1.1.0"
derive_builder = "0.20.2"
derive_more = "2.0.0"
downcast-rs = "2.0.1"
dyn-clonable = "0.9.0"
env_logger = "0.11.3"
ffmpeg-sys-next = { version = "7.0.0", default-features = false }
futures-util = { version = "0.3.28", default-features = false }
libflate = "2.1.0"
libwebp-sys = "0.9.4"
log = "0.4.17"
num-integer = { version = "0.1.45", default-features = false }
num-traits = { version = "0.2.16", default-features = false }
paste = "1.0.14"
pkg-config = "0.3.26"
proc-macro2 = "1.0"
quote = "1.0"
syn = "2.0"
thiserror = "2.0.12"
uuid = "1.3"

View File

@ -6,9 +6,9 @@ version = "0.1.0"
publish = false
[dependencies]
anyhow = "1.0.68"
clap = { version = "4.0.32", features = ["derive"] }
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
mp4san = { path = "../mp4san" }
webpsan = { path = "../webpsan" }
env_logger = "0.11.3"
log = "0.4.17"
env_logger = { workspace = true }
log = { workspace = true }

View File

@ -6,7 +6,7 @@ use std::path::PathBuf;
use anyhow::Context;
use clap::{Parser as _, ValueEnum};
use mp4san::SanitizedMetadata;
use mp4san::{Config, SanitizedMetadata};
#[derive(clap::Parser)]
struct Args {
@ -20,6 +20,9 @@ struct Args {
#[clap(long, short = 'o')]
output: Option<PathBuf>,
#[clap(long, short = 'c')]
cumulative_mdat_box_size: Option<u32>,
/// Path to the file to test sanitization on.
file: PathBuf,
}
@ -51,23 +54,26 @@ fn main() -> Result<(), anyhow::Error> {
let mut infile = File::open(&args.file).context("Error opening file")?;
match format {
Format::Mp4 => match mp4san::sanitize(&mut infile).context("Error parsing mp4 file")? {
SanitizedMetadata { metadata: Some(metadata), data } => {
if let Some(output_path) = args.output {
let mut outfile = File::create(output_path).context("Error opening output file")?;
outfile.write(&metadata).context("Error writing output")?;
infile
.seek(io::SeekFrom::Start(data.offset))
.context("Error seeking input")?;
io::copy(&mut infile.take(data.len), &mut outfile).context("Error copying input to output")?;
Format::Mp4 => {
let config = Config { cumulative_mdat_box_size: args.cumulative_mdat_box_size, ..Default::default() };
match mp4san::sanitize_with_config(&mut infile, config).context("Error parsing mp4 file")? {
SanitizedMetadata { metadata: Some(metadata), data } => {
if let Some(output_path) = args.output {
let mut outfile = File::create(output_path).context("Error opening output file")?;
outfile.write(&metadata).context("Error writing output")?;
infile
.seek(io::SeekFrom::Start(data.offset))
.context("Error seeking input")?;
io::copy(&mut infile.take(data.len), &mut outfile).context("Error copying input to output")?;
}
}
SanitizedMetadata { metadata: None, .. } => {
if let Some(output_path) = args.output {
fs::copy(&args.file, output_path).context("Error writing output")?;
}
}
}
SanitizedMetadata { metadata: None, .. } => {
if let Some(output_path) = args.output {
fs::copy(&args.file, output_path).context("Error writing output")?;
}
}
},
}
Format::Webp => {
webpsan::sanitize(infile).context("Error parsing webp file")?;
if let Some(output_path) = args.output {

View File

@ -6,6 +6,6 @@ version = "0.1.0"
publish = false
[dependencies]
env_logger = "0.11.3"
libflate = "2.1.0"
log = "0.4.17"
env_logger = { workspace = true }
libflate = { workspace = true }
log = { workspace = true }

View File

@ -15,7 +15,7 @@ readme = "../README.md"
exclude.workspace = true
[dependencies]
bytes = "1.3.0"
derive_more = "0.99.17"
futures-util = { version = "0.3.28", default-features = false, features = ["io"] }
thiserror = "1.0.38"
bytes = { workspace = true }
derive_more = { workspace = true, features = ["deref", "deref_mut", "display"] }
futures-util = { workspace = true, features = ["io"] }
thiserror = { workspace = true }

View File

@ -40,12 +40,12 @@ pub struct Report<E: ReportableError> {
/// A [`Display`]-able indicating there was extra trailing input after parsing.
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "extra unparsed input")]
#[display("extra unparsed input")]
pub struct ExtraUnparsedInput;
/// A [`Display`]-able indicating an error occurred while parsing a certain type.
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing value of type `{}`", _0)]
#[display("while parsing value of type `{}`", _0)]
pub struct WhileParsingType(&'static str);
/// A convenience type alias for a [`Result`](std::result::Result) containing an error wrapped by a [`Report`].
@ -70,7 +70,7 @@ pub struct ReportStack {
/// A null error stack which ignores all data attached to it.
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "")]
#[display("")]
pub struct NullReportStack;
/// A trait for error types which can be used in a [`Report`].
@ -95,7 +95,7 @@ pub trait ReportableErrorStack: Display {
//
#[derive(derive_more::Display)]
#[display(fmt = "{message} at {location}")]
#[display("{message} at {location}")]
struct ReportEntry {
message: Box<dyn Display + Send + Sync + 'static>,
location: &'static Location<'static>,

View File

@ -18,7 +18,7 @@ exclude.workspace = true
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
syn = "2.0"
quote = "1.0"
uuid = "1.3"
proc-macro2 = { workspace = true }
syn = { workspace = true }
quote = { workspace = true }
uuid = { workspace = true }

View File

@ -6,9 +6,9 @@ version = "0.1.0"
publish = false
[dependencies]
anyhow = "1.0.68"
clap = { version = "4.0.32", features = ["derive"] }
env_logger = "0.11.3"
libflate = "2.1.0"
log = "0.4.17"
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
env_logger = { workspace = true }
libflate = { workspace = true }
log = { workspace = true }
mp4san = { path = "../mp4san" }

View File

@ -11,12 +11,12 @@ ffmpeg = ["dep:ac-ffmpeg", "dep:bindgen", "dep:cc", "dep:ffmpeg-sys-next"]
gpac = ["dep:bindgen", "dep:cc", "dep:pkg-config"]
[dependencies]
ac-ffmpeg = { version = "0.18.1", optional = true }
ffmpeg-sys-next = { version = "7.0.0", default-features = false, features = ["avformat"], optional = true }
log = "0.4.17"
thiserror = "1.0.40"
ac-ffmpeg = { workspace = true, optional = true }
ffmpeg-sys-next = { workspace = true, default-features = false, features = ["avformat"], optional = true }
log = { workspace = true }
thiserror = { workspace = true }
[build-dependencies]
bindgen = { version = "0.69.4", optional = true }
cc = { version = "1.0.79", optional = true }
pkg-config = { version = "0.3.26", optional = true }
bindgen = { workspace = true, optional = true }
cc = { workspace = true, optional = true }
pkg-config = { workspace = true, optional = true }

View File

@ -15,20 +15,20 @@ readme = "README.md"
exclude.workspace = true
[dependencies]
bytes = "1.3.0"
derive-where = "1.1.0"
derive_builder = "0.20.0"
derive_more = "0.99.17"
downcast-rs = "1.2.0"
dyn-clonable = "0.9.0"
futures-util = { version = "0.3.28", default-features = false, features = ["io"] }
log = "0.4.17"
mediasan-common = { path = "../common", version = "=0.5.2" }
mp4san-derive = { path = "../mp4san-derive", version = "=0.5.2" }
paste = "1.0.14"
thiserror = "1.0.38"
bytes = { workspace = true }
derive-where = { workspace = true }
derive_builder = { workspace = true }
derive_more = { workspace = true, features = ["display", "from"] }
downcast-rs = { workspace = true }
dyn-clonable = { workspace = true }
futures-util = { workspace = true, features = ["io"] }
log = { workspace = true }
mediasan-common = { path = "../common", version = "=0.5.3" }
mp4san-derive = { path = "../mp4san-derive", version = "=0.5.3" }
paste = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
assert_matches = "1.5.0"
assert_matches = { workspace = true }
mediasan-common-test = { path = "../common-test" }
mp4san-test = { path = "../mp4san-test" }

View File

@ -90,6 +90,29 @@ pub struct Config {
/// The default is 1 GiB.
#[builder(default = "1024 * 1024 * 1024")]
pub max_metadata_size: u64,
/// The cumulative MDAT box size
///
/// The value is tightly associated with a specific
/// use case scenario in which the transcoder internally
/// generates a sequence of MDAT boxes, but delivers
/// them compounded as a single monolythic MDAT box.
///
/// In order to avoid constantly updating the single
/// MDAT box size (which may be impossible when the
/// payload is continually encrypted and written to
/// the file), the transcoder instead does the following:
/// a) writes the MDAT box size to be equal to the
/// fixed zero value
/// b) keeps accumulating the box size and passes
/// it as config argument to mp4sanitizer
///
/// IMPORTANT: given the special circumstances of
/// the use case scenario of transcoding on mobile
/// devices, the MDAT box size is expected to not
/// exceed the 32-bit max bytes limit. Hence the
/// cumulative_mdat_box_size is a 32-bit value
#[builder(default = None)]
pub cumulative_mdat_box_size: Option<u32>,
}
/// Sanitized metadata returned by the sanitizer.
@ -120,7 +143,7 @@ pub const COMPATIBLE_BRAND: FourCC = FourCC { value: *b"isom" };
//
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "box data too large: {} > {}", _0, _1)]
#[display("box data too large: {} > {}", _0, _1)]
struct BoxDataTooLarge(u64, u64);
const MAX_FTYP_SIZE: u64 = 1024;
@ -260,7 +283,7 @@ pub async fn sanitize_async_with_config<R: AsyncRead + AsyncSkip>(
while !reader.as_mut().fill_buf().await?.is_empty() {
let start_pos = reader.as_mut().stream_position().await?;
let header = BoxHeader::read(&mut reader)
let mut header = BoxHeader::read(&mut reader)
.await
.map_eof(|_| Error::Parse(report_attach!(ParseError::TruncatedBox, "while parsing box header")))?;
@ -306,6 +329,12 @@ pub async fn sanitize_async_with_config<R: AsyncRead + AsyncSkip>(
}
BoxType::MDAT => {
if let Ok(None) = header.box_data_size() {
if let Some(t) = config.cumulative_mdat_box_size {
header.overwrite_size(t);
}
}
let box_size = skip_box(reader.as_mut(), &header).await? + header.encoded_len();
log::info!("mdat @ 0x{start_pos:08x}: {box_size} bytes");
@ -803,4 +832,22 @@ mod test {
assert_matches!(err.into_inner(), ParseError::InvalidBoxLayout);
});
}
#[test]
fn cumulative_mdat_box_size() {
let test_spec = test_mp4().mdat_data_until_eof().build_spec().unwrap();
let test_1 = test_spec.build();
let mdat_box_length = test_1.mdat.len as u32;
let config_bad = Config::builder().build();
assert_matches!(sanitize_with_config(test_1, config_bad).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(_));
});
let test_2 = test_spec.build();
let config_good = Config::builder()
.cumulative_mdat_box_size(Some(mdat_box_length))
.build();
test_2.sanitize_ok_with_config(config_good);
}
}

View File

@ -77,23 +77,23 @@ pub trait __ParseResultExt: ResultExt + Sized {
pub(crate) use self::__ParseResultExt as ParseResultExt;
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "multiple `{}` boxes", _0)]
#[display("multiple `{}` boxes", _0)]
pub(crate) struct MultipleBoxes(pub(crate) BoxType);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` box", _0)]
#[display("while parsing `{}` box", _0)]
pub(crate) struct WhileParsingBox(pub(crate) BoxType);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` box field `{}`", _0, _1)]
#[display("while parsing `{}` box field `{}`", _0, _1)]
pub(crate) struct WhileParsingField<T>(pub(crate) BoxType, pub(crate) T);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` box child `{}`", _0, _1)]
#[display("while parsing `{}` box child `{}`", _0, _1)]
pub(crate) struct WhileParsingChild(pub(crate) BoxType, pub(crate) BoxType);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "where `{} = {}`", _0, _1)]
#[display("where `{} = {}`", _0, _1)]
pub(crate) struct WhereEq<T, U>(pub(crate) T, pub(crate) U);
impl ReportableError for ParseError {

View File

@ -131,6 +131,11 @@ impl BoxHeader {
Ok(Self { box_type: name, box_size: size })
}
pub fn overwrite_size(&mut self, actual_box_size: u32) {
assert_eq!(self.box_size, BoxSize::UntilEof);
self.box_size = BoxSize::Size(actual_box_size);
}
pub const fn encoded_len(&self) -> u64 {
let mut size = FourCC::size() + size_of::<u32>() as u64;
if let BoxSize::Ext(_) = self.box_size {

@ -1 +1 @@
Subproject commit b9f4d3f3273819ce50170357098a56113eb088b2
Subproject commit b2121e38ec04025fdb62306b4863566fa993c8cb

View File

@ -10,6 +10,6 @@ default = []
libwebp = ["dep:libwebp-sys"]
[dependencies]
libwebp-sys = { version = "0.9.4", optional = true }
log = "0.4.17"
thiserror = "1.0.40"
libwebp-sys = { workspace = true, optional = true }
log = { workspace = true }
thiserror = { workspace = true }

View File

@ -19,21 +19,21 @@ default = ["error-detail"]
error-detail = []
[dependencies]
assert_matches = "1.5.0"
bitflags = "2.4.0"
bitstream-io = "1.7.0"
bytes = "1.3.0"
derive_builder = "0.20.0"
derive_more = "0.99.17"
log = "0.4.17"
mediasan-common = { path = "../common", version = "=0.5.2" }
num-integer = { version = "0.1.45", default-features = false }
num-traits = { version = "0.2.16", default-features = false }
thiserror = "1.0.38"
assert_matches = { workspace = true }
bitflags = { workspace = true }
bitstream-io = { workspace = true }
bytes = { workspace = true }
derive_builder = { workspace = true }
derive_more = { workspace = true, features = ["display"] }
log = { workspace = true }
mediasan-common = { path = "../common", version = "=0.5.3" }
num-integer = { workspace = true }
num-traits = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
assert_matches = "1.5.0"
criterion = { version = "0.5.1", features = ["async_futures"] }
assert_matches = { workspace = true }
criterion = { workspace = true, features = ["async_futures"] }
mediasan-common-test = { path = "../common-test" }
webpsan-test = { path = "../webpsan-test" }

View File

@ -72,7 +72,7 @@ trait ReadSkip: Read + Skip {}
type DynChunkReader<'a> = ChunkReader<dyn ReadSkip + 'a>;
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "frame dimensions `{_0}`x`{_1}` do not match canvas dimensions `{_2}`x`{_3}`")]
#[display("frame dimensions `{_0}`x`{_1}` do not match canvas dimensions `{_2}`x`{_3}`")]
struct FrameDimensionsMismatch(NonZeroU16, NonZeroU16, NonZeroU32, NonZeroU32);
//

View File

@ -26,7 +26,7 @@ pub struct CanonicalHuffmanTree<E: Endianness, S: Clone> {
}
#[derive(Display)]
#[display(fmt = "invalid lz77 prefix code `{_0}`")]
#[display("invalid lz77 prefix code `{_0}`")]
struct InvalidLz77PrefixCode(u16);
pub const LZ77_MAX_LEN: u16 = (LZ77_MAX_SYMBOL - 2) >> 1;

View File

@ -58,19 +58,19 @@ pub(crate) trait ParseResultExt: ResultExt + Sized {
}
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "multiple `{}` chunks", _0)]
#[display("multiple `{}` chunks", _0)]
pub(crate) struct MultipleChunks(pub(crate) FourCC);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "expected `{}` chunk", _0)]
#[display("expected `{}` chunk", _0)]
pub(crate) struct ExpectedChunk(pub(crate) FourCC);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` chunk", _0)]
#[display("while parsing `{}` chunk", _0)]
pub(crate) struct WhileParsingChunk(pub(crate) FourCC);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` chunk field `{}`", _0, _1)]
#[display("while parsing `{}` chunk field `{}`", _0, _1)]
pub(crate) struct WhileParsingField<T>(pub(crate) FourCC, pub(crate) T);
impl ReportableError for ParseError {

View File

@ -26,26 +26,26 @@ pub struct LosslessImage {
#[derive(Clone, Display, PartialEq, Eq)]
enum Transform {
#[display(fmt = "predictor transform: block size {block_size}")]
#[display("predictor transform: block size {block_size}")]
Predictor { block_size: u16, _image: EntropyCodedImage },
#[display(fmt = "color transform: block size {block_size}")]
#[display("color transform: block size {block_size}")]
Color { block_size: u16, _image: EntropyCodedImage },
#[display(fmt = "subtract green transform")]
#[display("subtract green transform")]
SubtractGreen,
#[display(fmt = "color indexing transform: {} colors", "image.width")]
#[display("color indexing transform: {} colors", "image.width")]
ColorIndexing { image: EntropyCodedImage },
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, PartialOrd, Ord)]
enum TransformType {
#[display(fmt = "predictor")]
#[display("predictor")]
Predictor = 0b00,
#[display(fmt = "color")]
#[display("color")]
Color = 0b01,
#[display(fmt = "subtract green")]
#[display("subtract green")]
SubtractGreen = 0b10,
#[display(fmt = "color indexing")]
#[display("color indexing")]
ColorIndexing = 0b11,
}
@ -59,14 +59,14 @@ struct EntropyCodedImage {
struct SpatiallyCodedImage;
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq)]
#[display(fmt = "distance {dist} length {len}")]
#[display("distance {dist} length {len}")]
struct BackReference {
dist: NonZeroU32,
len: NonZeroU32,
}
#[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq)]
#[display(fmt = "({alpha}, {red}, {green}, {blue})")]
#[display("({alpha}, {red}, {green}, {blue})")]
struct Color {
alpha: u8,
red: u8,
@ -81,9 +81,9 @@ struct ColorCache {
#[derive(Clone, Display, PartialEq, Eq)]
enum MetaPrefixCodes {
#[display(fmt = "single meta prefix code")]
#[display("single meta prefix code")]
Single,
#[display(fmt = "multiple meta prefix codes: max code group {max_code_group}, block size {block_size}")]
#[display("multiple meta prefix codes: max code group {max_code_group}, block size {block_size}")]
Multiple {
block_size: u16,
max_code_group: u16,
@ -124,39 +124,39 @@ struct DistancePrefixCode {
}
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "out-of-bounds color cache index `{_0}` >= `{_1}`")]
#[display("out-of-bounds color cache index `{_0}` >= `{_1}`")]
struct ColorCacheIndexOutOfBounds(u16, u16);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid back-reference distance `{_0}` at pixel `{_1}`")]
#[display("invalid back-reference distance `{_0}` at pixel `{_1}`")]
struct InvalidBackRefDistance(NonZeroU32, u32);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid back-reference length `{_0}` at pixel `{_1}` with image length `{_2}`")]
#[display("invalid back-reference length `{_0}` at pixel `{_1}` with image length `{_2}`")]
struct InvalidBackRefLength(NonZeroU32, u32, u32);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid code length repetition `{_0}` at `{_1}` with max symbols `{_2}`")]
#[display("invalid code length repetition `{_0}` at `{_1}` with max symbols `{_2}`")]
struct InvalidCodeLengthRepetition(u8, usize, u16);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid color cache size `{_0}`")]
#[display("invalid color cache size `{_0}`")]
struct InvalidColorCacheSize(u8);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid duplicate {_0} transform")]
#[display("invalid duplicate {_0} transform")]
struct InvalidDuplicateTransform(TransformType);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid predictor `{_0}`")]
#[display("invalid predictor `{_0}`")]
struct InvalidPredictor(u8);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid symbol count `{_0}` >= `{_1}`")]
#[display("invalid symbol count `{_0}` >= `{_1}`")]
struct InvalidSymbolCount(u16, u16);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing {_0} transform")]
#[display("while parsing {_0} transform")]
struct WhileParsingTransform(TransformType);
//

View File

@ -31,7 +31,7 @@ pub struct Vp8lChunk {
//
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid VP8L signature `0x{_0:x}` != `0x{}`", Vp8lChunk::SIGNATURE)]
#[display("invalid VP8L signature `0x{_0:x}` != `0x{}`", Vp8lChunk::SIGNATURE)]
struct InvalidSignature(u8);
//