mirror of
https://github.com/storytold/soundcraft.git
synced 2026-11-09 20:00:25 -06:00
address review: check the sample budget before reading, keep the decoder from skipping a refused sample, stop on a failed read
This commit is contained in:
@@ -380,6 +380,11 @@ impl Movie {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Size the sample table declares for `sample` (0 when there is none).
|
||||
fn declared_size(&self, sample: usize) -> usize {
|
||||
self.mp4.tracks.get(self.track).and_then(|t| t.samples.get(sample)).map_or(0, |s| s.size as usize)
|
||||
}
|
||||
|
||||
fn read(&self, sample: usize) -> Result<Vec<u8>> {
|
||||
let t = self.mp4.tracks.get(self.track).ok_or(VideoError::NoVideoTrack)?;
|
||||
let size = t.samples.get(sample).map_or(0, |s| s.size);
|
||||
@@ -502,17 +507,21 @@ impl Movie {
|
||||
let s = st.next;
|
||||
st.next += 1;
|
||||
let pts = self.rank.get(s).copied().unwrap_or(0) as i64;
|
||||
// The declared size is checked before the read allocates it. An error leaves no decoder
|
||||
// behind that has stepped past this sample: the next request starts from its keyframe.
|
||||
if fed.saturating_add(self.declared_size(s)) > Self::MAX_REQUEST_BYTES {
|
||||
self.h264 = None;
|
||||
return Err(VideoError::Decode(format!("frame {index} needs more than {} MiB of samples", Self::MAX_REQUEST_BYTES >> 20)));
|
||||
}
|
||||
let data = match self.read(s) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
last_err = Some(e.to_string());
|
||||
continue;
|
||||
// a failing source is not probed sample after sample (each probe may allocate a whole sample)
|
||||
self.h264 = None;
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
fed = fed.saturating_add(data.len());
|
||||
if fed > Self::MAX_REQUEST_BYTES {
|
||||
return Err(VideoError::Decode(format!("frame {index} needs more than {} MiB of samples", Self::MAX_REQUEST_BYTES >> 20)));
|
||||
}
|
||||
let Some(st) = self.h264.as_mut() else { break };
|
||||
match st.dec.decode(&data, pts) {
|
||||
Ok(p) => p,
|
||||
|
||||
@@ -167,15 +167,23 @@ fn one_frame_request_reads_a_bounded_amount_of_samples() {
|
||||
// past what one request may feed the decoder.
|
||||
let mut bytes = h264_mp4(17, 17).unwrap();
|
||||
let at = bytes.windows(4).position(|w| w == b"stsz").unwrap();
|
||||
assert_eq!(bytes[at + 8..at + 12], [0; 4], "one size per sample");
|
||||
for i in 0..17 {
|
||||
let field = at + 16 + 4 * i;
|
||||
bytes[field..field + 4].copy_from_slice(&(32u32 << 20).to_be_bytes());
|
||||
if bytes[at + 8..at + 12] == [0; 4] {
|
||||
// one size per sample
|
||||
for i in 0..17 {
|
||||
let field = at + 16 + 4 * i;
|
||||
bytes[field..field + 4].copy_from_slice(&(32u32 << 20).to_be_bytes());
|
||||
}
|
||||
} else {
|
||||
// the writer stored one size for all the (equally large) samples
|
||||
bytes[at + 8..at + 12].copy_from_slice(&(32u32 << 20).to_be_bytes());
|
||||
}
|
||||
let src = Arc::new(PaddedSource { bytes, len: 1 << 30, largest: AtomicUsize::new(0) });
|
||||
let mut m = Movie::open_source(src).unwrap();
|
||||
let r = m.frame(16);
|
||||
assert!(matches!(&r, Err(VideoError::Decode(e)) if e.contains("needs more than 512 MiB of samples")), "{r:?}");
|
||||
// the sample that was refused is not skipped: asking again fails the same way instead of decoding past it
|
||||
let again = m.frame(16);
|
||||
assert!(matches!(&again, Err(VideoError::Decode(e)) if e.contains("needs more than 512 MiB of samples")), "{again:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user