diff --git a/.cargo/config.in b/.cargo/config.in index 648ee6705aab..52818b6c49b0 100644 --- a/.cargo/config.in +++ b/.cargo/config.in @@ -17,6 +17,11 @@ git = "https://github.com/mozilla/neqo" replace-with = "vendored-sources" tag = "v0.2.4" +[source."https://github.com/mozilla/mp4parse-rust"] +git = "https://github.com/mozilla/mp4parse-rust" +replace-with = "vendored-sources" +rev = "0dc3e6e7c5371fe21f69b847f61c65fe6d6dc317" + [source."https://github.com/mozilla/application-services"] git = "https://github.com/mozilla/application-services" replace-with = "vendored-sources" diff --git a/Cargo.lock b/Cargo.lock index 8361b4a2e639..c35933ba9df7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2940,11 +2940,12 @@ dependencies = [ [[package]] name = "mp4parse" version = "0.11.4" +source = "git+https://github.com/mozilla/mp4parse-rust?rev=0dc3e6e7c5371fe21f69b847f61c65fe6d6dc317#0dc3e6e7c5371fe21f69b847f61c65fe6d6dc317" dependencies = [ "bitreader", "byteorder", + "hashbrown", "log", - "mp4parse_fallible", "num-traits", "static_assertions", ] @@ -2955,7 +2956,8 @@ version = "0.1.0" [[package]] name = "mp4parse_capi" -version = "0.11.2" +version = "0.11.4" +source = "git+https://github.com/mozilla/mp4parse-rust?rev=0dc3e6e7c5371fe21f69b847f61c65fe6d6dc317#0dc3e6e7c5371fe21f69b847f61c65fe6d6dc317" dependencies = [ "byteorder", "log", @@ -2963,12 +2965,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "mp4parse_fallible" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704f773471ac3e7110427b6bdf93184932b19319c9b7717688da5424e519b10a" - [[package]] name = "msdos_time" version = "0.1.6" diff --git a/media/mp4parse-rust/README.md b/media/mp4parse-rust/README.md new file mode 100644 index 000000000000..956135f042c6 --- /dev/null +++ b/media/mp4parse-rust/README.md @@ -0,0 +1,74 @@ +This directory exists to provide the C++ interface to the mp4parse-rust code. +The code itself is hosted at https://github.com/mozilla/mp4parse-rust and +vendored into the /third_party/rust directory, so the only things here are the +moz.build file which specifies how the dynamically generated bindings header +should be generated and mp4parse.h, the header which consumers should include. +It includes the dynamically-generated bindings header as well as any +additional support code for FFI. + +# Updating the version from the github repository + +1. In /toolkit/library/rust/shared/Cargo.toml, Set the `rev` attribute of the + `mp4parse_capi` dependency to the revision you want to use. +2. Run `mach vendor rust` (`--build-peers-said-large-imports-were-ok` may be + necessary since the `mp4parse` crate's lib.rs is quite large). +3. Verify the expected changes in /third_party/rust. +4. Build, run try, etc. + +NOTE: Git has no concept of checking out a subdirectory, so `cargo` will +search the whole repository to find the crate. Because the `mp4parse_capi` +depends on the `mp4parse` crate in the same repository via a relative path, +both crates will be vendored into /third_party/rust and should be part of the +same revision of mp4parse-rust. + +# Developing changes to mp4parse-rust crates + +Before committing changes to the github repository, it's a good idea to test +them out in the context of mozilla-central. There are a number of ways to +achieve this with various trade-offs, but here are the two I recommend. Both +start the same way: + +1. `git clone https://github.com/mozilla/mp4parse-rust` somewhere outside + mozilla-central. For example: /Users/your_username/src/mp4parse-rust. + +## For rapid iteration on local, uncommitted changes + +2. In /toolkit/library/rust/shared/Cargo.toml, change the `mp4parse_capi` + dependency to + ``` + mp4parse_capi = { path = "/Users/your_username/src/mp4parse-rust/mp4parse_capi" } + ``` +3. Run `mach vendor rust`; the code in third_party/rust/mp4parse_capi and + third_party/rust/mp4parse will be removed, indicating the code in your + local checkout is being used instead. +4. In the moz.build in this directory, in `ffi_generated.inputs`, change + '/third_party/rust/mp4parse_capi' to + '//Users/your_username/src/mp4parse-rust/mp4parse_capi'. Note the + double-slash, it's required to reference paths outside mozilla-central. +5. Build and run the code or tests normally to exercise the code in + /Users/your_username/src/mp4parse-rust. + +This is a fast way to try experiment with the rust code, but because it exists +outside the mozilla-central tree, it cannot be used with try. + +## For validation of local, committed changes + +2. In /toolkit/library/rust/shared/Cargo.toml, change the `mp4parse_capi` + dependency to + ``` + mp4parse_capi = { git = "file:///Users/your_username/src/mp4parse-rust" } + ``` +3. Run `mach vendor rust`; the local, committed code will be copied into + third_party/rust/mp4parse_capi and third_party/rust/mp4parse. Confirm the + diff is what you expect. +4. Unlike above, no changes to moz.build are necessary; if locally changed, + make sure to revert. +5. Build and run the code or tests normally to exercise the code in + /Users/your_username/src/mp4parse-rust. This can include try runs, but if + you make any additional changes, you must be sure to commit them in your + local git checkout of mp4parse-rust and re-run `mach vendor rust`. + +This is a more thorough way to test local changes to the rust code since try +is available, but it's slower than the `path` dependency approach above +because every change must be committed and copied into the mozilla-central +tree with `mach vendor rust`. diff --git a/media/mp4parse-rust/moz.build b/media/mp4parse-rust/moz.build index 82717ec25036..9f26517693eb 100644 --- a/media/mp4parse-rust/moz.build +++ b/media/mp4parse-rust/moz.build @@ -21,7 +21,7 @@ if CONFIG['COMPILE_ENVIRONMENT']: ffi_generated = GENERATED_FILES['mp4parse_ffi_generated.h'] ffi_generated.script = '/build/RunCbindgen.py:generate' ffi_generated.inputs = [ - '/media/mp4parse-rust/mp4parse_capi', + '/third_party/rust/mp4parse_capi', ] FINAL_LIBRARY = 'xul' diff --git a/media/mp4parse-rust/mp4parse-cargo.patch b/media/mp4parse-rust/mp4parse-cargo.patch deleted file mode 100644 index e169b979244b..000000000000 --- a/media/mp4parse-rust/mp4parse-cargo.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/media/mp4parse-rust/_upstream/mp4parse/mp4parse_capi/Cargo.toml b/media/mp4parse-rust/mp4parse_capi/Cargo.toml -index 2c606b4..895739f 100644 ---- a/media/mp4parse-rust/_upstream/mp4parse/mp4parse_capi/Cargo.toml -+++ b/media/mp4parse-rust/mp4parse_capi/Cargo.toml -@@ -18,8 +18,7 @@ exclude = [ - "*.mp4", - ] - --[lib] --crate-type = ["lib", "cdylib"] -+build = false # See bug 1611431 - Generate mp4parse-rust bindings as part of mach build - - [badges] - travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } -@@ -33,9 +32,6 @@ num-traits = "0.2.0" - [dev-dependencies] - env_logger = "0.7.1" - --[build-dependencies] --cbindgen = "0.5.2" -- - [features] - # Enable mp4parse_fallible to use fallible memory allocation rather than - # panicking on OOM. Note that this is only safe within Gecko where the system diff --git a/media/mp4parse-rust/mp4parse.h b/media/mp4parse-rust/mp4parse.h index 86e1aadbaf77..220030880900 100644 --- a/media/mp4parse-rust/mp4parse.h +++ b/media/mp4parse-rust/mp4parse.h @@ -2,10 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -#ifndef mozilla_media_mp4parse_rust_mp4parse_h -#define mozilla_media_mp4parse_rust_mp4parse_h +#ifndef mp4parse_rust_mp4parse_h +#define mp4parse_rust_mp4parse_h -#include "mp4parse_ffi_generated.h" // prepend mozilla/media when we namespace this +#include "mp4parse_ffi_generated.h" // prepend mozilla/media when we namespace this // Add any non-generated support code here diff --git a/media/mp4parse-rust/mp4parse/tests/bipbop-cenc-audioinit.mp4 b/media/mp4parse-rust/mp4parse/tests/bipbop-cenc-audioinit.mp4 deleted file mode 100644 index b827aa49aac4..000000000000 Binary files a/media/mp4parse-rust/mp4parse/tests/bipbop-cenc-audioinit.mp4 and /dev/null differ diff --git a/media/mp4parse-rust/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 b/media/mp4parse-rust/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 deleted file mode 100644 index 5db21d091bc4..000000000000 Binary files a/media/mp4parse-rust/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 and /dev/null differ diff --git a/media/mp4parse-rust/mp4parse/tests/bipbop_cbcs_audio_init.mp4 b/media/mp4parse-rust/mp4parse/tests/bipbop_cbcs_audio_init.mp4 deleted file mode 100644 index 795223d5edf7..000000000000 Binary files a/media/mp4parse-rust/mp4parse/tests/bipbop_cbcs_audio_init.mp4 and /dev/null differ diff --git a/media/mp4parse-rust/mp4parse/tests/bipbop_cbcs_video_init.mp4 b/media/mp4parse-rust/mp4parse/tests/bipbop_cbcs_video_init.mp4 deleted file mode 100644 index 2f8f235a9b4b..000000000000 Binary files a/media/mp4parse-rust/mp4parse/tests/bipbop_cbcs_video_init.mp4 and /dev/null differ diff --git a/media/mp4parse-rust/mp4parse/tests/metadata.mp4 b/media/mp4parse-rust/mp4parse/tests/metadata.mp4 deleted file mode 100644 index c09ad1374a4d..000000000000 Binary files a/media/mp4parse-rust/mp4parse/tests/metadata.mp4 and /dev/null differ diff --git a/media/mp4parse-rust/mp4parse/tests/metadata_gnre.mp4 b/media/mp4parse-rust/mp4parse/tests/metadata_gnre.mp4 deleted file mode 100644 index 91c3a4d1c1f7..000000000000 Binary files a/media/mp4parse-rust/mp4parse/tests/metadata_gnre.mp4 and /dev/null differ diff --git a/media/mp4parse-rust/mp4parse/tests/minimal.mp4 b/media/mp4parse-rust/mp4parse/tests/minimal.mp4 deleted file mode 100644 index 9fe1e6722417..000000000000 Binary files a/media/mp4parse-rust/mp4parse/tests/minimal.mp4 and /dev/null differ diff --git a/media/mp4parse-rust/mp4parse/tests/tiny_av1.mp4 b/media/mp4parse-rust/mp4parse/tests/tiny_av1.mp4 deleted file mode 100644 index 3778cf708093..000000000000 Binary files a/media/mp4parse-rust/mp4parse/tests/tiny_av1.mp4 and /dev/null differ diff --git a/media/mp4parse-rust/update-rust.sh b/media/mp4parse-rust/update-rust.sh deleted file mode 100755 index 55ea35418f81..000000000000 --- a/media/mp4parse-rust/update-rust.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -# Script to update mp4parse-rust sources to latest upstream - -set -e - -# Default version. -VER="63ca8c6bde27b39dea87fb15e2922a23cb38c8df" - -# Accept version or commit from the command line. -if test -n "$1"; then - VER=$1 -fi - -echo "Fetching sources..." -rm -rf _upstream -git clone --recurse-submodules https://github.com/mozilla/mp4parse-rust _upstream/mp4parse - -pushd _upstream/mp4parse -git checkout ${VER} -echo "Verifying sources..." -cargo test -popd -rm -rf mp4parse -mkdir -p mp4parse/src -cp _upstream/mp4parse/mp4parse/Cargo.toml mp4parse/ -cp _upstream/mp4parse/mp4parse/src/*.rs mp4parse/src/ -mkdir -p mp4parse/tests -cp _upstream/mp4parse/mp4parse/tests/*.rs mp4parse/tests/ -cp _upstream/mp4parse/mp4parse/tests/*.mp4 mp4parse/tests/ -# Remove everything but the cbindgen.toml, since it's needed to configure the -# creation of the bindings as part of moz.build -find mp4parse_capi -not -name cbindgen.toml -delete -mkdir -p mp4parse_capi/src -cp _upstream/mp4parse/mp4parse_capi/Cargo.toml mp4parse_capi/ -cp _upstream/mp4parse/mp4parse_capi/src/*.rs mp4parse_capi/src/ - -echo "Applying patches..." -patch -p3 < mp4parse-cargo.patch - -echo "Cleaning up..." -rm -rf _upstream - -echo "Updating gecko Cargo.lock..." -pushd ../../toolkit/library/rust/ -cargo update --package mp4parse_capi -popd -pushd ../../toolkit/library/gtest/rust/ -cargo update --package mp4parse_capi -popd - -echo "Updated to ${VER}." diff --git a/third_party/rust/mp4parse/.cargo-checksum.json b/third_party/rust/mp4parse/.cargo-checksum.json new file mode 100644 index 000000000000..3ced0c9f50ee --- /dev/null +++ b/third_party/rust/mp4parse/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"107804fbf8f667fbad45e7dea9fa1bb32ce8ef5580b543a54455e678d7769708","src/boxes.rs":"623f948e69244db586d7cc1855e0a1828766cbe2d4998277d170047d0ae0fce0","src/fallible.rs":"836a36c2bc9803aead4bb24621e4fa6176c77b3752e69459a1f36555eb8bf2ec","src/lib.rs":"05d2e8523eab120bbc712adea9cd47979da3e95f54ff5816e6738ebeaf90afb2","src/macros.rs":"76c840f9299797527fe71aa5b378ffb01312767372b45cc62deddb19775400ae","src/tests.rs":"7c7f69051f8b8edcb994d3813bcca7277cc71bcfb0a6eaadbbdbb9a1f777ff88","tests/overflow.rs":"16b591d8def1a155b3b997622f6ea255536870d99c3d8f97c51755b77a50de3c","tests/public.rs":"eb02970aa69c31855936b4ec24dcf09b300fad274b620c879d9b6dfbcd89444c"},"package":null} \ No newline at end of file diff --git a/media/mp4parse-rust/mp4parse/Cargo.toml b/third_party/rust/mp4parse/Cargo.toml similarity index 72% rename from media/mp4parse-rust/mp4parse/Cargo.toml rename to third_party/rust/mp4parse/Cargo.toml index b38c6459f3de..83623eb4357b 100644 --- a/media/mp4parse-rust/mp4parse/Cargo.toml +++ b/third_party/rust/mp4parse/Cargo.toml @@ -18,6 +18,7 @@ repository = "https://github.com/mozilla/mp4parse-rust" # Avoid complaints about trying to package test files. exclude = [ "*.mp4", + "av1-avif/*" ] [badges] @@ -26,11 +27,18 @@ travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } [dependencies] byteorder = "1.2.1" bitreader = { version = "0.3.2" } +hashbrown = "0.7.1" num-traits = "0.2.0" -mp4parse_fallible = { version = "0.0.3", optional = true } log = "0.4" static_assertions = "1.1.0" [dev-dependencies] test-assembler = "0.1.2" env_logger = "0.7.1" +walkdir = "2.3.1" + +[features] +# Enable mp4parse_fallible to use fallible memory allocation rather than +# panicking on OOM. Note that this is only safe within Gecko where the system +# allocator has been globally overridden (see BMO 1457359). +mp4parse_fallible = [] diff --git a/media/mp4parse-rust/mp4parse/src/boxes.rs b/third_party/rust/mp4parse/src/boxes.rs similarity index 93% rename from media/mp4parse-rust/mp4parse/src/boxes.rs rename to third_party/rust/mp4parse/src/boxes.rs index 6efd83d4e0bb..1b7d8328138a 100644 --- a/media/mp4parse-rust/mp4parse/src/boxes.rs +++ b/third_party/rust/mp4parse/src/boxes.rs @@ -3,6 +3,16 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::fmt; +// To ensure we don't use stdlib allocating types by accident +#[allow(dead_code)] +struct Vec; +#[allow(dead_code)] +struct Box; +#[allow(dead_code)] +struct HashMap; +#[allow(dead_code)] +struct String; + macro_rules! box_database { ($($boxenum:ident $boxtype:expr),*,) => { #[derive(Clone, Copy, PartialEq)] @@ -42,24 +52,14 @@ macro_rules! box_database { #[derive(Default, PartialEq, Clone)] pub struct FourCC { - pub value: String, + pub value: [u8; 4], } impl From for FourCC { fn from(number: u32) -> FourCC { - let mut box_chars = Vec::new(); - for x in 0..4 { - let c = (number >> (x * 8) & 0x0000_00FF) as u8; - box_chars.push(c); + FourCC { + value: number.to_be_bytes(), } - box_chars.reverse(); - - let box_string = match String::from_utf8(box_chars) { - Ok(t) => t, - _ => String::from("null"), // error to retrieve fourcc - }; - - FourCC { value: box_string } } } @@ -70,23 +70,27 @@ impl From for FourCC { } } -impl<'a> From<&'a str> for FourCC { - fn from(v: &'a str) -> FourCC { - FourCC { - value: v.to_owned(), - } +impl From<[u8; 4]> for FourCC { + fn from(v: [u8; 4]) -> FourCC { + FourCC { value: v } } } impl fmt::Debug for FourCC { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.value) + match std::str::from_utf8(&self.value) { + Ok(s) => write!(f, "{}", s), + Err(_) => self.value.fmt(f), + } } } impl fmt::Display for FourCC { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.value) + match std::str::from_utf8(&self.value) { + Ok(s) => write!(f, "{}", s), + Err(_) => write!(f, "null"), + } } } diff --git a/third_party/rust/mp4parse/src/fallible.rs b/third_party/rust/mp4parse/src/fallible.rs new file mode 100644 index 000000000000..d0494448214b --- /dev/null +++ b/third_party/rust/mp4parse/src/fallible.rs @@ -0,0 +1,520 @@ +//! The types in this module are thin wrappers around the stdlib types to add +//! support for fallible allocation. Though the fallible allocator is only +//! enabled with the mp4parse_fallible feature, the API differences from the +//! stdlib types ensure that all operations which allocate return a Result. +//! For the most part, this simply means adding a Result return value to +//! functions which return nothing or a non-Result value. However, these types +//! implement some traits whose API cannot communicate failure, but which do +//! require allocation, so it is important that these wrapper types do not +//! implement these traits. +//! +//! Specifically, do not implement any of the following traits: +//! - Clone +//! - Extend +//! - From +//! - FromIterator +//! +//! This list may not be exhaustive. Exercise caution when implementing +//! any new traits to ensure they won't potentially allocate in a way that +//! can't return a Result to indicate allocation failure. + +#[cfg(feature = "mp4parse_fallible")] +extern "C" { + fn malloc(bytes: usize) -> *mut u8; + fn realloc(ptr: *mut u8, bytes: usize) -> *mut u8; +} + +use std::convert::TryInto as _; +use std::hash::Hash; +use std::io::Read; +use std::io::Take; +use std::iter::IntoIterator; +use BMFFBox; + +type Result = std::result::Result; + +pub trait TryRead { + fn try_read_to_end(&mut self, buf: &mut TryVec) -> Result; + + fn read_into_try_vec(&mut self) -> Result> { + let mut buf = TryVec::new(); + let _ = self.try_read_to_end(&mut buf)?; + Ok(buf) + } +} + +impl<'a, T: Read> TryRead for BMFFBox<'a, T> { + fn try_read_to_end(&mut self, buf: &mut TryVec) -> Result { + try_read_up_to(self, self.bytes_left(), buf) + } +} + +impl TryRead for Take { + /// With the `mp4parse_fallible` feature enabled, this function reserves the + /// upper limit of what `src` can generate before reading all bytes until EOF + /// in this source, placing them into buf. If the allocation is unsuccessful, + /// or reading from the source generates an error before reaching EOF, this + /// will return an error. Otherwise, it will return the number of bytes read. + /// + /// Since `Take::limit()` may return a value greater than the number of bytes + /// which can be read from the source, it's possible this function may fail + /// in the allocation phase even though allocating the number of bytes available + /// to read would have succeeded. In general, it is assumed that the callers + /// have accurate knowledge of the number of bytes of interest and have created + /// `src` accordingly. + /// + /// With the `mp4parse_fallible` feature disabled, this is essentially a wrapper + /// around `std::io::Read::read_to_end()`. + fn try_read_to_end(&mut self, buf: &mut TryVec) -> Result { + try_read_up_to(self, self.limit(), buf) + } +} + +fn try_read_up_to(src: &mut R, limit: u64, buf: &mut TryVec) -> Result { + let additional = limit.try_into()?; + buf.reserve(additional)?; + let bytes_read = src.read_to_end(&mut buf.inner)?; + Ok(bytes_read) +} + +/// TryBox is a thin wrapper around std::boxed::Box to provide support for +/// fallible allocation. +/// +/// See the `fallible` module documentation for more. +pub struct TryBox { + inner: std::boxed::Box, +} + +impl TryBox { + pub fn try_new(x: T) -> Result { + let inner; + + #[cfg(feature = "mp4parse_fallible")] + { + let size = std::mem::size_of::(); + let new_ptr = unsafe { malloc(size) as *mut T }; + + if new_ptr.is_null() { + return Err(super::Error::OutOfMemory); + } + + // If we did a simple assignment: *new_ptr = x, then the value + // pointed to by new_ptr would immediately be dropped, but + // that would cause an invalid memory access. Instead, we use + // replace() to avoid the immediate drop and forget() to + // ensure that drop never happens. + inner = unsafe { + std::mem::forget(std::mem::replace(&mut *new_ptr, x)); + Box::from_raw(new_ptr) + }; + } + #[cfg(not(feature = "mp4parse_fallible"))] + { + inner = Box::new(x); + } + + Ok(Self { inner }) + } + + pub fn into_raw(b: TryBox) -> *mut T { + Box::into_raw(b.inner) + } + + /// # Safety + /// + /// See std::boxed::from_raw + pub unsafe fn from_raw(raw: *mut T) -> Self { + Self { + inner: Box::from_raw(raw), + } + } +} + +/// TryHashMap is a thin wrapper around the hashbrown HashMap to provide +/// support for fallible allocation. This is the same library that stdlib +/// currently uses, but we use it directly since it provides try_reserve(). +/// +/// See the `fallible` module documentation for more. +#[derive(Default)] +pub struct TryHashMap { + inner: hashbrown::hash_map::HashMap, +} + +impl TryHashMap +where + K: Eq + Hash, +{ + pub fn get(&self, k: &Q) -> Option<&V> + where + K: std::borrow::Borrow, + Q: Hash + Eq, + { + self.inner.get(k) + } + + pub fn insert(&mut self, k: K, v: V) -> Result> { + self.reserve(if self.inner.capacity() == 0 { 4 } else { 1 })?; + Ok(self.inner.insert(k, v)) + } + + fn reserve(&mut self, additional: usize) -> Result<()> { + #[cfg(feature = "mp4parse_fallible")] + { + self.inner + .try_reserve(additional) + .map_err(|_| super::Error::OutOfMemory) + } + #[cfg(not(feature = "mp4parse_fallible"))] + { + self.inner.reserve(additional); + Ok(()) + } + } +} + +#[test] +#[cfg(feature = "mp4parse_fallible")] +fn tryhashmap_oom() { + match TryHashMap::::default().reserve(std::usize::MAX) { + Ok(_) => panic!("it should be OOM"), + _ => (), + } +} + +#[derive(Default, PartialEq)] +pub struct TryVec { + inner: std::vec::Vec, +} + +impl std::fmt::Debug for TryVec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.inner) + } +} + +/// TryVec is a thin wrapper around std::vec::Vec to provide support for +/// fallible allocation. +/// +/// See the `fallible` module documentation for more. +impl TryVec { + pub fn new() -> Self { + Self { inner: Vec::new() } + } + + pub fn with_capacity(capacity: usize) -> Result { + let mut v = Self::new(); + v.reserve(capacity)?; + Ok(v) + } + + pub fn append(&mut self, other: &mut Self) -> Result<()> { + self.reserve(other.inner.len())?; + self.inner.append(&mut other.inner); + Ok(()) + } + + pub fn as_mut_slice(&mut self) -> &mut [T] { + self + } + + pub fn as_slice(&self) -> &[T] { + self + } + + pub fn clear(&mut self) { + self.inner.clear() + } + + #[cfg(test)] + pub fn into_inner(self) -> Vec { + self.inner + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn iter_mut(&mut self) -> IterMut { + IterMut { + inner: self.inner.iter_mut(), + } + } + + pub fn iter(&self) -> Iter { + Iter { + inner: self.inner.iter(), + } + } + + pub fn pop(&mut self) -> Option { + self.inner.pop() + } + + pub fn push(&mut self, value: T) -> Result<()> { + self.reserve(if self.inner.capacity() == 0 { 4 } else { 1 })?; + self.inner.push(value); + Ok(()) + } + + fn reserve(&mut self, additional: usize) -> Result<()> { + #[cfg(feature = "mp4parse_fallible")] + { + let available = self + .inner + .capacity() + .checked_sub(self.inner.len()) + .expect("capacity >= len"); + if additional > available { + let increase = additional + .checked_sub(available) + .expect("additional > available"); + let new_cap = self + .inner + .capacity() + .checked_add(increase) + .ok_or(super::Error::OutOfMemory)?; + self.try_extend(new_cap)?; + debug_assert!(self.inner.capacity() == new_cap); + } + Ok(()) + } + #[cfg(not(feature = "mp4parse_fallible"))] + { + self.inner.reserve(additional); + Ok(()) + } + } + + pub fn resize_with(&mut self, new_len: usize, f: F) -> Result<()> + where + F: FnMut() -> T, + { + self.reserve(new_len)?; + self.inner.resize_with(new_len, f); + Ok(()) + } + + #[cfg(feature = "mp4parse_fallible")] + fn try_extend(&mut self, new_cap: usize) -> Result<()> { + let old_ptr = self.as_mut_ptr(); + let old_len = self.inner.len(); + + let old_cap: usize = self.inner.capacity(); + + if old_cap >= new_cap { + return Ok(()); + } + + let new_size_bytes = new_cap + .checked_mul(std::mem::size_of::()) + .ok_or(super::Error::OutOfMemory)?; + + let new_ptr = unsafe { + if old_cap == 0 { + malloc(new_size_bytes) + } else { + realloc(old_ptr as *mut u8, new_size_bytes) + } + }; + + if new_ptr.is_null() { + return Err(super::Error::OutOfMemory); + } + + let new_vec = unsafe { Vec::from_raw_parts(new_ptr as *mut T, old_len, new_cap) }; + + std::mem::forget(std::mem::replace(&mut self.inner, new_vec)); + Ok(()) + } +} + +impl TryVec> { + pub fn concat(&self) -> Result> { + let size = self.iter().map(|v| v.inner.len()).sum(); + let mut result = TryVec::with_capacity(size)?; + for v in self.iter() { + result.extend_from_slice(&v.inner)?; + } + Ok(result) + } +} + +impl TryVec { + pub fn extend_from_slice(&mut self, other: &[T]) -> Result<()> { + self.reserve(other.len())?; + self.inner.extend_from_slice(other); + Ok(()) + } +} + +#[test] +#[cfg(feature = "mp4parse_fallible")] +fn oom() { + let mut vec: TryVec = TryVec::new(); + match vec.reserve(std::usize::MAX) { + Ok(_) => panic!("it should be OOM"), + _ => (), + } +} + +#[test] +fn try_reserve() { + let mut vec: TryVec<_> = vec![1].into(); + let old_cap = vec.inner.capacity(); + let new_cap = old_cap + 1; + vec.reserve(new_cap).unwrap(); + assert!(vec.inner.capacity() >= new_cap); +} + +#[test] +fn try_reserve_idempotent() { + let mut vec: TryVec<_> = vec![1].into(); + let old_cap = vec.inner.capacity(); + let new_cap = old_cap + 1; + vec.reserve(new_cap).unwrap(); + let cap_after_reserve = vec.inner.capacity(); + vec.reserve(new_cap).unwrap(); + assert_eq!(cap_after_reserve, vec.inner.capacity()); +} + +#[test] +#[cfg(feature = "mp4parse_fallible")] +fn capacity_overflow() { + let mut vec: TryVec<_> = vec![1].into(); + match vec.reserve(std::usize::MAX) { + Ok(_) => panic!("capacity calculation should overflow"), + _ => (), + } +} + +#[test] +fn extend_from_slice() { + let mut vec: TryVec = b"foo".as_ref().try_into().unwrap(); + vec.extend_from_slice(b"bar").unwrap(); + assert_eq!(vec, b"foobar".as_ref()); +} + +impl IntoIterator for TryVec { + type Item = T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl<'a, T> IntoIterator for &'a TryVec { + type Item = &'a T; + type IntoIter = std::slice::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.inner.iter() + } +} + +impl std::io::Write for TryVec { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.extend_from_slice(buf)?; + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl PartialEq> for TryVec { + fn eq(&self, other: &Vec) -> bool { + self.inner.eq(other) + } +} + +impl<'a, T: PartialEq> PartialEq<&'a [T]> for TryVec { + fn eq(&self, other: &&[T]) -> bool { + self.inner.eq(other) + } +} + +impl PartialEq<&str> for TryVec { + fn eq(&self, other: &&str) -> bool { + self.as_slice() == other.as_bytes() + } +} + +impl std::convert::AsRef<[u8]> for TryVec { + fn as_ref(&self) -> &[u8] { + self.inner.as_ref() + } +} + +impl std::convert::From> for TryVec { + fn from(value: Vec) -> Self { + Self { inner: value } + } +} + +impl std::convert::TryFrom<&[T]> for TryVec { + type Error = super::Error; + + fn try_from(value: &[T]) -> Result { + let mut v = Self::new(); + v.extend_from_slice(value)?; + Ok(v) + } +} + +impl std::convert::TryFrom<&str> for TryVec { + type Error = super::Error; + + fn try_from(value: &str) -> Result { + let mut v = Self::new(); + v.extend_from_slice(value.as_bytes())?; + Ok(v) + } +} + +impl std::ops::Deref for TryVec { + type Target = [T]; + + fn deref(&self) -> &[T] { + self.inner.deref() + } +} + +impl std::ops::DerefMut for TryVec { + fn deref_mut(&mut self) -> &mut [T] { + self.inner.deref_mut() + } +} + +pub struct Iter<'a, T> { + inner: std::slice::Iter<'a, T>, +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + self.inner.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +pub struct IterMut<'a, T> { + inner: std::slice::IterMut<'a, T>, +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = &'a mut T; + + fn next(&mut self) -> Option { + self.inner.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} diff --git a/media/mp4parse-rust/mp4parse/src/lib.rs b/third_party/rust/mp4parse/src/lib.rs similarity index 91% rename from media/mp4parse-rust/mp4parse/src/lib.rs rename to third_party/rust/mp4parse/src/lib.rs index b4af3d8a8751..251655fe6dab 100644 --- a/media/mp4parse-rust/mp4parse/src/lib.rs +++ b/third_party/rust/mp4parse/src/lib.rs @@ -12,24 +12,21 @@ extern crate byteorder; extern crate num_traits; use bitreader::{BitReader, ReadInto}; use byteorder::{ReadBytesExt, WriteBytesExt}; +use fallible::TryRead as _; use num_traits::Num; use std::convert::{TryFrom, TryInto as _}; use std::io::Cursor; use std::io::{Read, Take}; use std::ops::{Range, RangeFrom}; -#[cfg(feature = "mp4parse_fallible")] -extern crate mp4parse_fallible; - -#[cfg(feature = "mp4parse_fallible")] -use mp4parse_fallible::FallibleVec; - #[macro_use] mod macros; mod boxes; use boxes::{BoxType, FourCC}; +mod fallible; + // Unit tests. #[cfg(test)] mod tests; @@ -122,72 +119,24 @@ impl<'a, T: Read> Read for OffsetReader<'a, T> { } } -// TODO: vec_push() needs to be replaced when Rust supports fallible memory -// allocation in raw_vec. -#[allow(unreachable_code)] -pub fn vec_push(vec: &mut Vec, val: T) -> std::result::Result<(), ()> { - #[cfg(feature = "mp4parse_fallible")] - { - return FallibleVec::try_push(vec, val); - } +// TODO: all the functions that rely on the mp4parse_fallible feature need to +// be updated when Rust supports fallible memory allocation in raw_vec. +// See https://github.com/mozilla/mp4parse-rust/issues/146 - vec.push(val); - Ok(()) -} +pub type TryVec = fallible::TryVec; +pub type TryString = TryVec; +pub type TryHashMap = fallible::TryHashMap; +pub type TryBox = fallible::TryBox; -fn vec_with_capacity(capacity: usize) -> std::result::Result, ()> { - #[cfg(feature = "mp4parse_fallible")] - { - let mut v = Vec::new(); - FallibleVec::try_reserve(&mut v, capacity)?; - Ok(v) - } - #[cfg(not(feature = "mp4parse_fallible"))] - { - Ok(Vec::with_capacity(capacity)) - } -} - -pub fn extend_from_slice(vec: &mut Vec, other: &[T]) -> std::result::Result<(), ()> { - #[cfg(feature = "mp4parse_fallible")] - { - FallibleVec::try_extend_from_slice(vec, other) - } - #[cfg(not(feature = "mp4parse_fallible"))] - { - vec.extend_from_slice(other); - Ok(()) - } -} - -/// With the `mp4parse_fallible` feature enabled, this function reserves the -/// upper limit of what `src` can generate before reading all bytes until EOF -/// in this source, placing them into buf. If the allocation is unsuccessful, -/// or reading from the source generates an error before reaching EOF, this -/// will return an error. Otherwise, it will return the number of bytes read. -/// -/// Since `src.limit()` may return a value greater than the number of bytes -/// which can be read from the source, it's possible this function may fail -/// in the allocation phase even though allocating the number of bytes available -/// to read would have succeeded. In general, it is assumed that the callers -/// have accurate knowledge of the number of bytes of interest and have created -/// `src` accordingly. -/// -/// With the `mp4parse_fallible` feature disabled, this is wrapper around -/// `std::io::Read::read_to_end()`. -fn read_to_end(src: &mut Take, buf: &mut Vec) -> std::result::Result { - #[cfg(feature = "mp4parse_fallible")] - { - let limit: usize = src.limit().try_into().map_err(|_| ())?; - FallibleVec::try_reserve(buf, limit)?; - let bytes_read = src.read_to_end(buf).map_err(|_| ())?; - Ok(bytes_read) - } - #[cfg(not(feature = "mp4parse_fallible"))] - { - src.read_to_end(buf).map_err(|_| ()) - } -} +// To ensure we don't use stdlib allocating types by accident +#[allow(dead_code)] +struct Vec; +#[allow(dead_code)] +struct Box; +#[allow(dead_code)] +struct HashMap; +#[allow(dead_code)] +struct String; /// Describes parser failures. /// @@ -209,6 +158,14 @@ pub enum Error { OutOfMemory, } +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for Error {} + impl From for Error { fn from(_: bitreader::BitReaderError) -> Error { Error::InvalidData("invalid data") @@ -236,9 +193,15 @@ impl From for Error { } } -impl From<()> for Error { - fn from(_: ()) -> Error { - Error::OutOfMemory +impl From for std::io::Error { + fn from(err: Error) -> Self { + let kind = match err { + Error::InvalidData(_) => std::io::ErrorKind::InvalidData, + Error::UnexpectedEOF => std::io::ErrorKind::UnexpectedEof, + Error::Io(io_err) => return io_err, + _ => std::io::ErrorKind::Other, + }; + Self::new(kind, err) } } @@ -268,7 +231,7 @@ struct BoxHeader { struct FileTypeBox { major_brand: FourCC, minor_version: u32, - compatible_brands: Vec, + compatible_brands: TryVec, } /// Movie header box 'mvhd'. @@ -305,7 +268,7 @@ pub struct TrackHeaderBox { /// Edit list box 'elst' #[derive(Debug)] struct EditListBox { - edits: Vec, + edits: TryVec, } #[derive(Debug)] @@ -326,19 +289,19 @@ struct MediaHeaderBox { // Chunk offset box 'stco' or 'co64' #[derive(Debug)] pub struct ChunkOffsetBox { - pub offsets: Vec, + pub offsets: TryVec, } // Sync sample box 'stss' #[derive(Debug)] pub struct SyncSampleBox { - pub samples: Vec, + pub samples: TryVec, } // Sample to chunk box 'stsc' #[derive(Debug)] pub struct SampleToChunkBox { - pub samples: Vec, + pub samples: TryVec, } #[derive(Debug)] @@ -352,13 +315,13 @@ pub struct SampleToChunk { #[derive(Debug)] pub struct SampleSizeBox { pub sample_size: u32, - pub sample_sizes: Vec, + pub sample_sizes: TryVec, } // Time to sample box 'stts' #[derive(Debug)] pub struct TimeToSampleBox { - pub samples: Vec, + pub samples: TryVec, } #[repr(C)] @@ -382,7 +345,7 @@ pub struct TimeOffset { #[derive(Debug)] pub struct CompositionOffsetBox { - pub samples: Vec, + pub samples: TryVec, } // Handler reference box 'hdlr' @@ -394,10 +357,10 @@ struct HandlerBox { // Sample description box 'stsd' #[derive(Debug)] pub struct SampleDescriptionBox { - pub descriptions: Vec, + pub descriptions: TryVec, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum SampleEntry { Audio(AudioSampleEntry), Video(VideoSampleEntry), @@ -405,19 +368,19 @@ pub enum SampleEntry { } #[allow(non_camel_case_types)] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] pub struct ES_Descriptor { pub audio_codec: CodecType, pub audio_object_type: Option, pub extended_audio_object_type: Option, pub audio_sample_rate: Option, pub audio_channel_count: Option, - pub codec_esds: Vec, - pub decoder_specific_data: Vec, // Data in DECODER_SPECIFIC_TAG + pub codec_esds: TryVec, + pub decoder_specific_data: TryVec, // Data in DECODER_SPECIFIC_TAG } #[allow(non_camel_case_types)] -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum AudioCodecSpecific { ES_Descriptor(ES_Descriptor), FLACSpecificBox(FLACSpecificBox), @@ -427,7 +390,7 @@ pub enum AudioCodecSpecific { LPCM, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct AudioSampleEntry { pub codec_type: CodecType, data_reference_index: u16, @@ -435,30 +398,30 @@ pub struct AudioSampleEntry { pub samplesize: u16, pub samplerate: f64, pub codec_specific: AudioCodecSpecific, - pub protection_info: Vec, + pub protection_info: TryVec, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum VideoCodecSpecific { - AVCConfig(Vec), + AVCConfig(TryVec), VPxConfig(VPxConfigBox), AV1Config(AV1ConfigBox), - ESDSConfig(Vec), + ESDSConfig(TryVec), } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct VideoSampleEntry { pub codec_type: CodecType, data_reference_index: u16, pub width: u16, pub height: u16, pub codec_specific: VideoCodecSpecific, - pub protection_info: Vec, + pub protection_info: TryVec, } /// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9). The meaning of each /// field is covered in detail in "VP Codec ISO Media File Format Binding". -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct VPxConfigBox { /// An integer that specifies the VP codec profile. profile: u8, @@ -481,10 +444,10 @@ pub struct VPxConfigBox { /// (e.g. 16-235 for 8 bit sample depth); 1 = full range (e.g. 0-255 for 8-bit sample depth). video_full_range_flag: bool, /// This is not used for VP8 and VP9 . Intended for binary codec initialization data. - pub codec_init: Vec, + pub codec_init: TryVec, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct AV1ConfigBox { pub profile: u8, pub level: u8, @@ -496,31 +459,31 @@ pub struct AV1ConfigBox { pub chroma_sample_position: u8, pub initial_presentation_delay_present: bool, pub initial_presentation_delay_minus_one: u8, - pub config_obus: Vec, + pub config_obus: TryVec, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct FLACMetadataBlock { pub block_type: u8, - pub data: Vec, + pub data: TryVec, } /// Represents a FLACSpecificBox 'dfLa' -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct FLACSpecificBox { version: u8, - pub blocks: Vec, + pub blocks: TryVec, } -#[derive(Debug, Clone)] +#[derive(Debug)] struct ChannelMappingTable { stream_count: u8, coupled_count: u8, - channel_mapping: Vec, + channel_mapping: TryVec, } /// Represent an OpusSpecificBox 'dOps' -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct OpusSpecificBox { pub version: u8, output_channel_count: u8, @@ -532,10 +495,10 @@ pub struct OpusSpecificBox { } /// Represent an ALACSpecificBox 'alac' -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ALACSpecificBox { version: u8, - pub data: Vec, + pub data: TryVec, } #[derive(Debug)] @@ -543,12 +506,12 @@ pub struct MovieExtendsBox { pub fragment_duration: Option, } -pub type ByteData = Vec; +pub type ByteData = TryVec; #[derive(Debug, Default)] pub struct ProtectionSystemSpecificHeaderBox { pub system_id: ByteData, - pub kid: Vec, + pub kid: TryVec, pub data: ByteData, // The entire pssh box (include header) required by Gecko. @@ -561,21 +524,21 @@ pub struct SchemeTypeBox { pub scheme_version: u32, } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default)] pub struct TrackEncryptionBox { pub is_encrypted: u8, pub iv_size: u8, - pub kid: Vec, + pub kid: TryVec, // Members for pattern encryption schemes pub crypt_byte_block_count: Option, pub skip_byte_block_count: Option, - pub constant_iv: Option>, + pub constant_iv: Option>, // End pattern encryption scheme members } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default)] pub struct ProtectionSchemeInfoBox { - pub code_name: String, + pub code_name: TryString, pub scheme_type: Option, pub tenc: Option, } @@ -583,7 +546,7 @@ pub struct ProtectionSchemeInfoBox { /// Represents a userdata box 'udta'. /// Currently, only the metadata atom 'meta' /// is parsed. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default)] pub struct UserdataBox { pub meta: Option, } @@ -593,12 +556,12 @@ pub struct UserdataBox { /// 'udta.meta.ilst' may only have either a /// standard genre box 'gnre' or a custom /// genre box '©gen', but never both at once. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, PartialEq)] pub enum Genre { /// A standard ID3v1 numbered genre. StandardGenre(u8), /// Any custom genre string. - CustomGenre(String), + CustomGenre(TryString), } /// Represents the contents of a 'stik' @@ -641,24 +604,24 @@ pub enum AdvisoryRating { /// Represents the contents of 'ilst' atoms within /// a metadata box 'meta', parsed as iTunes metadata using /// the conventional tags. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default)] pub struct MetadataBox { /// The album name, '©alb' - pub album: Option, + pub album: Option, /// The artist name '©art' or '©ART' - pub artist: Option, + pub artist: Option, /// The album artist 'aART' - pub album_artist: Option, + pub album_artist: Option, /// Track comments '©cmt' - pub comment: Option, + pub comment: Option, /// The date or year field '©day' /// /// This is stored as an arbitrary string, /// and may not necessarily be in a valid date /// format. - pub year: Option, + pub year: Option, /// The track title '©nam' - pub title: Option, + pub title: Option, /// The track genre '©gen' or 'gnre'. pub genre: Option, /// The track number 'trkn'. @@ -672,15 +635,15 @@ pub struct MetadataBox { /// stored in 'disk' pub total_discs: Option, /// The composer of the track '©wrt' - pub composer: Option, + pub composer: Option, /// The encoder used to create this track '©too' - pub encoder: Option, + pub encoder: Option, /// The encoded-by settingo this track '©enc' - pub encoded_by: Option, + pub encoded_by: Option, /// The tempo or BPM of the track 'tmpo' pub beats_per_minute: Option, /// Copyright information of the track 'cprt' - pub copyright: Option, + pub copyright: Option, /// Whether or not this track is part of a compilation 'cpil' pub compilation: Option, /// The advisory rating of this track 'rtng' @@ -690,45 +653,45 @@ pub struct MetadataBox { /// This is stored in the box as string data, but /// the format is an integer percentage from 0 - 100, /// where 100 is displayed as 5 stars out of 5. - pub rating: Option, + pub rating: Option, /// The grouping this track belongs to '©grp' - pub grouping: Option, + pub grouping: Option, /// The media type of this track 'stik' pub media_type: Option, // stik /// Whether or not this track is a podcast 'pcst' pub podcast: Option, /// The category of ths track 'catg' - pub category: Option, + pub category: Option, /// The podcast keyword 'keyw' - pub keyword: Option, + pub keyword: Option, /// The podcast url 'purl' - pub podcast_url: Option, + pub podcast_url: Option, /// The podcast episode GUID 'egid' - pub podcast_guid: Option, + pub podcast_guid: Option, /// The description of the track 'desc' - pub description: Option, + pub description: Option, /// The long description of the track 'ldes'. /// /// Unlike other string fields, the long description field /// can be longer than 256 characters. - pub long_description: Option, + pub long_description: Option, /// The lyrics of the track '©lyr'. /// /// Unlike other string fields, the lyrics field /// can be longer than 256 characters. - pub lyrics: Option, + pub lyrics: Option, /// The name of the TV network this track aired on 'tvnn'. - pub tv_network_name: Option, + pub tv_network_name: Option, /// The name of the TV Show for this track 'tvsh'. - pub tv_show_name: Option, + pub tv_show_name: Option, /// The name of the TV Episode for this track 'tven'. - pub tv_episode_name: Option, + pub tv_episode_name: Option, /// The number of the TV Episode for this track 'tves'. pub tv_episode_number: Option, /// The season of the TV Episode of this track 'tvsn'. pub tv_season: Option, /// The date this track was purchased 'purd'. - pub purchase_date: Option, + pub purchase_date: Option, /// Whether or not this track supports gapless playback 'pgap' pub gapless_playback: Option, /// Any cover artwork attached to this track 'covr' @@ -736,21 +699,21 @@ pub struct MetadataBox { /// 'covr' is unique in that it may contain multiple 'data' sub-entries, /// each an image file. Here, each subentry's raw binary data is exposed, /// which may contain image data in JPEG or PNG format. - pub cover_art: Option>>, + pub cover_art: Option>>, /// The owner of the track 'ownr' - pub owner: Option, + pub owner: Option, /// Whether or not this track is HD Video 'hdvd' pub hd_video: Option, /// The name of the track to sort by 'sonm' - pub sort_name: Option, + pub sort_name: Option, /// The name of the album to sort by 'soal' - pub sort_album: Option, + pub sort_album: Option, /// The name of the artist to sort by 'soar' - pub sort_artist: Option, + pub sort_artist: Option, /// The name of the album artist to sort by 'soaa' - pub sort_album_artist: Option, + pub sort_album_artist: Option, /// The name of the composer to sort by 'soco' - pub sort_composer: Option, + pub sort_composer: Option, } /// Internal data structures. @@ -758,9 +721,9 @@ pub struct MetadataBox { pub struct MediaContext { pub timescale: Option, /// Tracks found in the file. - pub tracks: Vec, + pub tracks: TryVec, pub mvex: Option, - pub psshs: Vec, + pub psshs: TryVec, pub userdata: Option>, } @@ -773,7 +736,7 @@ impl MediaContext { #[derive(Debug, Default)] pub struct AvifContext { /// The collected data indicated by the `pitm` box, See ISO 14496-12:2015 § 8.11.4 - pub primary_item: Vec, + pub primary_item: TryVec, } impl AvifContext { @@ -787,7 +750,7 @@ impl AvifContext { struct MediaDataBox { /// Offset of `data` from the beginning of the file. See ConstructionMethod::File offset: u64, - data: Vec, + data: TryVec, } impl MediaDataBox { @@ -823,7 +786,7 @@ impl MediaDataBox { /// Copy the range specified by `extent` to the end of `buf` or return an error if the range /// is not fully contained within `MediaDataBox`. - fn read_extent(&mut self, extent: &ExtentRange, buf: &mut Vec) -> Result<()> { + fn read_extent(&mut self, extent: &ExtentRange, buf: &mut TryVec) -> Result<()> { let start_offset = extent .start() .checked_sub(self.offset) @@ -842,7 +805,7 @@ impl MediaDataBox { ExtentRange::ToEnd(_) => self.data.get(start_offset.try_into()?..), }; let slice = slice.ok_or(Error::InvalidData("extent crosses box boundary"))?; - extend_from_slice(buf, slice)?; + buf.extend_from_slice(slice)?; Ok(()) } } @@ -911,12 +874,12 @@ impl TryFrom for IlocVersion { /// See ISO 14496-12:2015 § 8.11.3 /// `base_offset` is omitted since it is integrated into the ranges in `extents` /// `data_reference_index` is omitted, since only 0 (i.e., this file) is supported -#[derive(Clone, Debug)] +#[derive(Debug)] struct ItemLocationBoxItem { item_id: u32, construction_method: ConstructionMethod, /// Unused for ConstructionMethod::Idat - extents: Vec, + extents: TryVec, } #[derive(Clone, Copy, Debug)] @@ -1112,7 +1075,7 @@ impl<'a, T: Read + Offset> BMFFBox<'a, T> { /// Read the range specified by `extent` into `buf` or return an error if the range is not /// fully contained within the `BMFFBox`. - fn read_extent(&mut self, extent: &ExtentRange, buf: &mut Vec) -> Result<()> { + fn read_extent(&mut self, extent: &ExtentRange, buf: &mut TryVec) -> Result<()> { let start_offset = extent .start() .checked_sub(self.offset()) @@ -1127,10 +1090,10 @@ impl<'a, T: Read + Offset> BMFFBox<'a, T> { if len > self.bytes_left() { return Err(Error::InvalidData("extent crosses box boundary")); } - read_to_end(&mut self.take(len), buf)?; + self.take(len).try_read_to_end(buf)?; } ExtentRange::ToEnd(_) => { - read_to_end(&mut self.take(self.bytes_left()), buf)?; + self.try_read_to_end(buf)?; } } Ok(()) @@ -1261,7 +1224,7 @@ pub fn read_avif(f: &mut T, context: &mut AvifContext) -> Result<()> { if let Some(mut b) = iter.next_box()? { if b.head.name == BoxType::FileTypeBox { let ftyp = read_ftyp(&mut b)?; - if !ftyp.compatible_brands.contains(&FourCC::from("mif1")) { + if !ftyp.compatible_brands.contains(&FourCC::from(*b"mif1")) { return Err(Error::InvalidData("compatible_brands must contain 'mif1'")); } } else { @@ -1270,9 +1233,9 @@ pub fn read_avif(f: &mut T, context: &mut AvifContext) -> Result<()> { } let mut read_meta = false; - let mut mdats = vec![]; + let mut mdats = TryVec::new(); let mut primary_item_extents = None; - let mut primary_item_extents_data: Vec> = vec![]; + let mut primary_item_extents_data: TryVec> = TryVec::new(); while let Some(mut b) = iter.next_box()? { match b.head.name { @@ -1286,9 +1249,9 @@ pub fn read_avif(f: &mut T, context: &mut AvifContext) -> Result<()> { let primary_item_loc = read_avif_meta(&mut b)?; match primary_item_loc.construction_method { ConstructionMethod::File => { + primary_item_extents_data + .resize_with(primary_item_loc.extents.len(), Default::default)?; primary_item_extents = Some(primary_item_loc.extents); - primary_item_extents_data = - primary_item_extents.iter().map(|_| vec![]).collect(); } _ => return Err(Error::Unsupported("unsupported construction_method")), } @@ -1308,9 +1271,8 @@ pub fn read_avif(f: &mut T, context: &mut AvifContext) -> Result<()> { // Store any remaining data for potential later extraction if b.bytes_left() > 0 { let offset = b.offset(); - let mut data = vec_with_capacity(b.bytes_left().try_into()?)?; - b.read_to_end(&mut data)?; - vec_push(&mut mdats, MediaDataBox { offset, data })?; + let data = b.read_into_try_vec()?; + mdats.push(MediaDataBox { offset, data })?; } } _ => skip_box_content(&mut b)?, @@ -1328,9 +1290,9 @@ pub fn read_avif(f: &mut T, context: &mut AvifContext) -> Result<()> { { if data.is_empty() { // try to find an overlapping mdat - for mdat in &mut mdats { + for mdat in mdats.iter_mut() { if mdat.matches_extent(&extent.extent_range) { - data.append(&mut mdat.data) + data.append(&mut mdat.data)?; } else if mdat.contains_extent(&extent.extent_range) { mdat.read_extent(&extent.extent_range, data)?; } @@ -1338,7 +1300,7 @@ pub fn read_avif(f: &mut T, context: &mut AvifContext) -> Result<()> { } } - context.primary_item = primary_item_extents_data.concat(); + context.primary_item = primary_item_extents_data.concat()?; Ok(()) } @@ -1401,10 +1363,7 @@ fn read_avif_meta(src: &mut BMFFBox) -> Result(src: &mut BMFFBox) -> Result(src: &mut BMFFBox) -> Result { /// Parse an Item Information Box /// See ISO 14496-12:2015 § 8.11.6 -fn read_iinf(src: &mut BMFFBox) -> Result> { +fn read_iinf(src: &mut BMFFBox) -> Result> { let version = read_fullbox_version_no_flags(src)?; match version { @@ -1455,7 +1414,7 @@ fn read_iinf(src: &mut BMFFBox) -> Result> { } else { be_u32(src)?.to_usize() }; - let mut item_infos = vec_with_capacity(entry_count)?; + let mut item_infos = TryVec::with_capacity(entry_count)?; let mut iter = src.box_iter(); while let Some(mut b) = iter.next_box()? { @@ -1465,7 +1424,7 @@ fn read_iinf(src: &mut BMFFBox) -> Result> { )); } - vec_push(&mut item_infos, read_infe(&mut b)?)?; + item_infos.push(read_infe(&mut b)?)?; check_parser_state!(b.content); } @@ -1473,8 +1432,17 @@ fn read_iinf(src: &mut BMFFBox) -> Result> { Ok(item_infos) } -fn be_u32_to_string(src: u32) -> String { - String::from_utf8(src.to_be_bytes().to_vec()).unwrap_or(format!("{:x?}", src)) +/// A simple wrapper to interpret a u32 as a 4-byte string in big-endian +/// order without requiring any allocation. +struct U32BE(u32); + +impl std::fmt::Display for U32BE { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match std::str::from_utf8(&self.0.to_be_bytes()) { + Ok(s) => write!(f, "{}", s), + Err(_) => write!(f, "{:x?}", self.0), + } + } } /// Parse an Item Info Entry @@ -1500,11 +1468,7 @@ fn read_infe(src: &mut BMFFBox) -> Result { } let item_type = be_u32(src)?; - debug!( - "infe item_id {} item_type: {}", - item_id, - be_u32_to_string(item_type) - ); + debug!("infe item_id {} item_type: {}", item_id, U32BE(item_type)); // There are some additional fields here, but they're not of interest to us skip_box_remain(src)?; @@ -1514,12 +1478,11 @@ fn read_infe(src: &mut BMFFBox) -> Result { /// Parse an item location box inside a meta box /// See ISO 14496-12:2015 § 8.11.3 -fn read_iloc(src: &mut BMFFBox) -> Result> { +fn read_iloc(src: &mut BMFFBox) -> Result> { let version: IlocVersion = read_fullbox_version_no_flags(src)?.try_into()?; - let mut iloc = vec_with_capacity(src.bytes_left().try_into()?)?; - src.read_to_end(&mut iloc)?; - let mut iloc = BitReader::new(iloc.as_slice()); + let iloc = src.read_into_try_vec()?; + let mut iloc = BitReader::new(&iloc); let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; @@ -1538,7 +1501,7 @@ fn read_iloc(src: &mut BMFFBox) -> Result> IlocVersion::Two => iloc.read_u32(32)?, }; - let mut items = vec_with_capacity(item_count.to_usize())?; + let mut items = TryVec::with_capacity(item_count.to_usize())?; for _ in 0..item_count { let item_id = match version { @@ -1581,7 +1544,7 @@ fn read_iloc(src: &mut BMFFBox) -> Result> )); } - let mut extents = vec_with_capacity(extent_count.to_usize())?; + let mut extents = TryVec::with_capacity(extent_count.to_usize())?; for _ in 0..extent_count { // Parsed but currently ignored, see `ItemLocationBoxExtent` @@ -1614,17 +1577,14 @@ fn read_iloc(src: &mut BMFFBox) -> Result> ExtentRange::WithLength(Range { start, end }) }; - vec_push(&mut extents, ItemLocationBoxExtent { extent_range })?; + extents.push(ItemLocationBoxExtent { extent_range })?; } - vec_push( - &mut items, - ItemLocationBoxItem { - item_id, - construction_method, - extents, - }, - )?; + items.push(ItemLocationBoxItem { + item_id, + construction_method, + extents, + })?; } debug_assert_eq!(iloc.remaining(), 0); @@ -1714,7 +1674,7 @@ fn read_moov(f: &mut BMFFBox, context: &mut MediaContext) -> Result< BoxType::TrackBox => { let mut track = Track::new(context.tracks.len()); read_trak(&mut b, &mut track)?; - vec_push(&mut context.tracks, track)?; + context.tracks.push(track)?; } BoxType::MovieExtendsBox => { let mvex = read_mvex(&mut b)?; @@ -1724,7 +1684,7 @@ fn read_moov(f: &mut BMFFBox, context: &mut MediaContext) -> Result< BoxType::ProtectionSystemSpecificHeaderBox => { let pssh = read_pssh(&mut b)?; debug!("{:?}", pssh); - vec_push(&mut context.psshs, pssh)?; + context.psshs.push(pssh)?; } BoxType::UserdataBox => { let udta = read_udta(&mut b); @@ -1742,18 +1702,18 @@ fn read_pssh(src: &mut BMFFBox) -> Result = Vec::new(); + let mut kid = TryVec::::new(); if version > 0 { let count = be_u32_with_limit(pssh)?; for _ in 0..count { let item = read_buf(pssh, 16)?; - vec_push(&mut kid, item)?; + kid.push(item)?; } } @@ -1763,10 +1723,10 @@ fn read_pssh(src: &mut BMFFBox) -> Result(f: &mut BMFFBox, track: &mut Track) -> Result<()> { let hdlr = read_hdlr(&mut b)?; match hdlr.handler_type.value.as_ref() { - "vide" => track.track_type = TrackType::Video, - "soun" => track.track_type = TrackType::Audio, - "meta" => track.track_type = TrackType::Metadata, + b"vide" => track.track_type = TrackType::Video, + b"soun" => track.track_type = TrackType::Audio, + b"meta" => track.track_type = TrackType::Metadata, _ => (), } debug!("{:?}", hdlr); @@ -1984,9 +1944,9 @@ fn read_ftyp(src: &mut BMFFBox) -> Result { } // Is a brand_count of zero valid? let brand_count = bytes_left / 4; - let mut brands = Vec::new(); + let mut brands = TryVec::new(); for _ in 0..brand_count { - vec_push(&mut brands, From::from(be_u32(src)?))?; + brands.push(be_u32(src)?.into())?; } Ok(FileTypeBox { major_brand: From::from(major), @@ -2083,7 +2043,7 @@ fn read_tkhd(src: &mut BMFFBox) -> Result { fn read_elst(src: &mut BMFFBox) -> Result { let (version, _) = read_fullbox_extra(src)?; let edit_count = be_u32_with_limit(src)?; - let mut edits = Vec::new(); + let mut edits = TryVec::new(); for _ in 0..edit_count { let (segment_duration, media_time) = match version { 1 => { @@ -2098,15 +2058,12 @@ fn read_elst(src: &mut BMFFBox) -> Result { }; let media_rate_integer = be_i16(src)?; let media_rate_fraction = be_i16(src)?; - vec_push( - &mut edits, - Edit { - segment_duration, - media_time, - media_rate_integer, - media_rate_fraction, - }, - )?; + edits.push(Edit { + segment_duration, + media_time, + media_rate_integer, + media_rate_fraction, + })?; } // Padding could be added in some contents. @@ -2161,9 +2118,9 @@ fn read_mdhd(src: &mut BMFFBox) -> Result { fn read_stco(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; let offset_count = be_u32_with_limit(src)?; - let mut offsets = Vec::new(); + let mut offsets = TryVec::new(); for _ in 0..offset_count { - vec_push(&mut offsets, u64::from(be_u32(src)?))?; + offsets.push(be_u32(src)?.into())?; } // Padding could be added in some contents. @@ -2176,9 +2133,9 @@ fn read_stco(src: &mut BMFFBox) -> Result { fn read_co64(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; let offset_count = be_u32_with_limit(src)?; - let mut offsets = Vec::new(); + let mut offsets = TryVec::new(); for _ in 0..offset_count { - vec_push(&mut offsets, be_u64(src)?)?; + offsets.push(be_u64(src)?)?; } // Padding could be added in some contents. @@ -2191,9 +2148,9 @@ fn read_co64(src: &mut BMFFBox) -> Result { fn read_stss(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; let sample_count = be_u32_with_limit(src)?; - let mut samples = Vec::new(); + let mut samples = TryVec::new(); for _ in 0..sample_count { - vec_push(&mut samples, be_u32(src)?)?; + samples.push(be_u32(src)?)?; } // Padding could be added in some contents. @@ -2206,19 +2163,16 @@ fn read_stss(src: &mut BMFFBox) -> Result { fn read_stsc(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; let sample_count = be_u32_with_limit(src)?; - let mut samples = Vec::new(); + let mut samples = TryVec::new(); for _ in 0..sample_count { let first_chunk = be_u32(src)?; let samples_per_chunk = be_u32_with_limit(src)?; let sample_description_index = be_u32(src)?; - vec_push( - &mut samples, - SampleToChunk { - first_chunk, - samples_per_chunk, - sample_description_index, - }, - )?; + samples.push(SampleToChunk { + first_chunk, + samples_per_chunk, + sample_description_index, + })?; } // Padding could be added in some contents. @@ -2236,7 +2190,7 @@ fn read_ctts(src: &mut BMFFBox) -> Result { return Err(Error::InvalidData("insufficient data in 'ctts' box")); } - let mut offsets = Vec::new(); + let mut offsets = TryVec::new(); for _ in 0..counts { let (sample_count, time_offset) = match version { // According to spec, Version0 shoule be used when version == 0; @@ -2251,13 +2205,10 @@ fn read_ctts(src: &mut BMFFBox) -> Result { return Err(Error::InvalidData("unsupported version in 'ctts' box")); } }; - vec_push( - &mut offsets, - TimeOffset { - sample_count, - time_offset, - }, - )?; + offsets.push(TimeOffset { + sample_count, + time_offset, + })?; } skip_box_remain(src)?; @@ -2270,10 +2221,10 @@ fn read_stsz(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; let sample_size = be_u32(src)?; let sample_count = be_u32_with_limit(src)?; - let mut sample_sizes = Vec::new(); + let mut sample_sizes = TryVec::new(); if sample_size == 0 { for _ in 0..sample_count { - vec_push(&mut sample_sizes, be_u32(src)?)?; + sample_sizes.push(be_u32(src)?)?; } } @@ -2290,17 +2241,14 @@ fn read_stsz(src: &mut BMFFBox) -> Result { fn read_stts(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; let sample_count = be_u32_with_limit(src)?; - let mut samples = Vec::new(); + let mut samples = TryVec::new(); for _ in 0..sample_count { let sample_count = be_u32_with_limit(src)?; let sample_delta = be_u32(src)?; - vec_push( - &mut samples, - Sample { - sample_count, - sample_delta, - }, - )?; + samples.push(Sample { + sample_count, + sample_delta, + })?; } // Padding could be added in some contents. @@ -2657,7 +2605,7 @@ fn read_ds_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { esds.audio_sample_rate = Some(sample_frequency_value); esds.audio_channel_count = Some(channel_counts); assert!(esds.decoder_specific_data.is_empty()); - esds.decoder_specific_data.extend_from_slice(data); + esds.decoder_specific_data.extend_from_slice(data)?; Ok(()) } @@ -2751,10 +2699,10 @@ fn read_dfla(src: &mut BMFFBox) -> Result { if flags != 0 { return Err(Error::InvalidData("no-zero dfLa (FLAC) flags")); } - let mut blocks = Vec::new(); + let mut blocks = TryVec::new(); while src.bytes_left() > 0 { let block = read_flac_metadata(src)?; - vec_push(&mut blocks, block)?; + blocks.push(block)?; } // The box must have at least one meta block, and the first block // must be the METADATA_BLOCK_STREAMINFO @@ -2932,7 +2880,7 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result // Skip clap/pasp/etc. for now. let mut codec_specific = None; - let mut protection_info = Vec::new(); + let mut protection_info = TryVec::new(); let mut iter = src.box_iter(); while let Some(mut b) = iter.next_box()? { match b.head.name { @@ -2994,7 +2942,7 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result } let sinf = read_sinf(&mut b)?; debug!("{:?} (sinf)", sinf); - vec_push(&mut protection_info, sinf)?; + protection_info.push(sinf)?; } _ => { debug!("Unsupported video codec, box {:?} found", b.head.name); @@ -3086,7 +3034,7 @@ fn read_audio_sample_entry(src: &mut BMFFBox) -> Result BoxType::LPCMAudioSampleEntry => (CodecType::LPCM, Some(AudioCodecSpecific::LPCM)), _ => (CodecType::Unknown, None), }; - let mut protection_info = Vec::new(); + let mut protection_info = TryVec::new(); let mut iter = src.box_iter(); while let Some(mut b) = iter.next_box()? { match b.head.name { @@ -3141,7 +3089,7 @@ fn read_audio_sample_entry(src: &mut BMFFBox) -> Result let sinf = read_sinf(&mut b)?; debug!("{:?} (sinf)", sinf); codec_type = CodecType::EncryptedAudio; - vec_push(&mut protection_info, sinf)?; + protection_info.push(sinf)?; } _ => { debug!("Unsupported audio codec, box {:?} found", b.head.name); @@ -3171,7 +3119,7 @@ fn read_stsd(src: &mut BMFFBox, track: &mut Track) -> Result(src: &mut BMFFBox, track: &mut Track) -> Result return Err(e), }; - vec_push(&mut descriptions, description)?; + descriptions.push(description)?; check_parser_state!(b.content); if descriptions.len() == description_count.to_usize() { break; @@ -3293,9 +3241,8 @@ fn read_tenc(src: &mut BMFFBox) -> Result { }) } -fn read_frma(src: &mut BMFFBox) -> Result { - let code_name = read_buf(src, 4)?; - String::from_utf8(code_name).map_err(From::from) +fn read_frma(src: &mut BMFFBox) -> Result { + read_buf(src, 4) } fn read_schm(src: &mut BMFFBox) -> Result { @@ -3453,24 +3400,22 @@ fn read_ilst_bool_data(src: &mut BMFFBox) -> Result> { Ok(read_ilst_u8_data(src)?.and_then(|d| Some(d.get(0)? == &1))) } -fn read_ilst_string_data(src: &mut BMFFBox) -> Result> { - read_ilst_u8_data(src)?.map_or(Ok(None), |d| { - String::from_utf8(d).map_err(From::from).map(Some) - }) +fn read_ilst_string_data(src: &mut BMFFBox) -> Result> { + read_ilst_u8_data(src) } -fn read_ilst_u8_data(src: &mut BMFFBox) -> Result>> { +fn read_ilst_u8_data(src: &mut BMFFBox) -> Result>> { // For all non-covr atoms, there must only be one data atom. Ok(read_ilst_multiple_u8_data(src)?.pop()) } -fn read_ilst_multiple_u8_data(src: &mut BMFFBox) -> Result>> { +fn read_ilst_multiple_u8_data(src: &mut BMFFBox) -> Result>> { let mut iter = src.box_iter(); - let mut data = Vec::new(); + let mut data = TryVec::new(); while let Some(mut b) = iter.next_box()? { match b.head.name { BoxType::MetadataItemDataEntry => { - vec_push(&mut data, read_ilst_data(&mut b)?)?; + data.push(read_ilst_data(&mut b)?)?; } _ => skip_box_content(&mut b)?, }; @@ -3479,7 +3424,7 @@ fn read_ilst_multiple_u8_data(src: &mut BMFFBox) -> Result(src: &mut BMFFBox) -> Result> { +fn read_ilst_data(src: &mut BMFFBox) -> Result> { // Skip past the padding bytes skip(&mut src.content, src.head.offset)?; let size = src.content.limit(); @@ -3493,14 +3438,13 @@ fn skip(src: &mut T, bytes: u64) -> Result<()> { } /// Read size bytes into a Vector or return error. -fn read_buf(src: &mut T, size: u64) -> Result> { +fn read_buf(src: &mut T, size: u64) -> Result> { if size > BUF_SIZE_LIMIT { return Err(Error::InvalidData("read_buf size exceeds BUF_SIZE_LIMIT")); } - let mut buf = vec![]; - let r: u64 = read_to_end(&mut src.take(size), &mut buf)?.try_into()?; - if r != size { + let buf = src.take(size).read_into_try_vec()?; + if buf.len().to_u64() != size { return Err(Error::InvalidData("failed buffer read")); } diff --git a/media/mp4parse-rust/mp4parse/src/macros.rs b/third_party/rust/mp4parse/src/macros.rs similarity index 100% rename from media/mp4parse-rust/mp4parse/src/macros.rs rename to third_party/rust/mp4parse/src/macros.rs diff --git a/media/mp4parse-rust/mp4parse/src/tests.rs b/third_party/rust/mp4parse/src/tests.rs similarity index 97% rename from media/mp4parse-rust/mp4parse/src/tests.rs rename to third_party/rust/mp4parse/src/tests.rs index a49a9c0e492e..d9b166040a61 100644 --- a/media/mp4parse-rust/mp4parse/src/tests.rs +++ b/third_party/rust/mp4parse/src/tests.rs @@ -5,9 +5,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use super::fallible::TryRead as _; use super::read_mp4; use super::Error; use super::MediaContext; + #[cfg(feature = "mp4parse_fallible")] use std::convert::TryInto as _; use std::io::Cursor; @@ -179,11 +181,11 @@ fn read_ftyp() { assert_eq!(stream.head.name, BoxType::FileTypeBox); assert_eq!(stream.head.size, 24); let parsed = super::read_ftyp(&mut stream).unwrap(); - assert_eq!(parsed.major_brand, FourCC::from("mp42")); // mp42 + assert_eq!(parsed.major_brand, FourCC::from(*b"mp42")); // mp42 assert_eq!(parsed.minor_version, 0); assert_eq!(parsed.compatible_brands.len(), 2); - assert_eq!(parsed.compatible_brands[0], FourCC::from("isom")); // isom - assert_eq!(parsed.compatible_brands[1], FourCC::from("mp42")); // mp42 + assert_eq!(parsed.compatible_brands[0], FourCC::from(*b"isom")); // isom + assert_eq!(parsed.compatible_brands[1], FourCC::from(*b"mp42")); // mp42 } #[test] @@ -221,11 +223,11 @@ fn read_ftyp_case() { assert_eq!(stream.head.name, BoxType::FileTypeBox); assert_eq!(stream.head.size, 24); let parsed = super::read_ftyp(&mut stream).unwrap(); - assert_eq!(parsed.major_brand, FourCC::from("MP42")); + assert_eq!(parsed.major_brand, FourCC::from(*b"MP42")); assert_eq!(parsed.minor_version, 0); assert_eq!(parsed.compatible_brands.len(), 2); - assert_eq!(parsed.compatible_brands[0], FourCC::from("ISOM")); // ISOM - assert_eq!(parsed.compatible_brands[1], FourCC::from("MP42")); // MP42 + assert_eq!(parsed.compatible_brands[0], FourCC::from(*b"ISOM")); // ISOM + assert_eq!(parsed.compatible_brands[1], FourCC::from(*b"MP42")); // MP42 } #[test] @@ -472,7 +474,7 @@ fn read_hdlr() { assert_eq!(stream.head.name, BoxType::HandlerBox); assert_eq!(stream.head.size, 45); let parsed = super::read_hdlr(&mut stream).unwrap(); - assert_eq!(parsed.handler_type, FourCC::from("vide")); + assert_eq!(parsed.handler_type, FourCC::from(*b"vide")); } #[test] @@ -485,7 +487,7 @@ fn read_hdlr_short_name() { assert_eq!(stream.head.name, BoxType::HandlerBox); assert_eq!(stream.head.size, 33); let parsed = super::read_hdlr(&mut stream).unwrap(); - assert_eq!(parsed.handler_type, FourCC::from("vide")); + assert_eq!(parsed.handler_type, FourCC::from(*b"vide")); } #[test] @@ -498,7 +500,7 @@ fn read_hdlr_zero_length_name() { assert_eq!(stream.head.name, BoxType::HandlerBox); assert_eq!(stream.head.size, 32); let parsed = super::read_hdlr(&mut stream).unwrap(); - assert_eq!(parsed.handler_type, FourCC::from("vide")); + assert_eq!(parsed.handler_type, FourCC::from(*b"vide")); } fn flac_streaminfo() -> Vec { @@ -680,7 +682,7 @@ fn serialize_opus_header() { channel_mapping_table: Some(super::ChannelMappingTable { stream_count: 4, coupled_count: 2, - channel_mapping: vec![0, 4, 1, 2, 3, 5], + channel_mapping: vec![0, 4, 1, 2, 3, 5].into(), }), }; let mut v = Vec::::new(); @@ -1057,7 +1059,7 @@ fn read_stsd_mp4v() { assert_eq!(v.height, 480); match v.codec_specific { super::VideoCodecSpecific::ESDSConfig(esds_data) => { - assert_eq!(esds_data, esds_specific_data.to_vec()); + assert_eq!(esds_data.as_slice(), esds_specific_data); } _ => panic!("it should be ESDSConfig!"), } @@ -1293,16 +1295,14 @@ fn read_stsd_lpcm() { #[test] fn read_to_end_() { let mut src = b"1234567890".take(5); - let mut buf = vec![]; - let bytes_read = super::read_to_end(&mut src, &mut buf).unwrap(); - assert_eq!(bytes_read, 5); - assert_eq!(buf, b"12345"); + let buf = src.read_into_try_vec().unwrap(); + assert_eq!(buf.len(), 5); + assert_eq!(buf.into_inner(), b"12345"); } #[test] #[cfg(feature = "mp4parse_fallible")] fn read_to_end_oom() { let mut src = b"1234567890".take(std::usize::MAX.try_into().expect("usize < u64")); - let mut buf = vec![]; - assert!(super::read_to_end(&mut src, &mut buf).is_err()); + assert!(src.read_into_try_vec().is_err()); } diff --git a/media/mp4parse-rust/mp4parse/tests/overflow.rs b/third_party/rust/mp4parse/tests/overflow.rs similarity index 100% rename from media/mp4parse-rust/mp4parse/tests/overflow.rs rename to third_party/rust/mp4parse/tests/overflow.rs diff --git a/media/mp4parse-rust/mp4parse/tests/public.rs b/third_party/rust/mp4parse/tests/public.rs similarity index 96% rename from media/mp4parse-rust/mp4parse/tests/public.rs rename to third_party/rust/mp4parse/tests/public.rs index 4aa889972341..b8a9fc216a9b 100644 --- a/media/mp4parse-rust/mp4parse/tests/public.rs +++ b/third_party/rust/mp4parse/tests/public.rs @@ -4,6 +4,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. extern crate mp4parse as mp4; +use std::convert::TryInto; use std::fs::File; use std::io::{Cursor, Read}; use std::path::Path; @@ -28,8 +29,7 @@ static VIDEO_EME_CBCS_MP4: &str = "tests/bipbop_cbcs_video_init.mp4"; static VIDEO_AV1_MP4: &str = "tests/tiny_av1.mp4"; static IMAGE_AVIF: &str = "av1-avif/testFiles/Microsoft/Monochrome.avif"; static IMAGE_AVIF_GRID: &str = "av1-avif/testFiles/Microsoft/Summer_in_Tomsk_720p_5x4_grid.avif"; -static MICROSOFT_AVIF_TEST_DIR: &str = "av1-avif/testFiles/Microsoft"; -static NETFLIX_AVIF_TEST_DIR: &str = "av1-avif/testFiles/Netflix/avif"; +static AVIF_TEST_DIR: &str = "av1-avif/testFiles"; // Adapted from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53 #[test] @@ -175,7 +175,7 @@ fn public_metadata() { assert_eq!(meta.year.unwrap(), "2019"); assert_eq!( meta.genre.unwrap(), - mp4::Genre::CustomGenre("Custom Genre".to_string()) + mp4::Genre::CustomGenre("Custom Genre".try_into().unwrap()) ); assert_eq!(meta.encoder.unwrap(), "Lavf56.40.101"); assert_eq!(meta.encoded_by.unwrap(), "Encoded-by"); @@ -311,7 +311,7 @@ fn public_audio_tenc() { Some(ref p) => { assert_eq!(p.code_name, "mp4a"); if let Some(ref schm) = p.scheme_type { - assert_eq!(schm.scheme_type.value, "cenc"); + assert_eq!(schm.scheme_type.value, *b"cenc"); } else { panic!("Expected scheme type info"); } @@ -370,7 +370,7 @@ fn public_video_cenc() { Some(ref p) => { assert_eq!(p.code_name, "avc1"); if let Some(ref schm) = p.scheme_type { - assert_eq!(schm.scheme_type.value, "cenc"); + assert_eq!(schm.scheme_type.value, *b"cenc"); } else { panic!("Expected scheme type info"); } @@ -443,7 +443,7 @@ fn public_audio_cbcs() { found_encrypted_sample_description = true; assert_eq!(p.code_name, "mp4a"); if let Some(ref schm) = p.scheme_type { - assert_eq!(schm.scheme_type.value, "cbcs"); + assert_eq!(schm.scheme_type.value, *b"cbcs"); } else { panic!("Expected scheme type info"); } @@ -456,7 +456,7 @@ fn public_audio_cbcs() { // to indicate full encryption. assert_eq!(tenc.crypt_byte_block_count, Some(0)); assert_eq!(tenc.skip_byte_block_count, Some(0)); - assert_eq!(tenc.constant_iv, Some(default_iv.clone())); + assert_eq!(tenc.constant_iv, Some(default_iv.clone().into())); } else { panic!("Invalid test condition"); } @@ -528,7 +528,7 @@ fn public_video_cbcs() { found_encrypted_sample_description = true; assert_eq!(p.code_name, "avc1"); if let Some(ref schm) = p.scheme_type { - assert_eq!(schm.scheme_type.value, "cbcs"); + assert_eq!(schm.scheme_type.value, *b"cbcs"); } else { panic!("Expected scheme type info"); } @@ -538,7 +538,7 @@ fn public_video_cbcs() { assert_eq!(tenc.kid, kid); assert_eq!(tenc.crypt_byte_block_count, Some(1)); assert_eq!(tenc.skip_byte_block_count, Some(9)); - assert_eq!(tenc.constant_iv, Some(default_iv.clone())); + assert_eq!(tenc.constant_iv, Some(default_iv.clone().into())); } else { panic!("Invalid test condition"); } @@ -639,22 +639,19 @@ fn public_avif_primary_item_is_grid() { #[test] fn public_avif_read_samples() { env_logger::init(); - let microsoft = Path::new(MICROSOFT_AVIF_TEST_DIR) - .read_dir() - .expect("Cannot read AVIF test dir"); - let netflix = Path::new(NETFLIX_AVIF_TEST_DIR) - .read_dir() - .expect("Cannot read AVIF test dir"); - for entry in microsoft.chain(netflix) { - let path = entry.expect("AVIF entry").path(); - if path.extension().expect("no extension") != "avif" { + + for entry in walkdir::WalkDir::new(AVIF_TEST_DIR) { + let entry = entry.expect("AVIF entry"); + let path = entry.path(); + if !path.is_file() || path.extension().unwrap_or_default() != "avif" { eprintln!("Skipping {:?}", path); - continue; // Skip ReadMe.txt, etc. + continue; // Skip directories, ReadMe.txt, etc. } if path == Path::new(IMAGE_AVIF_GRID) { eprintln!("Skipping {:?}", path); continue; // Remove when public_avif_primary_item_is_grid passes } + println!("parsing {:?}", path); let context = &mut mp4::AvifContext::new(); let input = &mut File::open(path).expect("Unknow file"); mp4::read_avif(input, context).expect("read_avif failed"); diff --git a/third_party/rust/mp4parse_capi/.cargo-checksum.json b/third_party/rust/mp4parse_capi/.cargo-checksum.json new file mode 100644 index 000000000000..9e4dae55f91e --- /dev/null +++ b/third_party/rust/mp4parse_capi/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"13408d7785c5fe40f9db2bac1f93e8cf2aca7c35b5d2ba9acbbb23eb3a71e40a","cbindgen.toml":"5c9429f271d6e914d81b63e6509c04ffe84cab11ed3a53a2ed4715e5d5ace80e","examples/dump.rs":"2db33a7ffbb20880d140e95c93cdeabe7e2ff41e5c09d80aa10dfdda9277942d","src/lib.rs":"deff68e6d20bc20167aa94cc0aaa541d83ca80bd0f18d9524530706a51782f5c","tests/test_chunk_out_of_range.rs":"b5da583218d98027ed973a29c67434a91a1306f2d2fb39ec4d640d4824c308ce","tests/test_encryption.rs":"a26f2fdb40e1fb3619a0625d0afbd677c6bdff1e2e640962197142656499c409","tests/test_fragment.rs":"e90eb5a4418d30002655466c0c4b3125c7fd70a74b6871471eaa172f1def9db8","tests/test_rotation.rs":"fb43c2f2dfa496d151c33bdd46c0fd3252387c23cc71e2cac9ed0234de715a81","tests/test_sample_table.rs":"adc8d264c46aef78c047377fbb792a4400d6db98bc44583b464d7b3dc182c884","tests/test_workaround_stsc.rs":"7dd419f3d55b9a3a039cac57e58a9240a9c8166bcd4356c24f69f731c3ced83b"},"package":null} \ No newline at end of file diff --git a/media/mp4parse-rust/mp4parse_capi/Cargo.toml b/third_party/rust/mp4parse_capi/Cargo.toml similarity index 89% rename from media/mp4parse-rust/mp4parse_capi/Cargo.toml rename to third_party/rust/mp4parse_capi/Cargo.toml index 895739f4413c..2904775e7214 100644 --- a/media/mp4parse-rust/mp4parse_capi/Cargo.toml +++ b/third_party/rust/mp4parse_capi/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "mp4parse_capi" -version = "0.11.2" +version = "0.11.4" authors = [ "Ralph Giles ", "Matthew Gregan ", "Alfredo Yang ", + "Jon Bauman ", ] description = "Parser for ISO base media file format (mp4)" @@ -18,8 +19,6 @@ exclude = [ "*.mp4", ] -build = false # See bug 1611431 - Generate mp4parse-rust bindings as part of mach build - [badges] travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } diff --git a/media/mp4parse-rust/mp4parse_capi/cbindgen.toml b/third_party/rust/mp4parse_capi/cbindgen.toml similarity index 70% rename from media/mp4parse-rust/mp4parse_capi/cbindgen.toml rename to third_party/rust/mp4parse_capi/cbindgen.toml index f23797a75497..bbaf50b0787a 100644 --- a/media/mp4parse-rust/mp4parse_capi/cbindgen.toml +++ b/third_party/rust/mp4parse_capi/cbindgen.toml @@ -1,8 +1,8 @@ header = """/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""" -autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */ -#ifndef mozilla_media_mp4parse_rust_mp4parse_h +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */ +#ifndef mp4parse_rust_mp4parse_h #error "Don't include this file directly, instead include mp4parse.h" #endif """ @@ -11,9 +11,7 @@ braces = "SameLine" line_length = 100 tab_width = 2 language = "C" - -# If we change the language to C++, it would be nice to namespace it -# namespaces = ["mozilla", "media", "ffi"] +cpp_compat = true [enum] rename_variants = "QualifiedScreamingSnakeCase" diff --git a/third_party/rust/mp4parse_capi/examples/dump.rs b/third_party/rust/mp4parse_capi/examples/dump.rs new file mode 100644 index 000000000000..ac4cb738a620 --- /dev/null +++ b/third_party/rust/mp4parse_capi/examples/dump.rs @@ -0,0 +1,168 @@ +extern crate mp4parse; +extern crate mp4parse_capi; + +#[macro_use] +extern crate log; + +extern crate env_logger; + +use mp4parse_capi::*; +use std::env; +use std::fs::File; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(&mut buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +fn dump_file(filename: &str) { + let mut file = File::open(filename).expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let rv = mp4parse_new(&io, &mut parser); + + match rv { + Mp4parseStatus::Ok => (), + _ => { + println!("-- fail to parse, '-v' for more info"); + return; + } + } + + let mut frag_info = Mp4parseFragmentInfo::default(); + match mp4parse_get_fragment_info(parser, &mut frag_info) { + Mp4parseStatus::Ok => { + println!("-- mp4parse_fragment_info {:?}", frag_info); + } + _ => { + println!("-- mp4parse_fragment_info failed"); + return; + } + } + + let mut counts: u32 = 0; + match mp4parse_get_track_count(parser, &mut counts) { + Mp4parseStatus::Ok => (), + _ => { + println!("-- mp4parse_get_track_count failed"); + return; + } + } + + for i in 0..counts { + let mut track_info = Mp4parseTrackInfo { + track_type: Mp4parseTrackType::Audio, + track_id: 0, + duration: 0, + media_time: 0, + }; + match mp4parse_get_track_info(parser, i, &mut track_info) { + Mp4parseStatus::Ok => { + println!("-- mp4parse_get_track_info {:?}", track_info); + } + _ => { + println!("-- mp4parse_get_track_info failed, track id: {}", i); + return; + } + } + + match track_info.track_type { + Mp4parseTrackType::Audio => { + let mut audio_info = Mp4parseTrackAudioInfo::default(); + match mp4parse_get_track_audio_info(parser, i, &mut audio_info) { + Mp4parseStatus::Ok => { + println!("-- mp4parse_get_track_audio_info {:?}", audio_info); + for i in 0..audio_info.sample_info_count as isize { + let sample_info = audio_info.sample_info.offset(i); + println!( + " -- mp4parse_get_track_audio_info sample_info[{:?}] {:?}", + i, *sample_info + ); + } + } + _ => { + println!("-- mp4parse_get_track_audio_info failed, track id: {}", i); + return; + } + } + } + Mp4parseTrackType::Video => { + let mut video_info = Mp4parseTrackVideoInfo::default(); + match mp4parse_get_track_video_info(parser, i, &mut video_info) { + Mp4parseStatus::Ok => { + println!("-- mp4parse_get_track_video_info {:?}", video_info); + for i in 0..video_info.sample_info_count as isize { + let sample_info = video_info.sample_info.offset(i); + println!( + " -- mp4parse_get_track_video_info sample_info[{:?}] {:?}", + i, *sample_info + ); + } + } + _ => { + println!("-- mp4parse_get_track_video_info failed, track id: {}", i); + return; + } + } + } + Mp4parseTrackType::Metadata => { + println!("TODO metadata track"); + } + } + + let mut indices = Mp4parseByteData::default(); + match mp4parse_get_indice_table(parser, track_info.track_id, &mut indices) { + Mp4parseStatus::Ok => { + println!( + "-- mp4parse_get_indice_table track_id {} indices {:?}", + track_info.track_id, indices + ); + } + _ => { + println!( + "-- mp4parse_get_indice_table failed, track_info.track_id: {}", + track_info.track_id + ); + return; + } + } + } + mp4parse_free(parser); + } +} + +fn main() { + let args: Vec = env::args().collect(); + if args.len() < 2 { + return; + } + + // Initialize logging, setting the log level if requested. + let (skip, verbose) = if args[1] == "-v" { + (2, true) + } else { + (1, false) + }; + let env = env_logger::Env::default(); + let mut logger = env_logger::Builder::from_env(env); + if verbose { + logger.filter(None, log::LevelFilter::Debug); + } + logger.init(); + + for filename in args.iter().skip(skip) { + info!("-- dump of '{}' --", filename); + dump_file(filename); + info!("-- end of '{}' --", filename); + } +} diff --git a/media/mp4parse-rust/mp4parse_capi/src/lib.rs b/third_party/rust/mp4parse_capi/src/lib.rs similarity index 90% rename from media/mp4parse-rust/mp4parse_capi/src/lib.rs rename to third_party/rust/mp4parse_capi/src/lib.rs index 7321c03aa87c..ab2828d9a0cb 100644 --- a/media/mp4parse-rust/mp4parse_capi/src/lib.rs +++ b/third_party/rust/mp4parse_capi/src/lib.rs @@ -41,15 +41,12 @@ extern crate num_traits; use byteorder::WriteBytesExt; use num_traits::{PrimInt, Zero}; -use std::collections::HashMap; use std::io::Read; // Symbols we need from our rust api. -use mp4parse::extend_from_slice; use mp4parse::read_avif; use mp4parse::read_mp4; use mp4parse::serialize_opus_header; -use mp4parse::vec_push; use mp4parse::AudioCodecSpecific; use mp4parse::AvifContext; use mp4parse::CodecType; @@ -62,8 +59,21 @@ use mp4parse::Track; use mp4parse::TrackScaledTime; use mp4parse::TrackTimeScale; use mp4parse::TrackType; +use mp4parse::TryBox; +use mp4parse::TryHashMap; +use mp4parse::TryVec; use mp4parse::VideoCodecSpecific; +// To ensure we don't use stdlib allocating types by accident +#[allow(dead_code)] +struct Vec; +#[allow(dead_code)] +struct Box; +#[allow(dead_code)] +struct HashMap; +#[allow(dead_code)] +struct String; + #[repr(C)] #[derive(PartialEq, Debug)] pub enum Mp4parseStatus { @@ -295,15 +305,15 @@ pub struct Mp4parseFragmentInfo { #[derive(Default)] pub struct Mp4parseParser { context: MediaContext, - opus_header: HashMap>, - pssh_data: Vec, - sample_table: HashMap>, + opus_header: TryHashMap>, + pssh_data: TryVec, + sample_table: TryHashMap>, // Store a mapping from track index (not id) to associated sample // descriptions. Because each track has a variable number of sample // descriptions, and because we need the data to live long enough to be // copied out by callers, we store these on the parser struct. - audio_track_sample_descriptions: HashMap>, - video_track_sample_descriptions: HashMap>, + audio_track_sample_descriptions: TryHashMap>, + video_track_sample_descriptions: TryHashMap>, } /// A unified interface for the parsers which have different contexts, but @@ -328,18 +338,6 @@ impl Mp4parseParser { fn context_mut(&mut self) -> &mut MediaContext { &mut self.context } - - fn opus_header_mut(&mut self) -> &mut HashMap> { - &mut self.opus_header - } - - fn pssh_data_mut(&mut self) -> &mut Vec { - &mut self.pssh_data - } - - fn sample_table_mut(&mut self) -> &mut HashMap> { - &mut self.sample_table - } } impl ContextParser for Mp4parseParser { @@ -483,8 +481,8 @@ fn mp4parse_new_common_safe( P::read(io, &mut context) .map(|_| P::with_context(context)) - .map(Box::new) - .map(Box::into_raw) + .and_then(TryBox::try_new) + .map(TryBox::into_raw) .map_err(Mp4parseStatus::from) } @@ -505,6 +503,16 @@ impl From for Mp4parseStatus { } } +impl From> for Mp4parseStatus { + fn from(result: Result<(), Mp4parseStatus>) -> Self { + match result { + Ok(()) => Mp4parseStatus::Ok, + Err(Mp4parseStatus::Ok) => unreachable!(), + Err(e) => e, + } + } +} + /// Free an `Mp4parseParser*` allocated by `mp4parse_new()`. /// /// # Safety @@ -515,7 +523,7 @@ impl From for Mp4parseStatus { #[no_mangle] pub unsafe extern "C" fn mp4parse_free(parser: *mut Mp4parseParser) { assert!(!parser.is_null()); - let _ = Box::from_raw(parser); + let _ = TryBox::from_raw(parser); } /// Free an `Mp4parseAvifParser*` allocated by `mp4parse_avif_new()`. @@ -528,7 +536,7 @@ pub unsafe extern "C" fn mp4parse_free(parser: *mut Mp4parseParser) { #[no_mangle] pub unsafe extern "C" fn mp4parse_avif_free(parser: *mut Mp4parseAvifParser) { assert!(!parser.is_null()); - let _ = Box::from_raw(parser); + let _ = TryBox::from_raw(parser); } /// Return the number of tracks parsed by previous `mp4parse_read()` call. @@ -694,34 +702,46 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( // Initialize fields to default values to ensure all fields are always valid. *info = Default::default(); - let context = (*parser).context(); + get_track_audio_info(&mut *parser, track_index, &mut *info).into() +} + +fn get_track_audio_info( + parser: &mut Mp4parseParser, + track_index: u32, + info: &mut Mp4parseTrackAudioInfo, +) -> Result<(), Mp4parseStatus> { + let Mp4parseParser { + context, + opus_header, + .. + } = parser; if track_index as usize >= context.tracks.len() { - return Mp4parseStatus::BadArg; + return Err(Mp4parseStatus::BadArg); } let track = &context.tracks[track_index as usize]; if track.track_type != TrackType::Audio { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } // Handle track.stsd let stsd = match track.stsd { Some(ref stsd) => stsd, - None => return Mp4parseStatus::Invalid, // Stsd should be present + None => return Err(Mp4parseStatus::Invalid), // Stsd should be present }; if stsd.descriptions.is_empty() { - return Mp4parseStatus::Invalid; // Should have at least 1 description + return Err(Mp4parseStatus::Invalid); // Should have at least 1 description } - let mut audio_sample_infos = Vec::with_capacity(stsd.descriptions.len()); + let mut audio_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?; for description in stsd.descriptions.iter() { let mut sample_info = Mp4parseTrackAudioSampleInfo::default(); let audio = match description { SampleEntry::Audio(a) => a, - _ => return Mp4parseStatus::Invalid, + _ => return Err(Mp4parseStatus::Invalid), }; // UNKNOWN for unsupported format. @@ -748,7 +768,7 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( match audio.codec_specific { AudioCodecSpecific::ES_Descriptor(ref esds) => { if esds.codec_esds.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } sample_info.extra_data.length = esds.codec_esds.len() as u32; sample_info.extra_data.data = esds.codec_esds.as_ptr(); @@ -772,23 +792,22 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( // Return the STREAMINFO metadata block in the codec_specific. let streaminfo = &flac.blocks[0]; if streaminfo.block_type != 0 || streaminfo.data.len() != 34 { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } sample_info.codec_specific_config.length = streaminfo.data.len() as u32; sample_info.codec_specific_config.data = streaminfo.data.as_ptr(); } AudioCodecSpecific::OpusSpecificBox(ref opus) => { - let mut v = Vec::new(); + let mut v = TryVec::new(); match serialize_opus_header(opus, &mut v) { Err(_) => { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } Ok(_) => { - let header = (*parser).opus_header_mut(); - header.insert(track_index, v); - if let Some(v) = header.get(&track_index) { + opus_header.insert(track_index, v)?; + if let Some(v) = opus_header.get(&track_index) { if v.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } sample_info.codec_specific_config.length = v.len() as u32; sample_info.codec_specific_config.data = v.as_ptr(); @@ -811,8 +830,8 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( sample_info.protected_data.scheme_type = match p.scheme_type { Some(ref scheme_type_box) => { match scheme_type_box.scheme_type.value.as_ref() { - "cenc" => Mp4ParseEncryptionSchemeType::Cenc, - "cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, + b"cenc" => Mp4ParseEncryptionSchemeType::Cenc, + b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, // We don't support other schemes, and shouldn't reach // this case. Try to gracefully handle by treating as // no encryption case. @@ -835,35 +854,32 @@ pub unsafe extern "C" fn mp4parse_get_track_audio_info( }; if let Some(ref iv_vec) = tenc.constant_iv { if iv_vec.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } sample_info.protected_data.constant_iv.set_data(iv_vec); }; } } - let res = vec_push(&mut audio_sample_infos, sample_info); - if res.is_err() { - return Mp4parseStatus::Oom; - } + audio_sample_infos.push(sample_info)?; } - (*parser) + parser .audio_track_sample_descriptions - .insert(track_index, audio_sample_infos); - match (*parser).audio_track_sample_descriptions.get(&track_index) { + .insert(track_index, audio_sample_infos)?; + match parser.audio_track_sample_descriptions.get(&track_index) { Some(sample_info) => { if sample_info.len() > std::u32::MAX as usize { // Should never happen due to upper limits on number of sample // descriptions a track can have, but lets be safe. - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } - (*info).sample_info_count = sample_info.len() as u32; - (*info).sample_info = sample_info.as_ptr(); + info.sample_info_count = sample_info.len() as u32; + info.sample_info = sample_info.as_ptr(); } - None => return Mp4parseStatus::Invalid, // Shouldn't happen, we just inserted the info! + None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info! } - Mp4parseStatus::Ok + Ok(()) } /// Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`. @@ -887,54 +903,62 @@ pub unsafe extern "C" fn mp4parse_get_track_video_info( // Initialize fields to default values to ensure all fields are always valid. *info = Default::default(); - let context = (*parser).context(); + mp4parse_get_track_video_info_safe(&mut *parser, track_index, &mut *info).into() +} + +fn mp4parse_get_track_video_info_safe( + parser: &mut Mp4parseParser, + track_index: u32, + info: &mut Mp4parseTrackVideoInfo, +) -> Result<(), Mp4parseStatus> { + let context = parser.context(); if track_index as usize >= context.tracks.len() { - return Mp4parseStatus::BadArg; + return Err(Mp4parseStatus::BadArg); } let track = &context.tracks[track_index as usize]; if track.track_type != TrackType::Video { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } // Handle track.tkhd if let Some(ref tkhd) = track.tkhd { - (*info).display_width = tkhd.width >> 16; // 16.16 fixed point - (*info).display_height = tkhd.height >> 16; // 16.16 fixed point + info.display_width = tkhd.width >> 16; // 16.16 fixed point + info.display_height = tkhd.height >> 16; // 16.16 fixed point let matrix = ( tkhd.matrix.a >> 16, tkhd.matrix.b >> 16, tkhd.matrix.c >> 16, tkhd.matrix.d >> 16, ); - (*info).rotation = match matrix { + info.rotation = match matrix { (0, 1, -1, 0) => 90, // rotate 90 degrees (-1, 0, 0, -1) => 180, // rotate 180 degrees (0, -1, 1, 0) => 270, // rotate 270 degrees _ => 0, }; } else { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } // Handle track.stsd let stsd = match track.stsd { Some(ref stsd) => stsd, - None => return Mp4parseStatus::Invalid, // Stsd should be present + None => return Err(Mp4parseStatus::Invalid), // Stsd should be present }; if stsd.descriptions.is_empty() { - return Mp4parseStatus::Invalid; // Should have at least 1 description + return Err(Mp4parseStatus::Invalid); // Should have at least 1 description } - let mut video_sample_infos = Vec::with_capacity(stsd.descriptions.len()); + let mut video_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?; for description in stsd.descriptions.iter() { let mut sample_info = Mp4parseTrackVideoSampleInfo::default(); let video = match description { SampleEntry::Video(v) => v, - _ => return Mp4parseStatus::Invalid, + _ => return Err(Mp4parseStatus::Invalid), }; // UNKNOWN for unsupported format. @@ -966,8 +990,8 @@ pub unsafe extern "C" fn mp4parse_get_track_video_info( sample_info.protected_data.scheme_type = match p.scheme_type { Some(ref scheme_type_box) => { match scheme_type_box.scheme_type.value.as_ref() { - "cenc" => Mp4ParseEncryptionSchemeType::Cenc, - "cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, + b"cenc" => Mp4ParseEncryptionSchemeType::Cenc, + b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, // We don't support other schemes, and shouldn't reach // this case. Try to gracefully handle by treating as // no encryption case. @@ -990,34 +1014,31 @@ pub unsafe extern "C" fn mp4parse_get_track_video_info( }; if let Some(ref iv_vec) = tenc.constant_iv { if iv_vec.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } sample_info.protected_data.constant_iv.set_data(iv_vec); }; } } - let res = vec_push(&mut video_sample_infos, sample_info); - if res.is_err() { - return Mp4parseStatus::Oom; - } + video_sample_infos.push(sample_info)?; } - (*parser) + parser .video_track_sample_descriptions - .insert(track_index, video_sample_infos); - match (*parser).video_track_sample_descriptions.get(&track_index) { + .insert(track_index, video_sample_infos)?; + match parser.video_track_sample_descriptions.get(&track_index) { Some(sample_info) => { if sample_info.len() > std::u32::MAX as usize { // Should never happen due to upper limits on number of sample // descriptions a track can have, but lets be safe. - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } - (*info).sample_info_count = sample_info.len() as u32; - (*info).sample_info = sample_info.as_ptr(); + info.sample_info_count = sample_info.len() as u32; + info.sample_info = sample_info.as_ptr(); } - None => return Mp4parseStatus::Invalid, // Shouldn't happen, we just inserted the info! + None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info! } - Mp4parseStatus::Ok + Ok(()) } /// Return a pointer to the primary item parsed by previous `mp4parse_avif_new()` call. @@ -1071,17 +1092,28 @@ pub unsafe extern "C" fn mp4parse_get_indice_table( // Initialize fields to default values to ensure all fields are always valid. *indices = Default::default(); - let context = (*parser).context(); + get_indice_table(&mut *parser, track_id, &mut *indices).into() +} + +fn get_indice_table( + parser: &mut Mp4parseParser, + track_id: u32, + indices: &mut Mp4parseByteData, +) -> Result<(), Mp4parseStatus> { + let Mp4parseParser { + context, + sample_table: index_table, + .. + } = parser; let tracks = &context.tracks; let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) { Some(t) => t, - _ => return Mp4parseStatus::Invalid, + _ => return Err(Mp4parseStatus::Invalid), }; - let index_table = (*parser).sample_table_mut(); if let Some(v) = index_table.get(&track_id) { - (*indices).set_indices(v); - return Mp4parseStatus::Ok; + indices.set_indices(v); + return Ok(()); } let media_time = match (&track.media_time, &track.timescale) { @@ -1105,12 +1137,12 @@ pub unsafe extern "C" fn mp4parse_get_indice_table( }; if let Some(v) = create_sample_table(track, offset_time) { - (*indices).set_indices(&v); - index_table.insert(track_id, v); - return Mp4parseStatus::Ok; + indices.set_indices(&v); + index_table.insert(track_id, v)?; + return Ok(()); } - Mp4parseStatus::Invalid + Err(Mp4parseStatus::Invalid) } // Convert a 'ctts' compact table to full table by iterator, @@ -1274,7 +1306,7 @@ impl<'a> SampleToChunkIterator<'a> { } } -fn create_sample_table(track: &Track, track_offset_time: i64) -> Option> { +fn create_sample_table(track: &Track, track_offset_time: i64) -> Option> { let timescale = match track.timescale { Some(ref t) => TrackTimeScale::(t.0 as i64, t.1), _ => return None, @@ -1291,7 +1323,7 @@ fn create_sample_table(track: &Track, track_offset_time: i64) -> Option false, }; - let mut sample_table = Vec::new(); + let mut sample_table = TryVec::new(); let mut sample_size_iter = stsz.sample_sizes.iter(); // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample @@ -1322,20 +1354,16 @@ fn create_sample_table(track: &Track, track_offset_time: i64) -> Option Option Option 0, }); - let iter = sort_table.iter(); - for i in 0..(iter.len() - 1) { - let current_index = sort_table[i]; - let peek_index = sort_table[i + 1]; - let next_start_composition_time = sample_table[peek_index].start_composition; - let sample = &mut sample_table[current_index]; - sample.end_composition = next_start_composition_time; + for indices in sort_table.windows(2) { + if let [current_index, peek_index] = *indices { + let next_start_composition_time = sample_table[peek_index].start_composition; + let sample = &mut sample_table[current_index]; + sample.end_composition = next_start_composition_time; + } } } @@ -1544,37 +1569,38 @@ pub unsafe extern "C" fn mp4parse_get_pssh_info( // Initialize fields to default values to ensure all fields are always valid. *info = Default::default(); - let context = (*parser).context_mut(); - let pssh_data = (*parser).pssh_data_mut(); - let info: &mut Mp4parsePsshInfo = &mut *info; + get_pssh_info(&mut *parser, &mut *info).into() +} + +fn get_pssh_info( + parser: &mut Mp4parseParser, + info: &mut Mp4parsePsshInfo, +) -> Result<(), Mp4parseStatus> { + let Mp4parseParser { + context, pssh_data, .. + } = parser; pssh_data.clear(); for pssh in &context.psshs { let content_len = pssh.box_content.len(); if content_len > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + return Err(Mp4parseStatus::Invalid); } - let mut data_len = Vec::new(); + let mut data_len = TryVec::new(); if data_len .write_u32::(content_len as u32) .is_err() { - return Mp4parseStatus::Io; - } - pssh_data.extend_from_slice(pssh.system_id.as_slice()); - pssh_data.extend_from_slice(data_len.as_slice()); - // The previous two calls have known, small sizes, but pssh_data has - // arbitrary size based on untrusted input, so use fallible allocation - let res = extend_from_slice(pssh_data, pssh.box_content.as_slice()); - - if res.is_err() { - return Mp4parseStatus::Oom; + return Err(Mp4parseStatus::Io); } + pssh_data.extend_from_slice(pssh.system_id.as_slice())?; + pssh_data.extend_from_slice(data_len.as_slice())?; + pssh_data.extend_from_slice(pssh.box_content.as_slice())?; } info.data.set_data(pssh_data); - Mp4parseStatus::Ok + Ok(()) } #[cfg(test)] @@ -1676,7 +1702,7 @@ fn parser_input_must_be_null() { read: Some(error_read), userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, }; - let mut parser = 0xDEADBEEF as *mut _; + let mut parser = 0xDEAD_BEEF as *mut _; let rv = unsafe { mp4parse_new(&io, &mut parser) }; assert_eq!(rv, Mp4parseStatus::BadArg); } diff --git a/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs b/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs new file mode 100644 index 000000000000..5529c9218254 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs @@ -0,0 +1,45 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(&mut buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_out_of_chunk_range() { + let mut file = std::fs::File::open("tests/chunk_out_of_range.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + // its first chunk is out of range. + // + // + // + // + // + let mut indice = Mp4parseByteData::default(); + let rv = mp4parse_get_indice_table(parser, 1, &mut indice); + assert_eq!(rv, Mp4parseStatus::Invalid); + + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_encryption.rs b/third_party/rust/mp4parse_capi/tests/test_encryption.rs new file mode 100644 index 000000000000..2ecf5945ad1d --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_encryption.rs @@ -0,0 +1,200 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(&mut buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +#[allow(clippy::cognitive_complexity)] // TODO: Consider simplifying this +fn parse_cenc() { + let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 2); + + // Make sure we have a video track and it's at index 0 + let mut video_track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut video_track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video); + + // Make sure we have a audio track and it's at index 1 + let mut audio_track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 1, &mut audio_track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio_track_info.track_type, Mp4parseTrackType::Audio); + + // Verify video track and crypto information + let mut video = Mp4parseTrackVideoInfo::default(); + rv = mp4parse_get_track_video_info(parser, 0, &mut video); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video.sample_info_count, 1); + assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Avc); + assert_eq!((*video.sample_info).image_width, 320); + assert_eq!((*video.sample_info).image_height, 240); + let protected_data = &(*video.sample_info).protected_data; + assert_eq!( + protected_data.scheme_type, + Mp4ParseEncryptionSchemeType::Cenc + ); + assert_eq!(protected_data.is_encrypted, 0x01); + assert_eq!(protected_data.iv_size, 16); + assert_eq!(protected_data.kid.length, 16); + let expected_kid = [ + 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, + 0x1d, 0x01, + ]; + for (i, expected_byte) in expected_kid.iter().enumerate() { + assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte); + } + assert_eq!(protected_data.crypt_byte_block, 0); + assert_eq!(protected_data.skip_byte_block, 0); + assert_eq!(protected_data.constant_iv.length, 0); + + // Verify audio track and crypto information + let mut audio = Mp4parseTrackAudioInfo::default(); + rv = mp4parse_get_track_audio_info(parser, 1, &mut audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio.sample_info_count, 1); + assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Aac); + assert_eq!((*audio.sample_info).channels, 2); + assert_eq!((*audio.sample_info).bit_depth, 16); + assert_eq!((*audio.sample_info).sample_rate, 44100); + let protected_data = &(*audio.sample_info).protected_data; + assert_eq!(protected_data.is_encrypted, 0x01); + assert_eq!(protected_data.iv_size, 16); + assert_eq!(protected_data.kid.length, 16); + let expected_kid = [ + 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, + 0x1d, 0x02, + ]; + for (i, expected_byte) in expected_kid.iter().enumerate() { + assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte); + } + assert_eq!(protected_data.crypt_byte_block, 0); + assert_eq!(protected_data.skip_byte_block, 0); + assert_eq!(protected_data.constant_iv.length, 0); + } +} + +#[test] +fn parse_cbcs() { + let mut file = std::fs::File::open("tests/bipbop_cbcs_video_init.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + // Make sure we have a video track + let mut video_track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut video_track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video); + + // Verify video track and crypto information + let mut video = Mp4parseTrackVideoInfo::default(); + rv = mp4parse_get_track_video_info(parser, 0, &mut video); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video.sample_info_count, 2); + assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Avc); + assert_eq!((*video.sample_info).image_width, 400); + assert_eq!((*video.sample_info).image_height, 300); + let protected_data = &(*video.sample_info).protected_data; + assert_eq!( + protected_data.scheme_type, + Mp4ParseEncryptionSchemeType::Cbcs + ); + assert_eq!(protected_data.is_encrypted, 0x01); + assert_eq!(protected_data.iv_size, 0); + assert_eq!(protected_data.kid.length, 16); + let expected_kid = [ + 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, + 0x1d, 0x21, + ]; + for (i, expected_byte) in expected_kid.iter().enumerate() { + assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte); + } + assert_eq!(protected_data.crypt_byte_block, 1); + assert_eq!(protected_data.skip_byte_block, 9); + assert_eq!(protected_data.constant_iv.length, 16); + let expected_iv = [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, + ]; + for (i, expected_byte) in expected_iv.iter().enumerate() { + assert_eq!(&(*protected_data.constant_iv.data.add(i)), expected_byte); + } + } +} + +#[test] +fn parse_unencrypted() { + // Ensure the encryption related data is not populated for files without + // encryption metadata. + let mut file = std::fs::File::open("tests/opus_audioinit.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + + let mut audio = Mp4parseTrackAudioInfo::default(); + rv = mp4parse_get_track_audio_info(parser, 0, &mut audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio.sample_info_count, 1); + let protected_data = &(*audio.sample_info).protected_data; + assert_eq!( + protected_data.scheme_type, + Mp4ParseEncryptionSchemeType::None + ); + assert_eq!(protected_data.is_encrypted, 0x00); + assert_eq!(protected_data.iv_size, 0); + assert_eq!(protected_data.kid.length, 0); + assert_eq!(protected_data.crypt_byte_block, 0); + assert_eq!(protected_data.skip_byte_block, 0); + assert_eq!(protected_data.constant_iv.length, 0); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_fragment.rs b/third_party/rust/mp4parse_capi/tests/test_fragment.rs new file mode 100644 index 000000000000..5ded45fd3dfb --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_fragment.rs @@ -0,0 +1,117 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(&mut buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_fragment() { + let mut file = std::fs::File::open("tests/bipbop_audioinit.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + assert_eq!(track_info.track_id, 1); + assert_eq!(track_info.duration, 0); + assert_eq!(track_info.media_time, 0); + + let mut audio = Default::default(); + rv = mp4parse_get_track_audio_info(parser, 0, &mut audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio.sample_info_count, 1); + + assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Aac); + assert_eq!((*audio.sample_info).channels, 2); + assert_eq!((*audio.sample_info).bit_depth, 16); + assert_eq!((*audio.sample_info).sample_rate, 22050); + assert_eq!((*audio.sample_info).extra_data.length, 27); + assert_eq!((*audio.sample_info).codec_specific_config.length, 2); + + let mut is_fragmented_file: u8 = 0; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 1); + + let mut fragment_info = Mp4parseFragmentInfo::default(); + rv = mp4parse_get_fragment_info(parser, &mut fragment_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(fragment_info.fragment_duration, 10_032_000); + + mp4parse_free(parser); + } +} + +#[test] +fn parse_opus_fragment() { + let mut file = std::fs::File::open("tests/opus_audioinit.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + assert_eq!(track_info.track_id, 1); + assert_eq!(track_info.duration, 0); + assert_eq!(track_info.media_time, 0); + + let mut audio = Default::default(); + rv = mp4parse_get_track_audio_info(parser, 0, &mut audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio.sample_info_count, 1); + + assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Opus); + assert_eq!((*audio.sample_info).channels, 1); + assert_eq!((*audio.sample_info).bit_depth, 16); + assert_eq!((*audio.sample_info).sample_rate, 48000); + assert_eq!((*audio.sample_info).extra_data.length, 0); + assert_eq!((*audio.sample_info).codec_specific_config.length, 19); + + let mut is_fragmented_file: u8 = 0; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 1); + + let mut fragment_info = Mp4parseFragmentInfo::default(); + rv = mp4parse_get_fragment_info(parser, &mut fragment_info); + assert_eq!(rv, Mp4parseStatus::Ok); + + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_rotation.rs b/third_party/rust/mp4parse_capi/tests/test_rotation.rs new file mode 100644 index 000000000000..45991c6adaa6 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_rotation.rs @@ -0,0 +1,41 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(&mut buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_rotation() { + let mut file = std::fs::File::open("tests/video_rotation_90.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut video = Mp4parseTrackVideoInfo::default(); + + let rv = mp4parse_get_track_video_info(parser, 0, &mut video); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video.rotation, 90); + + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_sample_table.rs b/third_party/rust/mp4parse_capi/tests/test_sample_table.rs new file mode 100644 index 000000000000..d86ce8eda64c --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_sample_table.rs @@ -0,0 +1,278 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(&mut buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_sample_table() { + let mut file = + std::fs::File::open("tests/bipbop_nonfragment_header.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 2); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 1, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + + // Check audio smaple table + let mut is_fragmented_file: u8 = 0; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 0); + + let mut indice = Mp4parseByteData::default(); + rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice); + assert_eq!(rv, Mp4parseStatus::Ok); + + // Compare the value from stagefright. + let audio_indice_0 = Mp4parseIndice { + start_offset: 27_046, + end_offset: 27_052, + start_composition: 0, + end_composition: 46_439, + start_decode: 0, + sync: true, + }; + let audio_indice_215 = Mp4parseIndice { + start_offset: 283_550, + end_offset: 283_556, + start_composition: 9_984_580, + end_composition: 10_031_020, + start_decode: 9_984_580, + sync: true, + }; + assert_eq!(indice.length, 216); + assert_eq!(*indice.indices.offset(0), audio_indice_0); + assert_eq!(*indice.indices.offset(215), audio_indice_215); + + // Check video smaple table + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Video); + + let mut is_fragmented_file: u8 = 0; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 0); + + let mut indice = Mp4parseByteData::default(); + rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice); + assert_eq!(rv, Mp4parseStatus::Ok); + + // Compare the last few data from stagefright. + let video_indice_291 = Mp4parseIndice { + start_offset: 280_226, + end_offset: 280_855, + start_composition: 9_838_333, + end_composition: 9_871_677, + start_decode: 9_710_000, + sync: false, + }; + let video_indice_292 = Mp4parseIndice { + start_offset: 280_855, + end_offset: 281_297, + start_composition: 9_805_011, + end_composition: 9_838_333, + start_decode: 9_710_011, + sync: false, + }; + // TODO: start_composition time in stagefright is 9905000, but it is 9904999 in parser, it + // could be rounding error. + //let video_indice_293 = Mp4parseIndice { start_offset: 281_297, end_offset: 281_919, start_composition: 9_905_000, end_composition: 9_938_344, start_decode: 9_776_666, sync: false }; + //let video_indice_294 = Mp4parseIndice { start_offset: 281_919, end_offset: 282_391, start_composition: 9_871_677, end_composition: 9_905_000, start_decode: 9_776_677, sync: false }; + let video_indice_295 = Mp4parseIndice { + start_offset: 282_391, + end_offset: 283_032, + start_composition: 9_971_666, + end_composition: 9_971_677, + start_decode: 9_843_333, + sync: false, + }; + let video_indice_296 = Mp4parseIndice { + start_offset: 283_092, + end_offset: 283_526, + start_composition: 9_938_344, + end_composition: 9_971_666, + start_decode: 9_843_344, + sync: false, + }; + + assert_eq!(indice.length, 297); + assert_eq!(*indice.indices.offset(291), video_indice_291); + assert_eq!(*indice.indices.offset(292), video_indice_292); + //assert_eq!(*indice.indices.offset(293), video_indice_293); + //assert_eq!(*indice.indices.offset(294), video_indice_294); + assert_eq!(*indice.indices.offset(295), video_indice_295); + assert_eq!(*indice.indices.offset(296), video_indice_296); + + mp4parse_free(parser); + } +} + +#[test] +fn parse_sample_table_with_elst() { + let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 2); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 1, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + + // Check audio sample table + let mut is_fragmented_file: u8 = std::u8::MAX; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 0); + + let mut indice = Mp4parseByteData::default(); + rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice); + assert_eq!(rv, Mp4parseStatus::Ok); + + // Compare the value from stagefright. + // Due to 'elst', the start_composition and end_composition are negative + // at first two samples. + let audio_indice_0 = Mp4parseIndice { + start_offset: 6992, + end_offset: 7363, + start_composition: -36281, + end_composition: -13062, + start_decode: 0, + sync: true, + }; + let audio_indice_1 = Mp4parseIndice { + start_offset: 7363, + end_offset: 7735, + start_composition: -13062, + end_composition: 10158, + start_decode: 23219, + sync: true, + }; + let audio_indice_2 = Mp4parseIndice { + start_offset: 7735, + end_offset: 8106, + start_composition: 10158, + end_composition: 33378, + start_decode: 46439, + sync: true, + }; + assert_eq!(indice.length, 21); + assert_eq!(*indice.indices.offset(0), audio_indice_0); + assert_eq!(*indice.indices.offset(1), audio_indice_1); + assert_eq!(*indice.indices.offset(2), audio_indice_2); + + mp4parse_free(parser); + } +} + +#[test] +fn parse_sample_table_with_negative_ctts() { + let mut file = std::fs::File::open("tests/white.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Video); + + let mut is_fragmented_file: u8 = std::u8::MAX; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 0); + + let mut indice = Mp4parseByteData::default(); + rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice); + assert_eq!(rv, Mp4parseStatus::Ok); + + // There are negative value in 'ctts' table. + let video_indice_0 = Mp4parseIndice { + start_offset: 48, + end_offset: 890, + start_composition: 0, + end_composition: 33_333, + start_decode: 0, + sync: true, + }; + let video_indice_1 = Mp4parseIndice { + start_offset: 890, + end_offset: 913, + start_composition: 133_333, + end_composition: 166_666, + start_decode: 33_333, + sync: false, + }; + let video_indice_2 = Mp4parseIndice { + start_offset: 913, + end_offset: 934, + start_composition: 66_666, + end_composition: 100_000, + start_decode: 66_666, + sync: false, + }; + let video_indice_3 = Mp4parseIndice { + start_offset: 934, + end_offset: 955, + start_composition: 33_333, + end_composition: 66_666, + start_decode: 100_000, + sync: false, + }; + assert_eq!(indice.length, 300); + assert_eq!(*indice.indices.offset(0), video_indice_0); + assert_eq!(*indice.indices.offset(1), video_indice_1); + assert_eq!(*indice.indices.offset(2), video_indice_2); + assert_eq!(*indice.indices.offset(3), video_indice_3); + + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs b/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs new file mode 100644 index 000000000000..b77da23b6db8 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs @@ -0,0 +1,46 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(&mut buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_invalid_stsc_table() { + let mut file = std::fs::File::open("tests/zero_empty_stsc.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let rv = mp4parse_new(&io, &mut parser); + + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + let rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 2); + + let mut indice_video = Mp4parseByteData::default(); + let rv = mp4parse_get_indice_table(parser, 1, &mut indice_video); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(indice_video.length, 1040); + + let mut indice_audio = Mp4parseByteData::default(); + let rv = mp4parse_get_indice_table(parser, 2, &mut indice_audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(indice_audio.length, 1952); + + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_fallible/.cargo-checksum.json b/third_party/rust/mp4parse_fallible/.cargo-checksum.json deleted file mode 100644 index f0dc856fb760..000000000000 --- a/third_party/rust/mp4parse_fallible/.cargo-checksum.json +++ /dev/null @@ -1 +0,0 @@ -{"files":{"CODE_OF_CONDUCT.md":"902d5357af363426631d907e641e220b3ec89039164743f8442b3f120479b7cf","Cargo.toml":"2edecc4249f6ff011255fe3c92892050e466246b23ee6d002243b0df760625c7","LICENSE":"fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85","README":"6f28a5c89ff7c018760402038a991a581771b8f66869268a7288f64915f192a6","lib.rs":"70f5bec52c586809882a1edce407552e8a5c3d0f126eaa92abd676484395cfc8"},"package":"704f773471ac3e7110427b6bdf93184932b19319c9b7717688da5424e519b10a"} \ No newline at end of file diff --git a/third_party/rust/mp4parse_fallible/CODE_OF_CONDUCT.md b/third_party/rust/mp4parse_fallible/CODE_OF_CONDUCT.md deleted file mode 100644 index 498baa3fb0f0..000000000000 --- a/third_party/rust/mp4parse_fallible/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,15 +0,0 @@ -# Community Participation Guidelines - -This repository is governed by Mozilla's code of conduct and etiquette guidelines. -For more details, please read the -[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). - -## How to Report -For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. - - diff --git a/third_party/rust/mp4parse_fallible/Cargo.toml b/third_party/rust/mp4parse_fallible/Cargo.toml deleted file mode 100644 index c1e1cf4b2495..000000000000 --- a/third_party/rust/mp4parse_fallible/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies -# -# If you believe there's an error in this file please file an -# issue against the rust-lang/cargo repository. If you're -# editing this file be aware that the upstream Cargo.toml -# will likely look very different (and much more reasonable) - -[package] -name = "mp4parse_fallible" -version = "0.0.3" -authors = ["The Servo Project Developers"] -description = "Fallible replacement for Vec" -documentation = "https://docs.rs/mp4parse_fallible/" -license = "MPL-2.0" -repository = "https://github.com/mozilla/mp4parse_fallible" - -[lib] -name = "mp4parse_fallible" -path = "lib.rs" -[badges.travis-ci] -repository = "https://github.com/mozilla/mp4parse_fallible" diff --git a/third_party/rust/mp4parse_fallible/LICENSE b/third_party/rust/mp4parse_fallible/LICENSE deleted file mode 100644 index 14e2f777f6c3..000000000000 --- a/third_party/rust/mp4parse_fallible/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/third_party/rust/mp4parse_fallible/README b/third_party/rust/mp4parse_fallible/README deleted file mode 100644 index fa27b5daeb04..000000000000 --- a/third_party/rust/mp4parse_fallible/README +++ /dev/null @@ -1,7 +0,0 @@ -This is from https://github.com/servo/servo/tree/master/components/fallible -with modification for mp4 demuxer. - -The purpose of this crate is to solve infallible memory allocation problem -which causes OOM easily on win32. This is more like a temporary solution. -Once rust supports fallible memory allocation in its stdlib, this can be -retired. diff --git a/third_party/rust/mp4parse_fallible/lib.rs b/third_party/rust/mp4parse_fallible/lib.rs deleted file mode 100644 index d82267882c92..000000000000 --- a/third_party/rust/mp4parse_fallible/lib.rs +++ /dev/null @@ -1,147 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use std::mem; -use std::vec::Vec; - -extern "C" { - fn realloc(ptr: *mut u8, bytes: usize) -> *mut u8; - fn malloc(bytes: usize) -> *mut u8; -} - -pub trait FallibleVec { - /// Append |val| to the end of |vec|. Returns Ok(()) on success, - /// Err(()) if it fails, which can only be due to lack of memory. - fn try_push(&mut self, value: T) -> Result<(), ()>; - - /// Reserves capacity for at least `additional` more elements to - /// be inserted in the vector. Does nothing if capacity is already - /// sufficient. Return Ok(()) on success, Err(()) if it fails either - /// due to lack of memory, or overflowing the `usize` used to store - /// the capacity. - fn try_reserve(&mut self, additional: usize) -> Result<(), ()>; - - /// Clones and appends all elements in a slice to the Vec. - /// Returns Ok(()) on success, Err(()) if it fails, which can - /// only be due to lack of memory. - fn try_extend_from_slice(&mut self, other: &[T]) -> Result<(), ()> where T: Clone; -} - -///////////////////////////////////////////////////////////////// -// Vec - -impl FallibleVec for Vec { - #[inline] - fn try_push(&mut self, val: T) -> Result<(), ()> { - if self.capacity() == self.len() { - let old_cap: usize = self.capacity(); - let new_cap: usize - = if old_cap == 0 { 4 } else { old_cap.checked_mul(2).ok_or(()) ? }; - - try_extend_vec(self, new_cap)?; - debug_assert!(self.capacity() > self.len()); - } - self.push(val); - Ok(()) - } - - #[inline] - fn try_reserve(&mut self, additional: usize) -> Result<(), ()> { - let available = self.capacity().checked_sub(self.len()).expect("capacity >= len"); - if additional > available { - let increase = additional.checked_sub(available).expect("additional > available"); - let new_cap = self.capacity().checked_add(increase).ok_or(())?; - try_extend_vec(self, new_cap)?; - debug_assert!(self.capacity() == new_cap); - } - Ok(()) - } - - #[inline] - fn try_extend_from_slice(&mut self, other: &[T]) -> Result<(), ()> where T: Clone { - FallibleVec::try_reserve(self, other.len())?; - self.extend_from_slice(other); - Ok(()) - } -} - -#[inline(never)] -#[cold] -fn try_extend_vec(vec: &mut Vec, new_cap: usize) -> Result<(), ()> { - let old_ptr = vec.as_mut_ptr(); - let old_len = vec.len(); - - let old_cap: usize = vec.capacity(); - - if old_cap >= new_cap { - return Ok(()); - } - - let new_size_bytes - = new_cap.checked_mul(mem::size_of::()).ok_or(()) ? ; - - let new_ptr = unsafe { - if old_cap == 0 { - malloc(new_size_bytes) - } else { - realloc(old_ptr as *mut u8, new_size_bytes) - } - }; - - if new_ptr.is_null() { - return Err(()); - } - - let new_vec = unsafe { - Vec::from_raw_parts(new_ptr as *mut T, old_len, new_cap) - }; - - mem::forget(mem::replace(vec, new_vec)); - Ok(()) -} - -#[test] -fn oom() { - let mut vec: Vec = Vec::new(); - match FallibleVec::try_reserve(&mut vec, std::usize::MAX) { - Ok(_) => panic!("it should be OOM"), - _ => (), - } -} - -#[test] -fn try_reserve() { - let mut vec = vec![1]; - let old_cap = vec.capacity(); - let new_cap = old_cap + 1; - FallibleVec::try_reserve(&mut vec, new_cap).unwrap(); - assert!(vec.capacity() >= new_cap); -} - -#[test] -fn try_reserve_idempotent() { - let mut vec = vec![1]; - let old_cap = vec.capacity(); - let new_cap = old_cap + 1; - FallibleVec::try_reserve(&mut vec, new_cap).unwrap(); - let cap_after_reserve = vec.capacity(); - FallibleVec::try_reserve(&mut vec, new_cap).unwrap(); - assert_eq!(cap_after_reserve, vec.capacity()); -} - -#[test] -fn capacity_overflow() { - let mut vec = vec![1]; - match FallibleVec::try_reserve(&mut vec, std::usize::MAX) { - Ok(_) => panic!("capacity calculation should overflow"), - _ => (), - } -} - -#[test] -fn extend_from_slice() { - let mut vec = b"foo".to_vec(); - FallibleVec::try_extend_from_slice(&mut vec, b"bar").unwrap(); - assert_eq!(&vec, b"foobar"); -} diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index 36296cf193ec..6608707f0369 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -9,7 +9,7 @@ description = "Shared Rust code for libxul" geckoservo = { path = "../../../../servo/ports/geckolib" } kvstore = { path = "../../../components/kvstore" } lmdb-rkv-sys = { version = "0.11", features = ["mdb_idl_logn_9"] } -mp4parse_capi = { path = "../../../../media/mp4parse-rust/mp4parse_capi" } +mp4parse_capi = { git = "https://github.com/mozilla/mp4parse-rust", rev = "0dc3e6e7c5371fe21f69b847f61c65fe6d6dc317" } nserror = { path = "../../../../xpcom/rust/nserror" } nsstring = { path = "../../../../xpcom/rust/nsstring" } netwerk_helper = { path = "../../../../netwerk/base/rust-helper" } diff --git a/tools/lint/clippy.yml b/tools/lint/clippy.yml index 12a8356371fc..62bc5913cfae 100644 --- a/tools/lint/clippy.yml +++ b/tools/lint/clippy.yml @@ -17,8 +17,6 @@ clippy: - js/src/rust/shared/ - js/src/wasm/cranelift/ - media/audioipc/ - - media/mp4parse-rust/mp4parse/ - - media/mp4parse-rust/mp4parse_capi/ - modules/libpref/init/static_prefs/ - mozglue/static/rust/ - netwerk/base/mozurl/ @@ -39,6 +37,8 @@ clippy: - testing/mozbase/rust/mozrunner/ - testing/mozbase/rust/mozversion/ - testing/webdriver/ + - third_party/rust/mp4parse/ + - third_party/rust/mp4parse_capi/ - toolkit/components/kvstore/ - toolkit/components/glean/ - toolkit/components/xulstore/tests/gtest/