Skip to content

Commit 90f45e2

Browse files
Make page CRC verification toggleable by end-users (#44)
* Fix Cargo warning on latest Rust versions about custom cfg flags * Make page CRC verification toggleable by end-users These changes make the Ogg page CRC verification check toggleable at runtime by end-users via a new `PageParsingOptions` struct that may be passed at reader/parser construction time. I have paid special attention to making the new feature both backwards and forwards compatible, which led me to taking the following design choices: - No signature of any existing public method was modified. People looking into changing parsing behavior via the new `PageParsingOptions` struct must use the new `*_with_parse_opts` methods. - Marking `PageParsingOptions` as `#[non_exhaustive]`, so that we may add new public fields to it in the future without a semver-breaking change. Users must always default-initialize this struct. It only derives `Clone` too, in case we ever need to make it hold non-`Copy` types. - Shared ownership of the `PageParsingOptions` between different reader structs is achieved through `Arc`s, which ensures that no struct stops being `Send` or `Sync` with a negligble performance impact. The `fuzzing` cfg flag is still honored after these changes by using it to set a default value for the new `verify_hash` page parsing option accordingly. * Fix build with `async` feature * Attempt to fix MSRV build * Bump MSRV to 1.61 We required it anyway due to recent changes. * Further CI MSRV check fixes
1 parent 4b22c17 commit 90f45e2

File tree

5 files changed

+109
-38
lines changed

5 files changed

+109
-38
lines changed

.github/workflows/ogg.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
strategy:
99
matrix:
1010
os: [macOS-latest, ubuntu-latest, windows-latest]
11-
toolchain: [stable, beta, 1.56.1]
11+
toolchain: [stable, beta, 1.61]
1212

1313
runs-on: ${{ matrix.os }}
1414

@@ -19,23 +19,23 @@ jobs:
1919
with:
2020
toolchain: ${{ matrix.toolchain }}
2121
override: true
22-
- name: Downgrade dependencies to comply with MSRV
23-
if: matrix.toolchain == '1.56.1'
24-
run: cargo update -p tokio --precise 1.29.1
2522
- name: Run no-default-features builds
26-
if: matrix.toolchain != '1.56.1'
23+
if: matrix.toolchain != '1.61'
2724
run: |
2825
cargo test --verbose --no-default-features
2926
cargo doc --verbose --no-default-features
27+
- name: Downgrade dependencies to comply with MSRV
28+
if: matrix.toolchain == '1.61'
29+
run: cargo update -p tokio --precise 1.29.1
3030
- name: Run no-default-features builds (forbidding warnings)
31-
if: matrix.toolchain == '1.56.1'
31+
if: matrix.toolchain == '1.61'
3232
env:
3333
RUSTFLAGS: -D warnings
3434
run: |
3535
cargo test --verbose --no-default-features
3636
cargo doc --verbose --no-default-features
3737
- name: Run all-features builds
38-
if: matrix.toolchain != '1.56.1'
38+
if: matrix.toolchain != '1.61'
3939
run: |
4040
cargo test --verbose --all-features
4141
cargo doc --verbose --all-features

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ keywords = ["ogg", "decoder", "encoder", "xiph"]
99
documentation = "https://docs.rs/ogg/0.8.0"
1010
repository = "https://github.com/RustAudio/ogg"
1111
readme = "README.md"
12-
rust-version = "1.56.0"
12+
rust-version = "1.61.0"
1313

1414
[lib]
1515
name = "ogg"
@@ -34,3 +34,6 @@ async = ["futures-core", "futures-io", "tokio", "tokio-util", "bytes", "pin-proj
3434
[package.metadata.docs.rs]
3535
all-features = true
3636
rustdoc-args = ["--cfg", "docsrs"]
37+
38+
[lints.rust]
39+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
An Ogg decoder and encoder. Implements the [xiph.org Ogg spec](https://www.xiph.org/vorbis/doc/framing.html) in pure Rust.
44

5-
If the `async` feature is disabled, Version 1.56.1 of Rust is the minimum supported one.
5+
If the `async` feature is disabled, Version 1.61 of Rust is the minimum supported one.
66

77
Note: `.ogg` files are vorbis encoded audio files embedded into an Ogg transport stream.
88
There is no extra support for vorbis codec decoding or encoding in this crate,

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub mod reading;
4040
pub mod writing;
4141

4242
pub use crate::writing::{PacketWriter, PacketWriteEndInfo};
43-
pub use crate::reading::{PacketReader, OggReadError};
43+
pub use crate::reading::{PacketReader, OggReadError, PageParsingOptions};
4444

4545
/**
4646
Ogg packet representation.

src/reading.rs

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use std::mem::replace;
2121
use crate::crc::vorbis_crc32_update;
2222
use crate::Packet;
2323
use std::io::Seek;
24+
use std::sync::Arc;
2425

2526
/// Error that can be raised when decoding an Ogg transport.
2627
#[derive(Debug)]
@@ -157,6 +158,28 @@ impl OggPage {
157158
}
158159
}
159160

161+
/// A set of options that control details on how a `PageParser` parses OGG pages.
162+
#[derive(Debug, Clone)]
163+
#[non_exhaustive] // Allow for non-breaking field expansion: https://doc.rust-lang.org/cargo/reference/semver.html#struct-add-public-field-when-no-private
164+
pub struct PageParsingOptions {
165+
/// Whether the CRC checksum for the parsed pages will be verified to match
166+
/// the page and packet data. Verifying it is the default behavior, as
167+
/// a hash mismatch usually signals stream corruption, but specialized
168+
/// applications may wish to deal with the contained data anyway.
169+
pub verify_checksum :bool,
170+
}
171+
172+
impl Default for PageParsingOptions {
173+
fn default() -> Self {
174+
Self {
175+
// Do not verify checksum by default when the decoder is being fuzzed.
176+
// This makes it easier for random input from fuzzers to reach decoding code that's
177+
// actually interesting, instead of being rejected early due to checksum mismatch.
178+
verify_checksum: !cfg!(fuzzing),
179+
}
180+
}
181+
}
182+
160183
/**
161184
Helper struct for parsing pages
162185
@@ -178,6 +201,8 @@ pub struct PageParser {
178201
/// after segments have been parsed, this contains the segments buffer,
179202
/// after the packet data have been read, this contains the packets buffer.
180203
segments_or_packets_buf :Vec<u8>,
204+
205+
parse_opts :Arc<PageParsingOptions>,
181206
}
182207

183208
impl PageParser {
@@ -193,6 +218,23 @@ impl PageParser {
193218
/// You should allocate and fill such an array, in order to pass it to the `parse_segments`
194219
/// function.
195220
pub fn new(header_buf :[u8; 27]) -> Result<(PageParser, usize), OggReadError> {
221+
Self::new_with_parse_opts(header_buf, PageParsingOptions::default())
222+
}
223+
224+
/// Creates a new Page parser with the specified `parse_opts`
225+
///
226+
/// The `header_buf` param contains the first 27 bytes of a new OGG page.
227+
/// Determining when one begins is your responsibility. Usually they
228+
/// begin directly after the end of a previous OGG page, but
229+
/// after you've performed a seek you might end up within the middle of a page
230+
/// and need to recapture.
231+
///
232+
/// `parse_opts` controls details on how the OGG page will be parsed.
233+
///
234+
/// Returns a page parser, and the requested size of the segments array.
235+
/// You should allocate and fill such an array, in order to pass it to the `parse_segments`
236+
/// function.
237+
pub fn new_with_parse_opts(header_buf :[u8; 27], parse_opts :impl Into<Arc<PageParsingOptions>>) -> Result<(PageParser, usize), OggReadError> {
196238
let mut header_rdr = Cursor::new(header_buf);
197239
header_rdr.set_position(4);
198240
let stream_structure_version = tri!(header_rdr.read_u8());
@@ -221,6 +263,7 @@ impl PageParser {
221263
header_buf,
222264
packet_count : 0,
223265
segments_or_packets_buf :Vec::new(),
266+
parse_opts: parse_opts.into(),
224267
},
225268
// Number of page segments
226269
header_rdr.read_u8().unwrap() as usize
@@ -271,31 +314,28 @@ impl PageParser {
271314
page_siz as usize
272315
}
273316

274-
/// Parses the packets data and verifies the checksum.
317+
/// Parses the packets data and verifies the checksum if appropriate.
275318
///
276319
/// Returns an `OggPage` to be used by later code.
277320
pub fn parse_packet_data(mut self, packet_data :Vec<u8>) ->
278321
Result<OggPage, OggReadError> {
279-
// Now to hash calculation.
280-
// 1. Clear the header buffer
281-
self.header_buf[22] = 0;
282-
self.header_buf[23] = 0;
283-
self.header_buf[24] = 0;
284-
self.header_buf[25] = 0;
285-
286-
// 2. Calculate the hash
287-
let mut hash_calculated :u32;
288-
hash_calculated = vorbis_crc32_update(0, &self.header_buf);
289-
hash_calculated = vorbis_crc32_update(hash_calculated,
290-
&self.segments_or_packets_buf);
291-
hash_calculated = vorbis_crc32_update(hash_calculated, &packet_data);
292-
293-
// 3. Compare to the extracted one
294-
if self.checksum != hash_calculated {
295-
// Do not verify checksum when the decoder is being fuzzed.
296-
// This allows random input from fuzzers reach decoding code that's actually interesting,
297-
// instead of being rejected early due to checksum mismatch.
298-
if !cfg!(fuzzing) {
322+
if self.parse_opts.verify_checksum {
323+
// Now to hash calculation.
324+
// 1. Clear the header buffer
325+
self.header_buf[22] = 0;
326+
self.header_buf[23] = 0;
327+
self.header_buf[24] = 0;
328+
self.header_buf[25] = 0;
329+
330+
// 2. Calculate the hash
331+
let mut hash_calculated :u32;
332+
hash_calculated = vorbis_crc32_update(0, &self.header_buf);
333+
hash_calculated = vorbis_crc32_update(hash_calculated,
334+
&self.segments_or_packets_buf);
335+
hash_calculated = vorbis_crc32_update(hash_calculated, &packet_data);
336+
337+
// 3. Compare to the extracted one
338+
if self.checksum != hash_calculated {
299339
tri!(Err(OggReadError::HashMismatch(self.checksum, hash_calculated)));
300340
}
301341
}
@@ -721,6 +761,7 @@ and look into the async module.
721761
*/
722762
pub struct PacketReader<T :io::Read + io::Seek> {
723763
rdr :T,
764+
pg_parse_opts :Arc<PageParsingOptions>,
724765

725766
base_pck_rdr :BasePacketReader,
726767

@@ -730,7 +771,11 @@ pub struct PacketReader<T :io::Read + io::Seek> {
730771
impl<T :io::Read + io::Seek> PacketReader<T> {
731772
/// Constructs a new `PacketReader` with a given `Read`.
732773
pub fn new(rdr :T) -> PacketReader<T> {
733-
PacketReader { rdr, base_pck_rdr : BasePacketReader::new(), read_some_pg : false }
774+
Self::new_with_page_parse_opts(rdr, PageParsingOptions::default())
775+
}
776+
/// Constructs a new `PacketReader` with a given `Read` and OGG page parsing options.
777+
pub fn new_with_page_parse_opts(rdr :T, pg_parse_opts : impl Into<Arc<PageParsingOptions>>) -> PacketReader<T> {
778+
PacketReader { rdr, pg_parse_opts: pg_parse_opts.into(), base_pck_rdr : BasePacketReader::new(), read_some_pg : false }
734779
}
735780
/// Returns the wrapped reader, consuming the `PacketReader`.
736781
pub fn into_inner(self) -> T {
@@ -820,14 +865,14 @@ impl<T :io::Read + io::Seek> PacketReader<T> {
820865
None if self.read_some_pg => return Ok(None),
821866
None => return Err(OggReadError::NoCapturePatternFound)
822867
};
823-
let (mut pg_prs, page_segments) = tri!(PageParser::new(header_buf));
868+
let (mut pg_prs, page_segments) = tri!(PageParser::new_with_parse_opts(header_buf, Arc::clone(&self.pg_parse_opts)));
824869

825870
let mut segments_buf = vec![0; page_segments]; // TODO fix this, we initialize memory for NOTHING!!! Out of some reason, this is seen as "unsafe" by rustc.
826871
tri!(self.rdr.read_exact(&mut segments_buf));
827872

828873
let page_siz = pg_prs.parse_segments(segments_buf);
829874

830-
let mut packet_data = vec![0; page_siz as usize];
875+
let mut packet_data = vec![0; page_siz];
831876
tri!(self.rdr.read_exact(&mut packet_data));
832877

833878
Ok(Some(tri!(pg_prs.parse_packet_data(packet_data))))
@@ -1088,12 +1133,14 @@ pub mod async_api {
10881133
*/
10891134
struct PageDecoder {
10901135
state : PageDecodeState,
1136+
parse_opts : Arc<PageParsingOptions>,
10911137
}
10921138

10931139
impl PageDecoder {
1094-
fn new() -> Self {
1140+
fn new(parse_opts : impl Into<Arc<PageParsingOptions>>) -> Self {
10951141
PageDecoder {
10961142
state : PageDecodeState::Head,
1143+
parse_opts : parse_opts.into(),
10971144
}
10981145
}
10991146
}
@@ -1119,7 +1166,7 @@ pub mod async_api {
11191166
// TODO once we have const generics, the copy below can be done
11201167
// much nicer, maybe with a new into_array fn on Vec's
11211168
hdr_buf.copy_from_slice(&consumed_buf);
1122-
let tup = tri!(PageParser::new(hdr_buf));
1169+
let tup = tri!(PageParser::new_with_parse_opts(hdr_buf, Arc::clone(&self.parse_opts)));
11231170
Segments(tup.0, tup.1)
11241171
},
11251172
Segments(mut pg_prs, _) => {
@@ -1162,9 +1209,18 @@ pub mod async_api {
11621209
/// This is the recommended constructor when using the Tokio runtime
11631210
/// types.
11641211
pub fn new(inner :T) -> Self {
1212+
Self::new_with_page_parse_opts(inner, PageParsingOptions::default())
1213+
}
1214+
1215+
/// Wraps the specified Tokio runtime `AsyncRead` into an Ogg packet
1216+
/// reader, using the specified options for parsing Ogg pages.
1217+
///
1218+
/// This is the recommended constructor when using the Tokio runtime
1219+
/// types.
1220+
pub fn new_with_page_parse_opts(inner :T, pg_parse_opts :impl Into<Arc<PageParsingOptions>>) -> Self {
11651221
PacketReader {
11661222
base_pck_rdr : BasePacketReader::new(),
1167-
pg_rd : FramedRead::new(inner, PageDecoder::new()),
1223+
pg_rd : FramedRead::new(inner, PageDecoder::new(pg_parse_opts)),
11681224
}
11691225
}
11701226
}
@@ -1179,7 +1235,19 @@ pub mod async_api {
11791235
/// from other runtimes, and implementing a Tokio `AsyncRead`
11801236
/// compatibility layer oneself is not desired.
11811237
pub fn new_compat(inner :T) -> Self {
1182-
Self::new(inner.compat())
1238+
Self::new_compat_with_page_parse_opts(inner, PageParsingOptions::default())
1239+
}
1240+
1241+
/// Wraps the specified futures_io `AsyncRead` into an Ogg packet
1242+
/// reader, using the specified options for parsing Ogg pages.
1243+
///
1244+
/// This crate uses Tokio internally, so a wrapper that may have
1245+
/// some performance cost will be used. Therefore, this constructor
1246+
/// is to be used only when dealing with `AsyncRead` implementations
1247+
/// from other runtimes, and implementing a Tokio `AsyncRead`
1248+
/// compatibility layer oneself is not desired.
1249+
pub fn new_compat_with_page_parse_opts(inner :T, pg_parse_opts :impl Into<Arc<PageParsingOptions>>) -> Self {
1250+
Self::new_with_page_parse_opts(inner.compat(), pg_parse_opts)
11831251
}
11841252
}
11851253

0 commit comments

Comments
 (0)