Bug 1267997 - Update source for mp4parse v0.4.0. r=kinetik draft
authorRalph Giles <giles@mozilla.com>
Tue, 28 Jun 2016 13:43:08 -0700
changeset 382113 4d4a010fe15bf416f9270495468d75af47e64947
parent 382112 eba90c68e5d8ea3bc2ee266c084ea282bccd8f35
child 382114 7505a8d10c5e554c2315621fc3eef5a1b8a22819
push id21620
push userbmo:giles@thaumas.net
push dateTue, 28 Jun 2016 22:04:53 +0000
reviewerskinetik
bugs1267997
milestone50.0a1
Bug 1267997 - Update source for mp4parse v0.4.0. r=kinetik Result of running the update script. MozReview-Commit-ID: AE6jXz8IMU7
media/libstagefright/binding/include/mp4parse.h
media/libstagefright/binding/mp4parse/byteorder/mod.rs
media/libstagefright/binding/mp4parse/byteorder/new.rs
media/libstagefright/binding/mp4parse/capi.rs
media/libstagefright/binding/mp4parse/lib.rs
media/libstagefright/binding/mp4parse/tests.rs
--- a/media/libstagefright/binding/include/mp4parse.h
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -1,68 +1,103 @@
-// 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 https://mozilla.org/MPL/2.0/.
 
-#ifndef _MP4PARSE_RUST_H
-#define _MP4PARSE_RUST_H
+#ifndef cheddar_generated_mp4parse_h
+#define cheddar_generated_mp4parse_h
+
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-struct mp4parse_state;
+#include <stdint.h>
+#include <stdbool.h>
+
+// THIS FILE IS AUTOGENERATED BY mp4parse-rust/build.rs - DO NOT EDIT
+
+// 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 https://mozilla.org/MPL/2.0/.
 
-#define MP4PARSE_OK 0
-#define MP4PARSE_ERROR_BADARG 1      // Argument validation failure
-#define MP4PARSE_ERROR_INVALID 2     // Error::InvalidData
-#define MP4PARSE_ERROR_UNSUPPORTED 3 // Error::Unsupported
-#define MP4PARSE_ERROR_EOF 4         // Error::UnexpectedEOF
-#define MP4PARSE_ASSERT 5            // Error::AssertCaught
-#define MP4PARSE_ERROR_IO 6          // Error::Io(_)
+typedef enum mp4parse_error {
+	MP4PARSE_OK = 0,
+	MP4PARSE_ERROR_BADARG = 1,
+	MP4PARSE_ERROR_INVALID = 2,
+	MP4PARSE_ERROR_UNSUPPORTED = 3,
+	MP4PARSE_ERROR_EOF = 4,
+	MP4PARSE_ERROR_IO = 5,
+} mp4parse_error;
 
-#define MP4PARSE_TRACK_TYPE_H264 0 // "video/avc"
-#define MP4PARSE_TRACK_TYPE_AAC  1 // "audio/mp4a-latm"
+typedef enum mp4parse_track_type {
+	MP4PARSE_TRACK_TYPE_VIDEO = 0,
+	MP4PARSE_TRACK_TYPE_AUDIO = 1,
+} mp4parse_track_type;
+
+typedef enum mp4parse_codec {
+	MP4PARSE_CODEC_UNKNOWN,
+	MP4PARSE_CODEC_AAC,
+	MP4PARSE_CODEC_OPUS,
+	MP4PARSE_CODEC_AVC,
+	MP4PARSE_CODEC_VP9,
+} mp4parse_codec;
 
-#define MP4PARSE_TRACK_CODEC_UNKNOWN 0
-#define MP4PARSE_TRACK_CODEC_AAC 1
-#define MP4PARSE_TRACK_CODEC_OPUS 2
-#define MP4PARSE_TRACK_CODEC_H264 3
-#define MP4PARSE_TRACK_CODEC_VP9 4
+typedef struct mp4parse_track_info {
+	mp4parse_track_type track_type;
+	mp4parse_codec codec;
+	uint32_t track_id;
+	uint64_t duration;
+	int64_t media_time;
+} mp4parse_track_info;
 
-struct mp4parse_track_audio_info {
-  uint16_t channels;
-  uint16_t bit_depth;
-  uint32_t sample_rate;
-};
+typedef struct mp4parse_codec_specific_config {
+	uint32_t length;
+	uint8_t const* data;
+} mp4parse_codec_specific_config;
 
-struct mp4parse_track_video_info {
-  uint32_t display_width;
-  uint32_t display_height;
-  uint16_t image_width;
-  uint16_t image_height;
-};
+typedef struct mp4parse_track_audio_info {
+	uint16_t channels;
+	uint16_t bit_depth;
+	uint32_t sample_rate;
+	mp4parse_codec_specific_config codec_specific_config;
+} mp4parse_track_audio_info;
+
+typedef struct mp4parse_track_video_info {
+	uint32_t display_width;
+	uint32_t display_height;
+	uint16_t image_width;
+	uint16_t image_height;
+} mp4parse_track_video_info;
+
+typedef struct mp4parse_parser mp4parse_parser;
+
+typedef struct mp4parse_io {
+	intptr_t (*read)(uint8_t* buffer, uintptr_t size, void* userdata);
+	void* userdata;
+} mp4parse_io;
 
-struct mp4parse_track_info {
-  uint32_t track_type;
-  uint32_t track_id;
-  uint64_t duration;
-  int64_t media_time;
-};
+/// Allocate an `mp4parse_parser*` to read from the supplied `mp4parse_io`.
+mp4parse_parser* mp4parse_new(mp4parse_io const* io);
 
-struct mp4parse_state* mp4parse_new(void);
-void mp4parse_free(struct mp4parse_state* state);
+/// Free an `mp4parse_parser*` allocated by `mp4parse_new()`.
+void mp4parse_free(mp4parse_parser* parser);
+
+/// Run the `mp4parse_parser*` allocated by `mp4parse_new()` until EOF or error.
+mp4parse_error mp4parse_read(mp4parse_parser* parser);
 
-int32_t mp4parse_read(struct mp4parse_state* state, uint8_t *buffer, size_t size);
+/// Return the number of tracks parsed by previous `mp4parse_read()` call.
+mp4parse_error mp4parse_get_track_count(mp4parse_parser const* parser, uint32_t* count);
 
-uint32_t mp4parse_get_track_count(struct mp4parse_state* state);
+/// Fill the supplied `mp4parse_track_info` with metadata for `track`.
+mp4parse_error mp4parse_get_track_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_info* info);
 
-int32_t mp4parse_get_track_info(struct mp4parse_state* state, uint32_t track, struct mp4parse_track_info* track_info);
+/// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
+mp4parse_error mp4parse_get_track_audio_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_audio_info* info);
 
-int32_t mp4parse_get_track_audio_info(struct mp4parse_state* state, uint32_t track, struct mp4parse_track_audio_info* track_info);
+/// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
+mp4parse_error mp4parse_get_track_video_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_video_info* info);
 
-int32_t mp4parse_get_track_video_info(struct mp4parse_state* state, uint32_t track, struct mp4parse_track_video_info* track_info);
+
 
 #ifdef __cplusplus
 }
 #endif
 
+
 #endif
--- a/media/libstagefright/binding/mp4parse/byteorder/mod.rs
+++ b/media/libstagefright/binding/mp4parse/byteorder/mod.rs
@@ -38,21 +38,19 @@ assert_eq!(wtr, vec![5, 2, 0, 3]);
 
 #![doc(html_root_url = "http://burntsushi.net/rustdoc/byteorder")]
 
 #![deny(missing_docs)]
 
 use std::mem::transmute;
 use std::ptr::copy_nonoverlapping;
 
-#[cfg(not(feature = "no-std"))]
-pub use byteorder::new::{ReadBytesExt, WriteBytesExt, Error, Result};
+pub use byteorder::new::{ReadBytesExt, WriteBytesExt};
 
-#[cfg(not(feature = "no-std"))]
-// Re-export new so gecko can build us as a mod intead of a crate.
+// Re-export new so gecko can build us as a mod instead of a crate.
 pub mod new;
 
 #[inline]
 fn extend_sign(val: u64, nbytes: usize) -> i64 {
     let shift = (8 - nbytes) * 8;
     (val << shift) as i64 >> shift
 }
 
@@ -262,36 +260,54 @@ pub trait ByteOrder {
 #[allow(missing_copy_implementations)] pub enum BigEndian {}
 
 /// Defines little-endian serialization.
 ///
 /// Note that this type has no value constructor. It is used purely at the
 /// type level.
 #[allow(missing_copy_implementations)] pub enum LittleEndian {}
 
+/// Defines network byte order serialization.
+///
+/// Network byte order is defined by [RFC 1700][1] to be big-endian, and is
+/// referred to in several protocol specifications.  This type is an alias of
+/// BigEndian.
+///
+/// [1]: https://tools.ietf.org/html/rfc1700
+///
+/// Note that this type has no value constructor. It is used purely at the
+/// type level.
+pub type NetworkEndian = BigEndian;
+
 /// Defines system native-endian serialization.
 ///
 /// Note that this type has no value constructor. It is used purely at the
 /// type level.
 #[cfg(target_endian = "little")]
 pub type NativeEndian = LittleEndian;
 
 /// Defines system native-endian serialization.
 ///
 /// Note that this type has no value constructor. It is used purely at the
 /// type level.
 #[cfg(target_endian = "big")]
 pub type NativeEndian = BigEndian;
 
 macro_rules! read_num_bytes {
     ($ty:ty, $size:expr, $src:expr, $which:ident) => ({
+        assert!($size == ::std::mem::size_of::<$ty>());
         assert!($size <= $src.len());
+        let mut data: $ty = 0;
         unsafe {
-            (*($src.as_ptr() as *const $ty)).$which()
+            copy_nonoverlapping(
+                $src.as_ptr(),
+                &mut data as *mut $ty as *mut u8,
+                $size);
         }
+        data.$which()
     });
 }
 
 macro_rules! write_num_bytes {
     ($ty:ty, $size:expr, $n:expr, $dst:expr, $which:ident) => ({
         assert!($size <= $dst.len());
         unsafe {
             // N.B. https://github.com/rust-lang/rust/issues/22776
--- a/media/libstagefright/binding/mp4parse/byteorder/new.rs
+++ b/media/libstagefright/binding/mp4parse/byteorder/new.rs
@@ -1,75 +1,12 @@
-use std::error;
-use std::fmt;
-use std::io;
-use std::result;
+use std::io::{self, Result};
 
 use byteorder::ByteOrder;
 
-/// A short-hand for `result::Result<T, byteorder::Error>`.
-pub type Result<T> = result::Result<T, Error>;
-
-/// An error type for reading bytes.
-///
-/// This is a thin wrapper over the standard `io::Error` type. Namely, it
-/// adds one additional error case: an unexpected EOF.
-///
-/// Note that this error is also used for the `write` methods to keep things
-/// consistent.
-#[derive(Debug)]
-pub enum Error {
-    /// An unexpected EOF.
-    ///
-    /// This occurs when a call to the underlying reader returns `0` bytes,
-    /// but more bytes are required to decode a meaningful value.
-    UnexpectedEOF,
-    /// Any underlying IO error that occurs while reading bytes.
-    Io(io::Error),
-}
-
-impl From<io::Error> for Error {
-    fn from(err: io::Error) -> Error { Error::Io(err) }
-}
-
-impl From<Error> for io::Error {
-    fn from(err: Error) -> io::Error {
-        match err {
-            Error::Io(err) => err,
-            Error::UnexpectedEOF => io::Error::new(io::ErrorKind::Other,
-                                                   "unexpected EOF")
-        }
-    }
-}
-
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self {
-            Error::UnexpectedEOF => write!(f, "Unexpected end of file."),
-            Error::Io(ref err) => err.fmt(f),
-        }
-    }
-}
-
-impl error::Error for Error {
-    fn description(&self) -> &str {
-        match *self {
-            Error::UnexpectedEOF => "Unexpected end of file.",
-            Error::Io(ref err) => error::Error::description(err),
-        }
-    }
-
-    fn cause(&self) -> Option<&error::Error> {
-        match *self {
-            Error::UnexpectedEOF => None,
-            Error::Io(ref err) => err.cause(),
-        }
-    }
-}
-
 /// Extends `Read` with methods for reading numbers. (For `std::io`.)
 ///
 /// Most of the methods defined here have an unconstrained type parameter that
 /// must be explicitly instantiated. Typically, it is instantiated with either
 /// the `BigEndian` or `LittleEndian` types defined in this crate.
 ///
 /// # Examples
 ///
@@ -86,135 +23,118 @@ impl error::Error for Error {
 pub trait ReadBytesExt: io::Read {
     /// Reads an unsigned 8 bit integer from the underlying reader.
     ///
     /// Note that since this reads a single byte, no byte order conversions
     /// are used. It is included for completeness.
     #[inline]
     fn read_u8(&mut self) -> Result<u8> {
         let mut buf = [0; 1];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(buf[0])
     }
 
     /// Reads a signed 8 bit integer from the underlying reader.
     ///
     /// Note that since this reads a single byte, no byte order conversions
     /// are used. It is included for completeness.
     #[inline]
     fn read_i8(&mut self) -> Result<i8> {
         let mut buf = [0; 1];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(buf[0] as i8)
     }
 
     /// Reads an unsigned 16 bit integer from the underlying reader.
     #[inline]
     fn read_u16<T: ByteOrder>(&mut self) -> Result<u16> {
         let mut buf = [0; 2];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(T::read_u16(&buf))
     }
 
     /// Reads a signed 16 bit integer from the underlying reader.
     #[inline]
     fn read_i16<T: ByteOrder>(&mut self) -> Result<i16> {
         let mut buf = [0; 2];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(T::read_i16(&buf))
     }
 
     /// Reads an unsigned 32 bit integer from the underlying reader.
     #[inline]
     fn read_u32<T: ByteOrder>(&mut self) -> Result<u32> {
         let mut buf = [0; 4];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(T::read_u32(&buf))
     }
 
     /// Reads a signed 32 bit integer from the underlying reader.
     #[inline]
     fn read_i32<T: ByteOrder>(&mut self) -> Result<i32> {
         let mut buf = [0; 4];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(T::read_i32(&buf))
     }
 
     /// Reads an unsigned 64 bit integer from the underlying reader.
     #[inline]
     fn read_u64<T: ByteOrder>(&mut self) -> Result<u64> {
         let mut buf = [0; 8];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(T::read_u64(&buf))
     }
 
     /// Reads a signed 64 bit integer from the underlying reader.
     #[inline]
     fn read_i64<T: ByteOrder>(&mut self) -> Result<i64> {
         let mut buf = [0; 8];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(T::read_i64(&buf))
     }
 
     /// Reads an unsigned n-bytes integer from the underlying reader.
     #[inline]
     fn read_uint<T: ByteOrder>(&mut self, nbytes: usize) -> Result<u64> {
         let mut buf = [0; 8];
-        try!(read_full(self, &mut buf[..nbytes]));
+        try!(self.read_exact(&mut buf[..nbytes]));
         Ok(T::read_uint(&buf[..nbytes], nbytes))
     }
 
     /// Reads a signed n-bytes integer from the underlying reader.
     #[inline]
     fn read_int<T: ByteOrder>(&mut self, nbytes: usize) -> Result<i64> {
         let mut buf = [0; 8];
-        try!(read_full(self, &mut buf[..nbytes]));
+        try!(self.read_exact(&mut buf[..nbytes]));
         Ok(T::read_int(&buf[..nbytes], nbytes))
     }
 
     /// Reads a IEEE754 single-precision (4 bytes) floating point number from
     /// the underlying reader.
     #[inline]
     fn read_f32<T: ByteOrder>(&mut self) -> Result<f32> {
         let mut buf = [0; 4];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(T::read_f32(&buf))
     }
 
     /// Reads a IEEE754 double-precision (8 bytes) floating point number from
     /// the underlying reader.
     #[inline]
     fn read_f64<T: ByteOrder>(&mut self) -> Result<f64> {
         let mut buf = [0; 8];
-        try!(read_full(self, &mut buf));
+        try!(self.read_exact(&mut buf));
         Ok(T::read_f64(&buf))
     }
 }
 
 /// All types that implement `Read` get methods defined in `ReadBytesExt`
 /// for free.
 impl<R: io::Read + ?Sized> ReadBytesExt for R {}
 
-fn read_full<R: io::Read + ?Sized>(rdr: &mut R, buf: &mut [u8]) -> Result<()> {
-    let mut nread = 0usize;
-    while nread < buf.len() {
-        match rdr.read(&mut buf[nread..]) {
-            Ok(0) => return Err(Error::UnexpectedEOF),
-            Ok(n) => nread += n,
-            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {},
-            Err(e) => return Err(From::from(e))
-        }
-    }
-    Ok(())
-}
-
-fn write_all<W: io::Write + ?Sized>(wtr: &mut W, buf: &[u8]) -> Result<()> {
-    wtr.write_all(buf).map_err(From::from)
-}
-
 /// Extends `Write` with methods for writing numbers. (For `std::io`.)
 ///
 /// Most of the methods defined here have an unconstrained type parameter that
 /// must be explicitly instantiated. Typically, it is instantiated with either
 /// the `BigEndian` or `LittleEndian` types defined in this crate.
 ///
 /// # Examples
 ///
@@ -230,120 +150,120 @@ fn write_all<W: io::Write + ?Sized>(wtr:
 /// ```
 pub trait WriteBytesExt: io::Write {
     /// Writes an unsigned 8 bit integer to the underlying writer.
     ///
     /// Note that since this writes a single byte, no byte order conversions
     /// are used. It is included for completeness.
     #[inline]
     fn write_u8(&mut self, n: u8) -> Result<()> {
-        write_all(self, &[n])
+        self.write_all(&[n])
     }
 
     /// Writes a signed 8 bit integer to the underlying writer.
     ///
     /// Note that since this writes a single byte, no byte order conversions
     /// are used. It is included for completeness.
     #[inline]
     fn write_i8(&mut self, n: i8) -> Result<()> {
-        write_all(self, &[n as u8])
+        self.write_all(&[n as u8])
     }
 
     /// Writes an unsigned 16 bit integer to the underlying writer.
     #[inline]
     fn write_u16<T: ByteOrder>(&mut self, n: u16) -> Result<()> {
         let mut buf = [0; 2];
         T::write_u16(&mut buf, n);
-        write_all(self, &buf)
+        self.write_all(&buf)
     }
 
     /// Writes a signed 16 bit integer to the underlying writer.
     #[inline]
     fn write_i16<T: ByteOrder>(&mut self, n: i16) -> Result<()> {
         let mut buf = [0; 2];
         T::write_i16(&mut buf, n);
-        write_all(self, &buf)
+        self.write_all(&buf)
     }
 
     /// Writes an unsigned 32 bit integer to the underlying writer.
     #[inline]
     fn write_u32<T: ByteOrder>(&mut self, n: u32) -> Result<()> {
         let mut buf = [0; 4];
         T::write_u32(&mut buf, n);
-        write_all(self, &buf)
+        self.write_all(&buf)
     }
 
     /// Writes a signed 32 bit integer to the underlying writer.
     #[inline]
     fn write_i32<T: ByteOrder>(&mut self, n: i32) -> Result<()> {
         let mut buf = [0; 4];
         T::write_i32(&mut buf, n);
-        write_all(self, &buf)
+        self.write_all(&buf)
     }
 
     /// Writes an unsigned 64 bit integer to the underlying writer.
     #[inline]
     fn write_u64<T: ByteOrder>(&mut self, n: u64) -> Result<()> {
         let mut buf = [0; 8];
         T::write_u64(&mut buf, n);
-        write_all(self, &buf)
+        self.write_all(&buf)
     }
 
     /// Writes a signed 64 bit integer to the underlying writer.
     #[inline]
     fn write_i64<T: ByteOrder>(&mut self, n: i64) -> Result<()> {
         let mut buf = [0; 8];
         T::write_i64(&mut buf, n);
-        write_all(self, &buf)
+        self.write_all(&buf)
     }
 
     /// Writes an unsigned n-bytes integer to the underlying writer.
     ///
     /// If the given integer is not representable in the given number of bytes,
     /// this method panics. If `nbytes > 8`, this method panics.
     #[inline]
     fn write_uint<T: ByteOrder>(
         &mut self,
         n: u64,
         nbytes: usize,
     ) -> Result<()> {
         let mut buf = [0; 8];
         T::write_uint(&mut buf, n, nbytes);
-        write_all(self, &buf[0..nbytes])
+        self.write_all(&buf[0..nbytes])
     }
 
     /// Writes a signed n-bytes integer to the underlying writer.
     ///
     /// If the given integer is not representable in the given number of bytes,
     /// this method panics. If `nbytes > 8`, this method panics.
     #[inline]
     fn write_int<T: ByteOrder>(
         &mut self,
         n: i64,
         nbytes: usize,
     ) -> Result<()> {
         let mut buf = [0; 8];
         T::write_int(&mut buf, n, nbytes);
-        write_all(self, &buf[0..nbytes])
+        self.write_all(&buf[0..nbytes])
     }
 
     /// Writes a IEEE754 single-precision (4 bytes) floating point number to
     /// the underlying writer.
     #[inline]
     fn write_f32<T: ByteOrder>(&mut self, n: f32) -> Result<()> {
         let mut buf = [0; 4];
         T::write_f32(&mut buf, n);
-        write_all(self, &buf)
+        self.write_all(&buf)
     }
 
     /// Writes a IEEE754 double-precision (8 bytes) floating point number to
     /// the underlying writer.
     #[inline]
     fn write_f64<T: ByteOrder>(&mut self, n: f64) -> Result<()> {
         let mut buf = [0; 8];
         T::write_f64(&mut buf, n);
-        write_all(self, &buf)
+        self.write_all(&buf)
     }
 }
 
 /// All types that implement `Write` get methods defined in `WriteBytesExt`
 /// for free.
 impl<W: io::Write + ?Sized> WriteBytesExt for W {}
--- a/media/libstagefright/binding/mp4parse/capi.rs
+++ b/media/libstagefright/binding/mp4parse/capi.rs
@@ -1,237 +1,354 @@
 //! C API for mp4parse module.
 //!
 //! Parses ISO Base Media Format aka video/mp4 streams.
 //!
 //! # Examples
 //!
 //! ```rust
 //! extern crate mp4parse;
-//!
-//! // Minimal valid mp4 containing no tracks.
-//! let data = b"\0\0\0\x0cftypmp42";
+//! use std::io::Read;
 //!
-//! let context = mp4parse::mp4parse_new();
+//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+//!    let mut 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,
+//!    }
+//! }
+//!
+//! let mut file = std::fs::File::open("examples/minimal.mp4").unwrap();
+//! let io = mp4parse::mp4parse_io { read: buf_read,
+//!                                  userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
 //! unsafe {
-//!     let rv = mp4parse::mp4parse_read(context, data.as_ptr(), data.len());
-//!     assert_eq!(0, rv);
-//!     mp4parse::mp4parse_free(context);
+//!     let parser = mp4parse::mp4parse_new(&io);
+//!     let rv = mp4parse::mp4parse_read(parser);
+//!     assert_eq!(rv, mp4parse::mp4parse_error::MP4PARSE_OK);
+//!     mp4parse::mp4parse_free(parser);
 //! }
 //! ```
 
 // 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 https://mozilla.org/MPL/2.0/.
 
 use std;
-use std::io::Cursor;
+use std::io::Read;
+use std::collections::HashMap;
 
 // Symbols we need from our rust api.
 use MediaContext;
 use TrackType;
 use read_mp4;
 use Error;
 use media_time_to_ms;
 use track_time_to_ms;
 use SampleEntry;
-
-// These constants *must* match those in include/mp4parse.h.
-
-/// Map Error to int32 return codes.
-const MP4PARSE_OK: i32 = 0;
-const MP4PARSE_ERROR_BADARG: i32 = 1;
-const MP4PARSE_ERROR_INVALID: i32 = 2;
-const MP4PARSE_ERROR_UNSUPPORTED: i32 = 3;
-const MP4PARSE_ERROR_EOF: i32 = 4;
-const MP4PARSE_ASSERT: i32 = 5;
-const MP4PARSE_ERROR_IO: i32 = 6;
+use AudioCodecSpecific;
+use VideoCodecSpecific;
+use serialize_opus_header;
 
-/// Map TrackType to uint32 constants.
-const TRACK_TYPE_H264: u32 = 0;
-const TRACK_TYPE_AAC: u32 = 1;
+// rusty-cheddar's C enum generation doesn't namespace enum members by
+// prefixing them, so we're forced to do it in our member names until
+// https://github.com/Sean1708/rusty-cheddar/pull/35 is fixed.  Importing
+// the members into the module namespace avoids doubling up on the
+// namespacing on the Rust side.
+use mp4parse_error::*;
+use mp4parse_track_type::*;
 
-/// Map Track mime_type to uint32 constants.
-const TRACK_CODEC_UNKNOWN: u32 = 0;
-const TRACK_CODEC_AAC: u32 = 1;
-const TRACK_CODEC_OPUS: u32 = 2;
-const TRACK_CODEC_H264: u32 = 3;
-const TRACK_CODEC_VP9: u32 = 4;
-
-// These structs *must* match those declared in include/mp4parse.h.
+#[repr(C)]
+#[derive(PartialEq, Debug)]
+pub enum mp4parse_error {
+    MP4PARSE_OK = 0,
+    MP4PARSE_ERROR_BADARG = 1,
+    MP4PARSE_ERROR_INVALID = 2,
+    MP4PARSE_ERROR_UNSUPPORTED = 3,
+    MP4PARSE_ERROR_EOF = 4,
+    MP4PARSE_ERROR_IO = 5,
+}
 
 #[repr(C)]
-pub struct TrackInfo {
-    track_type: u32,
-    track_id: u32,
-    codec: u32,
-    duration: u64,
-    media_time: i64, // wants to be u64? understand how elst adjustment works
+#[derive(PartialEq, Debug)]
+pub enum mp4parse_track_type {
+    MP4PARSE_TRACK_TYPE_VIDEO = 0,
+    MP4PARSE_TRACK_TYPE_AUDIO = 1,
+}
+
+#[repr(C)]
+#[derive(PartialEq, Debug)]
+pub enum mp4parse_codec {
+    MP4PARSE_CODEC_UNKNOWN,
+    MP4PARSE_CODEC_AAC,
+    MP4PARSE_CODEC_OPUS,
+    MP4PARSE_CODEC_AVC,
+    MP4PARSE_CODEC_VP9,
+}
+
+#[repr(C)]
+pub struct mp4parse_track_info {
+    pub track_type: mp4parse_track_type,
+    pub codec: mp4parse_codec,
+    pub track_id: u32,
+    pub duration: u64,
+    pub media_time: i64, // wants to be u64? understand how elst adjustment works
     // TODO(kinetik): include crypto guff
 }
 
 #[repr(C)]
-pub struct TrackAudioInfo {
-    channels: u16,
-    bit_depth: u16,
-    sample_rate: u32,
+pub struct mp4parse_codec_specific_config {
+    pub length: u32,
+    pub data: *const u8,
+}
+
+impl Default for mp4parse_codec_specific_config {
+    fn default() -> Self {
+        mp4parse_codec_specific_config {
+            length: 0,
+            data: std::ptr::null_mut(),
+        }
+    }
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct mp4parse_track_audio_info {
+    pub channels: u16,
+    pub bit_depth: u16,
+    pub sample_rate: u32,
     // TODO(kinetik):
     // int32_t profile;
     // int32_t extended_profile; // check types
-    // extra_data
-    // codec_specific_config
+    codec_specific_config: mp4parse_codec_specific_config,
 }
 
 #[repr(C)]
-pub struct TrackVideoInfo {
-    display_width: u32,
-    display_height: u32,
-    image_width: u16,
-    image_height: u16,
+pub struct mp4parse_track_video_info {
+    pub display_width: u32,
+    pub display_height: u32,
+    pub image_width: u16,
+    pub image_height: u16,
     // TODO(kinetik):
     // extra_data
     // codec_specific_config
 }
 
-// C API wrapper functions.
+// Even though mp4parse_parser is opaque to C, rusty-cheddar won't let us
+// use more than one member, so we introduce *another* wrapper.
+struct Wrap {
+    context: MediaContext,
+    io: mp4parse_io,
+    poisoned: bool,
+    opus_header: HashMap<u32, Vec<u8>>,
+}
+
+#[repr(C)]
+#[allow(non_camel_case_types)]
+pub struct mp4parse_parser(Wrap);
+
+impl mp4parse_parser {
+    fn context(&self) -> &MediaContext {
+        &self.0.context
+    }
+
+    fn context_mut(&mut self) -> &mut MediaContext {
+        &mut self.0.context
+    }
 
-/// Allocate an opaque rust-side parser context.
-#[no_mangle]
-pub extern "C" fn mp4parse_new() -> *mut MediaContext {
-    let context = Box::new(MediaContext::new());
-    Box::into_raw(context)
+    fn io_mut(&mut self) -> &mut mp4parse_io {
+        &mut self.0.io
+    }
+
+    fn poisoned(&self) -> bool {
+        self.0.poisoned
+    }
+
+    fn set_poisoned(&mut self, poisoned: bool) {
+        self.0.poisoned = poisoned;
+    }
+
+    fn opus_header_mut(&mut self) -> &mut HashMap<u32, Vec<u8>> {
+        &mut self.0.opus_header
+    }
+}
+
+#[repr(C)]
+#[derive(Clone)]
+pub struct mp4parse_io {
+    pub read: extern fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize,
+    pub userdata: *mut std::os::raw::c_void,
 }
 
-/// Free a rust-side parser context.
-#[no_mangle]
-pub unsafe extern "C" fn mp4parse_free(context: *mut MediaContext) {
-    assert!(!context.is_null());
-    let _ = Box::from_raw(context);
+impl Read for mp4parse_io {
+    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+        if buf.len() > isize::max_value() as usize {
+            return Err(std::io::Error::new(std::io::ErrorKind::Other, "buf length overflow in mp4parse_io Read impl"));
+        }
+        let rv = (self.read)(buf.as_mut_ptr(), buf.len(), self.userdata);
+        if rv >= 0 {
+            Ok(rv as usize)
+        } else {
+            Err(std::io::Error::new(std::io::ErrorKind::Other, "I/O error in mp4parse_io Read impl"))
+        }
+    }
 }
 
-/// Feed a buffer through `read_mp4()` with the given rust-side
-/// parser context, returning success or an error code.
-///
-/// This is safe to call with NULL arguments but will crash
-/// if given invalid pointers, as is usual for C.
+// C API wrapper functions.
+
+/// Allocate an `mp4parse_parser*` to read from the supplied `mp4parse_io`.
 #[no_mangle]
-pub unsafe extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *const u8, size: usize) -> i32 {
+pub unsafe extern fn mp4parse_new(io: *const mp4parse_io) -> *mut mp4parse_parser {
+    if io.is_null() || (*io).userdata.is_null() {
+        return std::ptr::null_mut();
+    }
+    // is_null() isn't available on a fn type because it can't be null (in
+    // Rust) by definition.  But since this value is coming from the C API,
+    // it *could* be null.  Ideally, we'd wrap it in an Option to represent
+    // reality, but this causes rusty-cheddar to emit the wrong type
+    // (https://github.com/Sean1708/rusty-cheddar/issues/30).
+    if ((*io).read as *mut std::os::raw::c_void).is_null() {
+        return std::ptr::null_mut();
+    }
+    let parser = Box::new(mp4parse_parser(Wrap {
+        context: MediaContext::new(),
+        io: (*io).clone(),
+        poisoned: false,
+        opus_header: HashMap::new(),
+    }));
+    Box::into_raw(parser)
+}
+
+/// Free an `mp4parse_parser*` allocated by `mp4parse_new()`.
+#[no_mangle]
+pub unsafe extern fn mp4parse_free(parser: *mut mp4parse_parser) {
+    assert!(!parser.is_null());
+    let _ = Box::from_raw(parser);
+}
+
+/// Run the `mp4parse_parser*` allocated by `mp4parse_new()` until EOF or error.
+#[no_mangle]
+pub unsafe extern fn mp4parse_read(parser: *mut mp4parse_parser) -> mp4parse_error {
     // Validate arguments from C.
-    if context.is_null() || buffer.is_null() || size < 8 {
+    if parser.is_null() || (*parser).poisoned() {
         return MP4PARSE_ERROR_BADARG;
     }
 
-    let mut context: &mut MediaContext = &mut *context;
-
-    // Wrap the buffer we've been give in a slice.
-    let b = std::slice::from_raw_parts(buffer, size);
-    let mut c = Cursor::new(b);
+    let mut context = (*parser).context_mut();
+    let mut io = (*parser).io_mut();
 
-    // Parse in a subthread to catch any panics.
-    // We must use the thread::Builder API to avoid spawn itself
-    // panicking if thread creation fails. See bug 1266309.
-    let task = match std::thread::Builder::new()
-        .name("mp4parse_read isolation".to_string())
-        .spawn(move || read_mp4(&mut c, &mut context)) {
-            Ok(task) => task,
-            Err(_) => return MP4PARSE_ASSERT,
-    };
-    // The task's JoinHandle will return an error result if the
-    // thread panicked, and will wrap the closure's return'd
-    // result in an Ok(..) otherwise, meaning we could see
-    // Ok(Err(Error::..)) here. So map thread failures back
-    // to an mp4parse::Error before converting to a C return value.
-    match task.join().unwrap_or(Err(Error::AssertCaught)) {
+    let r = read_mp4(io, context);
+    match r {
         Ok(_) => MP4PARSE_OK,
-        Err(Error::InvalidData) => MP4PARSE_ERROR_INVALID,
-        Err(Error::Unsupported) => MP4PARSE_ERROR_UNSUPPORTED,
+        Err(Error::NoMoov) | Err(Error::InvalidData(_)) => {
+            // Block further calls. We've probable lost sync.
+            (*parser).set_poisoned(true);
+            MP4PARSE_ERROR_INVALID
+        }
+        Err(Error::Unsupported(_)) => MP4PARSE_ERROR_UNSUPPORTED,
         Err(Error::UnexpectedEOF) => MP4PARSE_ERROR_EOF,
-        Err(Error::AssertCaught) => MP4PARSE_ASSERT,
-        Err(Error::Io(_)) => MP4PARSE_ERROR_IO,
+        Err(Error::Io(_)) => {
+            // Block further calls after a read failure.
+            // Getting std::io::ErrorKind::UnexpectedEof is normal
+            // but our From trait implementation should have converted
+            // those to our Error::UnexpectedEOF variant.
+            (*parser).set_poisoned(true);
+            MP4PARSE_ERROR_IO
+        }
     }
 }
 
-/// Return the number of tracks parsed by previous `read_mp4()` calls.
+/// Return the number of tracks parsed by previous `mp4parse_read()` call.
 #[no_mangle]
-pub unsafe extern "C" fn mp4parse_get_track_count(context: *const MediaContext) -> u32 {
-    // Validate argument from C.
-    assert!(!context.is_null());
-    let context = &*context;
+pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, count: *mut u32) -> mp4parse_error {
+    // Validate arguments from C.
+    if parser.is_null() || count.is_null() || (*parser).poisoned() {
+        return MP4PARSE_ERROR_BADARG;
+    }
+    let context = (*parser).context();
 
     // Make sure the track count fits in a u32.
-    assert!(context.tracks.len() < u32::max_value() as usize);
-    context.tracks.len() as u32
+    if context.tracks.len() >= u32::max_value() as usize {
+        return MP4PARSE_ERROR_INVALID;
+    }
+    *count = context.tracks.len() as u32;
+    MP4PARSE_OK
 }
 
+/// Fill the supplied `mp4parse_track_info` with metadata for `track`.
 #[no_mangle]
-pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, track: u32, info: *mut TrackInfo) -> i32 {
-    if context.is_null() || info.is_null() {
+pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_info) -> mp4parse_error {
+    if parser.is_null() || info.is_null() || (*parser).poisoned() {
         return MP4PARSE_ERROR_BADARG;
     }
 
-    let context: &mut MediaContext = &mut *context;
-    let track_index: usize = track as usize;
-    let info: &mut TrackInfo = &mut *info;
+    let context = (*parser).context_mut();
+    let track_index: usize = track_index as usize;
+    let info: &mut mp4parse_track_info = &mut *info;
 
     if track_index >= context.tracks.len() {
         return MP4PARSE_ERROR_BADARG;
     }
 
     info.track_type = match context.tracks[track_index].track_type {
-        TrackType::Video => TRACK_TYPE_H264,
-        TrackType::Audio => TRACK_TYPE_AAC,
+        TrackType::Video => MP4PARSE_TRACK_TYPE_VIDEO,
+        TrackType::Audio => MP4PARSE_TRACK_TYPE_AUDIO,
         TrackType::Unknown => return MP4PARSE_ERROR_UNSUPPORTED,
     };
 
-    info.codec = match &*context.tracks[track_index].mime_type {
-        "audio/opus" => TRACK_CODEC_OPUS,
-        "video/vp9" => TRACK_CODEC_VP9,
-        "video/h264" => TRACK_CODEC_H264,
-        "audio/aac" => TRACK_CODEC_AAC,
-        _ => TRACK_CODEC_UNKNOWN,
+    info.codec = match context.tracks[track_index].data {
+        Some(SampleEntry::Audio(ref audio)) => match audio.codec_specific {
+            AudioCodecSpecific::OpusSpecificBox(_) =>
+                mp4parse_codec::MP4PARSE_CODEC_OPUS,
+            AudioCodecSpecific::ES_Descriptor(_) =>
+                mp4parse_codec::MP4PARSE_CODEC_AAC,
+        },
+        Some(SampleEntry::Video(ref video)) => match video.codec_specific {
+            VideoCodecSpecific::VPxConfig(_) =>
+                mp4parse_codec::MP4PARSE_CODEC_VP9,
+            VideoCodecSpecific::AVCConfig(_) =>
+                mp4parse_codec::MP4PARSE_CODEC_AVC,
+        },
+        _ => mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
     };
 
     // Maybe context & track should just have a single simple is_valid() instead?
     if context.timescale.is_none() ||
        context.tracks[track_index].timescale.is_none() ||
        context.tracks[track_index].duration.is_none() ||
        context.tracks[track_index].track_id.is_none() {
         return MP4PARSE_ERROR_INVALID;
     }
 
     let track = &context.tracks[track_index];
-    let empty_duration = if track.empty_duration.is_some() {
-        media_time_to_ms(track.empty_duration.unwrap(), context.timescale.unwrap())
-    } else {
-        0
-    };
-    info.media_time = if track.media_time.is_some() {
-        track_time_to_ms(track.media_time.unwrap(), track.timescale.unwrap()) as i64 - empty_duration as i64
-    } else {
-        0
-    };
+    info.media_time = track.media_time.map_or(0, |media_time| {
+        track_time_to_ms(media_time, track.timescale.unwrap()) as i64
+    }) - track.empty_duration.map_or(0, |empty_duration| {
+        media_time_to_ms(empty_duration, context.timescale.unwrap()) as i64
+    });
     info.duration = track_time_to_ms(track.duration.unwrap(), track.timescale.unwrap());
     info.track_id = track.track_id.unwrap();
 
     MP4PARSE_OK
 }
 
+/// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
 #[no_mangle]
-pub unsafe extern "C" fn mp4parse_get_track_audio_info(context: *mut MediaContext, track: u32, info: *mut TrackAudioInfo) -> i32 {
-    if context.is_null() || info.is_null() {
+pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_audio_info) -> mp4parse_error {
+    if parser.is_null() || info.is_null() || (*parser).poisoned() {
         return MP4PARSE_ERROR_BADARG;
     }
 
-    let context: &mut MediaContext = &mut *context;
+    let context = (*parser).context_mut();
 
-    if track as usize >= context.tracks.len() {
+    if track_index as usize >= context.tracks.len() {
         return MP4PARSE_ERROR_BADARG;
     }
 
-    let track = &context.tracks[track as usize];
+    let track = &context.tracks[track_index as usize];
 
     match track.track_type {
         TrackType::Audio => {}
         _ => return MP4PARSE_ERROR_INVALID,
     };
 
     let audio = match track.data {
         Some(ref data) => data,
@@ -242,32 +359,62 @@ pub unsafe extern "C" fn mp4parse_get_tr
         SampleEntry::Audio(ref x) => x,
         _ => return MP4PARSE_ERROR_INVALID,
     };
 
     (*info).channels = audio.channelcount;
     (*info).bit_depth = audio.samplesize;
     (*info).sample_rate = audio.samplerate >> 16; // 16.16 fixed point
 
+    match audio.codec_specific {
+        AudioCodecSpecific::ES_Descriptor(ref v) => {
+            if v.len() > std::u32::MAX as usize {
+                return MP4PARSE_ERROR_INVALID;
+            }
+            (*info).codec_specific_config.length = v.len() as u32;
+            (*info).codec_specific_config.data = v.as_ptr();
+        }
+        AudioCodecSpecific::OpusSpecificBox(ref opus) => {
+            let mut v = Vec::new();
+            match serialize_opus_header(opus, &mut v) {
+                Err(_) => {
+                    return MP4PARSE_ERROR_INVALID;
+                }
+                Ok(_) => {
+                    let header = (*parser).opus_header_mut();
+                    header.insert(track_index, v);
+                    match header.get(&track_index) {
+                        None => {}
+                        Some(v) => {
+                            (*info).codec_specific_config.length = v.len() as u32;
+                            (*info).codec_specific_config.data = v.as_ptr();
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     MP4PARSE_OK
 }
 
+/// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
 #[no_mangle]
-pub unsafe extern "C" fn mp4parse_get_track_video_info(context: *mut MediaContext, track: u32, info: *mut TrackVideoInfo) -> i32 {
-    if context.is_null() || info.is_null() {
+pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_video_info) -> mp4parse_error {
+    if parser.is_null() || info.is_null() || (*parser).poisoned() {
         return MP4PARSE_ERROR_BADARG;
     }
 
-    let context: &mut MediaContext = &mut *context;
+    let context = (*parser).context_mut();
 
-    if track as usize >= context.tracks.len() {
+    if track_index as usize >= context.tracks.len() {
         return MP4PARSE_ERROR_BADARG;
     }
 
-    let track = &context.tracks[track as usize];
+    let track = &context.tracks[track_index as usize];
 
     match track.track_type {
         TrackType::Video => {}
         _ => return MP4PARSE_ERROR_INVALID,
     };
 
     let video = match track.data {
         Some(ref data) => data,
@@ -281,59 +428,274 @@ pub unsafe extern "C" fn mp4parse_get_tr
 
     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
     } else {
         return MP4PARSE_ERROR_INVALID;
     }
     (*info).image_width = video.width;
-    (*info).image_width = video.height;
+    (*info).image_height = video.height;
 
     MP4PARSE_OK
 }
 
+#[cfg(test)]
+extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
+    panic!("panic_read shouldn't be called in these tests");
+}
+
+#[cfg(test)]
+extern fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
+    -1
+}
+
+#[cfg(test)]
+extern fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+    let mut 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 new_context() {
-    let context = mp4parse_new();
-    assert!(!context.is_null());
+fn new_parser() {
+    let mut dummy_value: u32 = 42;
+    let io = mp4parse_io {
+        read: panic_read,
+        userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+    };
     unsafe {
-        mp4parse_free(context);
+        let parser = mp4parse_new(&io);
+        assert!(!parser.is_null());
+        mp4parse_free(parser);
     }
 }
 
 #[test]
 #[should_panic(expected = "assertion failed")]
-fn free_null_context() {
+fn free_null_parser() {
     unsafe {
         mp4parse_free(std::ptr::null_mut());
     }
 }
 
 #[test]
-fn arg_validation() {
-    let null_buffer = std::ptr::null();
-    let null_context = std::ptr::null_mut();
+fn get_track_count_null_parser() {
+    unsafe {
+        let mut count: u32 = 0;
+        let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut());
+        assert!(rv == MP4PARSE_ERROR_BADARG);
+        let rv = mp4parse_get_track_count(std::ptr::null(), &mut count);
+        assert!(rv == MP4PARSE_ERROR_BADARG);
+    }
+}
 
-    let context = mp4parse_new();
-    assert!(!context.is_null());
-
-    let buffer = vec![0u8; 8];
-
+#[test]
+fn arg_validation() {
     unsafe {
-        assert_eq!(MP4PARSE_ERROR_BADARG,
-                   mp4parse_read(null_context, null_buffer, 0));
-        assert_eq!(MP4PARSE_ERROR_BADARG,
-                   mp4parse_read(context, null_buffer, 0));
-    }
+        // Passing a null mp4parse_io is an error.
+        let parser = mp4parse_new(std::ptr::null());
+        assert!(parser.is_null());
+
+        let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut();
+
+        // Passing an mp4parse_io with null members is an error.
+        let io = mp4parse_io { read: std::mem::transmute(null_mut),
+                               userdata: null_mut };
+        let parser = mp4parse_new(&io);
+        assert!(parser.is_null());
+
+        let io = mp4parse_io { read: panic_read,
+                               userdata: null_mut };
+        let parser = mp4parse_new(&io);
+        assert!(parser.is_null());
+
+        let mut dummy_value = 42;
+        let io = mp4parse_io {
+            read: std::mem::transmute(null_mut),
+            userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+        };
+        let parser = mp4parse_new(&io);
+        assert!(parser.is_null());
+
+        // Passing a null mp4parse_parser is an error.
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_read(std::ptr::null_mut()));
+
+        let mut dummy_info = mp4parse_track_info {
+            track_type: MP4PARSE_TRACK_TYPE_VIDEO,
+            codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+            track_id: 0,
+            duration: 0,
+            media_time: 0,
+        };
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info));
+
+        let mut dummy_video = mp4parse_track_video_info {
+            display_width: 0,
+            display_height: 0,
+            image_width: 0,
+            image_height: 0,
+        };
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video));
 
-    for size in 0..buffer.len() {
-        println!("testing buffer length {}", size);
-        unsafe {
-            assert_eq!(MP4PARSE_ERROR_BADARG,
-                       mp4parse_read(context, buffer.as_ptr(), size));
-        }
+        let mut dummy_audio = Default::default();
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio));
     }
+}
 
+#[test]
+fn arg_validation_with_parser() {
     unsafe {
-        mp4parse_free(context);
+        let mut dummy_value = 42;
+        let io = mp4parse_io {
+            read: error_read,
+            userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+        };
+        let parser = mp4parse_new(&io);
+        assert!(!parser.is_null());
+
+        // Our mp4parse_io read should simply fail with an error.
+        assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser));
+
+        // The parser is now poisoned and unusable.
+        assert_eq!(MP4PARSE_ERROR_BADARG,  mp4parse_read(parser));
+
+        // Null info pointers are an error.
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, std::ptr::null_mut()));
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut()));
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut()));
+
+        let mut dummy_info = mp4parse_track_info {
+            track_type: MP4PARSE_TRACK_TYPE_VIDEO,
+            codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+            track_id: 0,
+            duration: 0,
+            media_time: 0,
+        };
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, &mut dummy_info));
+
+        let mut dummy_video = mp4parse_track_video_info {
+            display_width: 0,
+            display_height: 0,
+            image_width: 0,
+            image_height: 0,
+        };
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, &mut dummy_video));
+
+        let mut dummy_audio = Default::default();
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio));
+
+        mp4parse_free(parser);
     }
 }
+
+#[test]
+fn get_track_count_poisoned_parser() {
+    unsafe {
+        let mut dummy_value = 42;
+        let io = mp4parse_io {
+            read: error_read,
+            userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+        };
+        let parser = mp4parse_new(&io);
+        assert!(!parser.is_null());
+
+        // Our mp4parse_io read should simply fail with an error.
+        assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser));
+
+        let mut count: u32 = 0;
+        let rv = mp4parse_get_track_count(parser, &mut count);
+        assert!(rv == MP4PARSE_ERROR_BADARG);
+    }
+}
+
+#[test]
+fn arg_validation_with_data() {
+    unsafe {
+        let mut file = std::fs::File::open("examples/minimal.mp4").unwrap();
+        let io = mp4parse_io { read: valid_read,
+                               userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
+        let parser = mp4parse_new(&io);
+        assert!(!parser.is_null());
+
+        assert_eq!(MP4PARSE_OK, mp4parse_read(parser));
+
+        let mut count: u32 = 0;
+        assert_eq!(MP4PARSE_OK, mp4parse_get_track_count(parser, &mut count));
+        assert_eq!(2, count);
+
+        let mut info = mp4parse_track_info {
+            track_type: MP4PARSE_TRACK_TYPE_VIDEO,
+            codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+            track_id: 0,
+            duration: 0,
+            media_time: 0,
+        };
+        assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 0, &mut info));
+        assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO);
+        assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AVC);
+        assert_eq!(info.track_id, 1);
+        assert_eq!(info.duration, 40000);
+        assert_eq!(info.media_time, 0);
+
+        assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 1, &mut info));
+        assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_AUDIO);
+        assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AAC);
+        assert_eq!(info.track_id, 2);
+        assert_eq!(info.duration, 61333);
+        assert_eq!(info.media_time, 21333);
+
+        let mut video = mp4parse_track_video_info {
+            display_width: 0,
+            display_height: 0,
+            image_width: 0,
+            image_height: 0,
+        };
+        assert_eq!(MP4PARSE_OK, mp4parse_get_track_video_info(parser, 0, &mut video));
+        assert_eq!(video.display_width, 320);
+        assert_eq!(video.display_height, 240);
+        assert_eq!(video.image_width, 320);
+        assert_eq!(video.image_height, 240);
+
+        let mut audio = Default::default();
+        assert_eq!(MP4PARSE_OK, mp4parse_get_track_audio_info(parser, 1, &mut audio));
+        assert_eq!(audio.channels, 2);
+        assert_eq!(audio.bit_depth, 16);
+        assert_eq!(audio.sample_rate, 48000);
+
+        // Test with an invalid track number.
+        let mut info = mp4parse_track_info {
+            track_type: MP4PARSE_TRACK_TYPE_VIDEO,
+            codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+            track_id: 0,
+            duration: 0,
+            media_time: 0,
+        };
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 3, &mut info));
+        assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO);
+        assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_UNKNOWN);
+        assert_eq!(info.track_id, 0);
+        assert_eq!(info.duration, 0);
+        assert_eq!(info.media_time, 0);
+
+        let mut video = mp4parse_track_video_info { display_width: 0,
+                                                    display_height: 0,
+                                                    image_width: 0,
+                                                    image_height: 0 };
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 3, &mut video));
+        assert_eq!(video.display_width, 0);
+        assert_eq!(video.display_height, 0);
+        assert_eq!(video.image_width, 0);
+        assert_eq!(video.image_height, 0);
+
+        let mut audio = Default::default();
+        assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 3, &mut audio));
+        assert_eq!(audio.channels, 0);
+        assert_eq!(audio.bit_depth, 0);
+        assert_eq!(audio.sample_rate, 0);
+
+        mp4parse_free(parser);
+    }
+}
--- a/media/libstagefright/binding/mp4parse/lib.rs
+++ b/media/libstagefright/binding/mp4parse/lib.rs
@@ -1,215 +1,207 @@
 //! Module for parsing ISO Base Media Format aka video/mp4 streams.
 
 // 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 https://mozilla.org/MPL/2.0/.
+#![cfg_attr(feature = "fuzz", feature(plugin))]
+#![cfg_attr(feature = "fuzz", plugin(afl_plugin))]
+#[cfg(feature = "fuzz")]
+extern crate afl;
 
 mod byteorder; // 'extern crate' upstream.
 use byteorder::ReadBytesExt;
-use std::error::Error as ErrorTrait; // For Err(e) => e.description().
-use std::io::{Read, BufRead, Take};
+use std::io::{Read, Take};
 use std::cmp;
-use std::fmt;
 
 // Expose C api wrapper.
 pub mod capi;
 pub use capi::*;
 
+mod boxes;
+use boxes::BoxType;
+
 // Unit tests.
 #[cfg(test)]
 mod tests;
 
+// Arbitrary buffer size limit used for raw read_bufs on a box.
+const BUF_SIZE_LIMIT: u64 = 1024 * 1024;
+
+static DEBUG_MODE: std::sync::atomic::AtomicBool = std::sync::atomic::ATOMIC_BOOL_INIT;
+
+pub fn set_debug_mode(mode: bool) {
+    DEBUG_MODE.store(mode, std::sync::atomic::Ordering::SeqCst);
+}
+
+#[inline(always)]
+fn get_debug_mode() -> bool {
+    DEBUG_MODE.load(std::sync::atomic::Ordering::Relaxed)
+}
+
+macro_rules! log {
+    ($($args:tt)*) => (
+        if get_debug_mode() {
+            println!( $( $args )* );
+        }
+    )
+}
+
 /// Describes parser failures.
 ///
-/// This enum wraps athe standard `io::Error` type, unified with
+/// This enum wraps the standard `io::Error` type, unified with
 /// our own parser error states and those of crates we use.
 #[derive(Debug)]
 pub enum Error {
     /// Parse error caused by corrupt or malformed data.
-    InvalidData,
+    InvalidData(&'static str),
     /// Parse error caused by limited parser support rather than invalid data.
-    Unsupported,
-    /// Reflect `byteorder::Error::UnexpectedEOF` for short data.
+    Unsupported(&'static str),
+    /// Reflect `std::io::ErrorKind::UnexpectedEof` for short data.
     UnexpectedEOF,
-    /// Caught panic! or assert! meaning the parser couldn't recover.
-    AssertCaught,
     /// Propagate underlying errors from `std::io`.
     Io(std::io::Error),
+    /// read_mp4 terminated without detecting a moov box.
+    NoMoov,
 }
 
 impl From<std::io::Error> for Error {
     fn from(err: std::io::Error) -> Error {
-        Error::Io(err)
-    }
-}
-
-impl From<byteorder::Error> for Error {
-    fn from(err: byteorder::Error) -> Error {
-        match err {
-            byteorder::Error::UnexpectedEOF => Error::UnexpectedEOF,
-            byteorder::Error::Io(e) => Error::Io(e),
+        match err.kind() {
+            std::io::ErrorKind::UnexpectedEof => Error::UnexpectedEOF,
+            _ => Error::Io(err),
         }
     }
 }
 
 impl From<std::string::FromUtf8Error> for Error {
     fn from(_: std::string::FromUtf8Error) -> Error {
-        Error::InvalidData
+        Error::InvalidData("invalid utf8")
     }
 }
 
 /// Result shorthand using our Error enum.
 pub type Result<T> = std::result::Result<T, Error>;
 
-/// Four-byte 'character code' describing the type of a piece of data.
-#[derive(Clone, Copy, Eq, PartialEq)]
-pub struct FourCC([u8; 4]);
-
-impl FourCC {
-    fn as_bytes(&self) -> &[u8; 4] {
-        &self.0
-    }
-}
-
-impl fmt::Debug for FourCC {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "'{}'", String::from_utf8_lossy(&self.0))
-    }
-}
-
 /// Basic ISO box structure.
 ///
-/// mp4 files are a sequence of possibly-nested 'box' structures.
-/// Each box begins with a header describing the length of the
-/// box's data and a four-byte 'character code' or `FourCC` which
-/// identifies the type of the box. Together these are enough to
-/// interpret the contents of that section of the file.
+/// mp4 files are a sequence of possibly-nested 'box' structures.  Each box
+/// begins with a header describing the length of the box's data and a
+/// four-byte box type which identifies the type of the box. Together these
+/// are enough to interpret the contents of that section of the file.
 #[derive(Debug, Clone, Copy)]
-pub struct BoxHeader {
-    /// Four character box type.
-    pub name: FourCC,
+struct BoxHeader {
+    /// Box type.
+    name: BoxType,
     /// Size of the box in bytes.
-    pub size: u64,
+    size: u64,
     /// Offset to the start of the contained data (or header size).
-    pub offset: u64,
+    offset: u64,
 }
 
 /// File type box 'ftyp'.
 #[derive(Debug)]
 struct FileTypeBox {
-    header: BoxHeader,
-    major_brand: FourCC,
+    major_brand: u32,
     minor_version: u32,
-    compatible_brands: Vec<FourCC>,
+    compatible_brands: Vec<u32>,
 }
 
 /// Movie header box 'mvhd'.
 #[derive(Debug)]
 struct MovieHeaderBox {
-    header: BoxHeader,
     timescale: u32,
     duration: u64,
 }
 
 /// Track header box 'tkhd'
 #[derive(Debug, Clone)]
 struct TrackHeaderBox {
-    header: BoxHeader,
     track_id: u32,
     disabled: bool,
     duration: u64,
     width: u32,
     height: u32,
 }
 
 /// Edit list box 'elst'
 #[derive(Debug)]
 struct EditListBox {
-    header: BoxHeader,
     edits: Vec<Edit>,
 }
 
 #[derive(Debug)]
 struct Edit {
     segment_duration: u64,
     media_time: i64,
     media_rate_integer: i16,
     media_rate_fraction: i16,
 }
 
 /// Media header box 'mdhd'
 #[derive(Debug)]
 struct MediaHeaderBox {
-    header: BoxHeader,
     timescale: u32,
     duration: u64,
 }
 
 // Chunk offset box 'stco' or 'co64'
 #[derive(Debug)]
 struct ChunkOffsetBox {
-    header: BoxHeader,
     offsets: Vec<u64>,
 }
 
 // Sync sample box 'stss'
 #[derive(Debug)]
 struct SyncSampleBox {
-    header: BoxHeader,
     samples: Vec<u32>,
 }
 
 // Sample to chunk box 'stsc'
 #[derive(Debug)]
 struct SampleToChunkBox {
-    header: BoxHeader,
     samples: Vec<SampleToChunk>,
 }
 
 #[derive(Debug)]
 struct SampleToChunk {
     first_chunk: u32,
     samples_per_chunk: u32,
     sample_description_index: u32,
 }
 
 // Sample size box 'stsz'
 #[derive(Debug)]
 struct SampleSizeBox {
-    header: BoxHeader,
     sample_size: u32,
     sample_sizes: Vec<u32>,
 }
 
 // Time to sample box 'stts'
 #[derive(Debug)]
 struct TimeToSampleBox {
-    header: BoxHeader,
     samples: Vec<Sample>,
 }
 
 #[derive(Debug)]
 struct Sample {
     sample_count: u32,
     sample_delta: u32,
 }
 
 // Handler reference box 'hdlr'
 #[derive(Debug)]
 struct HandlerBox {
-    header: BoxHeader,
-    handler_type: FourCC,
+    handler_type: u32,
 }
 
 // Sample description box 'stsd'
 #[derive(Debug)]
 struct SampleDescriptionBox {
-    header: BoxHeader,
     descriptions: Vec<SampleEntry>,
 }
 
 #[derive(Debug, Clone)]
 enum SampleEntry {
     Audio(AudioSampleEntry),
     Video(VideoSampleEntry),
     Unknown,
@@ -278,38 +270,23 @@ struct OpusSpecificBox {
 }
 
 /// Internal data structures.
 #[derive(Debug)]
 pub struct MediaContext {
     timescale: Option<MediaTimeScale>,
     /// Tracks found in the file.
     tracks: Vec<Track>,
-    /// Print boxes and other info as parsing proceeds. For debugging.
-    trace: bool,
 }
 
 impl MediaContext {
-    pub fn new() -> Self {
+    pub fn new() -> MediaContext {
         MediaContext {
             timescale: None,
             tracks: Vec::new(),
-            trace: false,
-        }
-    }
-
-    pub fn trace(&mut self, on: bool) {
-        self.trace = on;
-    }
-}
-
-macro_rules! log {
-    ( $ctx:expr, $( $args:tt )* ) => {
-        if $ctx.trace {
-            println!( $( $args )* );
         }
     }
 }
 
 #[derive(Debug)]
 enum TrackType {
     Audio,
     Video,
@@ -339,56 +316,102 @@ struct Track {
     empty_duration: Option<MediaScaledTime>,
     media_time: Option<TrackScaledTime>,
     timescale: Option<TrackTimeScale>,
     duration: Option<TrackScaledTime>,
     track_id: Option<u32>,
     mime_type: String,
     data: Option<SampleEntry>,
     tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
-    trace: bool,
 }
 
 impl Track {
-    fn new(id: usize) -> Self {
+    fn new(id: usize) -> Track {
         Track {
             id: id,
             track_type: TrackType::Unknown,
             empty_duration: None,
             media_time: None,
             timescale: None,
             duration: None,
             track_id: None,
             mime_type: String::new(),
             data: None,
             tkhd: None,
-            trace: false,
         }
     }
 }
 
+struct BMFFBox<'a, T: 'a + Read> {
+    head: BoxHeader,
+    content: Take<&'a mut T>,
+}
+
+struct BoxIter<'a, T: 'a + Read> {
+    src: &'a mut T,
+}
+
+impl<'a, T: Read> BoxIter<'a, T> {
+    fn new(src: &mut T) -> BoxIter<T> {
+        BoxIter { src: src }
+    }
+
+    fn next_box(&mut self) -> Result<Option<BMFFBox<T>>> {
+        let r = read_box_header(self.src);
+        match r {
+            Ok(h) => Ok(Some(BMFFBox {
+                head: h,
+                content: self.src.take(h.size - h.offset),
+            })),
+            Err(Error::UnexpectedEOF) => Ok(None),
+            Err(e) => Err(e),
+        }
+    }
+}
+
+impl<'a, T: Read> Read for BMFFBox<'a, T> {
+    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+        self.content.read(buf)
+    }
+}
+
+impl<'a, T: Read> BMFFBox<'a, T> {
+    fn bytes_left(&self) -> usize {
+        self.content.limit() as usize
+    }
+
+    fn get_header(&self) -> &BoxHeader {
+        &self.head
+    }
+
+    fn box_iter<'b>(&'b mut self) -> BoxIter<BMFFBox<'a, T>> {
+        BoxIter::new(self)
+    }
+}
+
 /// Read and parse a box header.
 ///
 /// Call this first to determine the type of a particular mp4 box
 /// and its length. Used internally for dispatching to specific
 /// parsers for the internal content, or to get the length to
 /// skip unknown or uninteresting boxes.
-pub fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
+fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
     let size32 = try!(be_u32(src));
-    let name = try!(be_fourcc(src));
+    let name = BoxType::from(try!(be_u32(src)));
     let size = match size32 {
-        0 => return Err(Error::Unsupported),
+        // valid only for top-level box and indicates it's the last box in the file.  usually mdat.
+        0 => return Err(Error::Unsupported("unknown sized box")),
         1 => {
             let size64 = try!(be_u64(src));
             if size64 < 16 {
-                return Err(Error::InvalidData);
+                return Err(Error::InvalidData("malformed wide size"));
             }
             size64
         }
-        2...7 => return Err(Error::InvalidData),
+        2...7 => return Err(Error::InvalidData("malformed size")),
         _ => size32 as u64,
     };
     let offset = match size32 {
         1 => 4 + 4 + 8,
         _ => 4 + 4,
     };
     assert!(offset <= size);
     Ok(BoxHeader {
@@ -404,423 +427,395 @@ fn read_fullbox_extra<T: ReadBytesExt>(s
     let flags_a = try!(src.read_u8());
     let flags_b = try!(src.read_u8());
     let flags_c = try!(src.read_u8());
     Ok((version,
         (flags_a as u32) << 16 | (flags_b as u32) << 8 | (flags_c as u32)))
 }
 
 /// Skip over the entire contents of a box.
-fn skip_box_content<T: BufRead>(src: &mut T, header: &BoxHeader) -> Result<usize> {
-    skip(src, (header.size - header.offset) as usize)
-}
-
-/// Skip over the remaining contents of a box.
-fn skip_remaining_box_content<T: BufRead>(src: &mut T, header: &BoxHeader) -> Result<()> {
-    match skip(src, (header.size - header.offset) as usize) {
-        Ok(_) | Err(Error::UnexpectedEOF) => Ok(()),
-        e => Err(e.err().unwrap()),
-    }
-}
-
-/// Helper to construct a Take over the contents of a box.
-fn limit<'a, T: BufRead>(f: &'a mut T, h: &BoxHeader) -> Take<&'a mut T> {
-    f.take(h.size - h.offset)
+fn skip_box_content<T: Read>(src: &mut BMFFBox<T>) -> Result<()> {
+    // Skip the contents of unknown chunks.
+    let to_skip = {
+        let header = src.get_header();
+        log!("{:?} (skipped)", header);
+        (header.size - header.offset) as usize
+    };
+    assert!(to_skip == src.bytes_left());
+    skip(src, to_skip)
 }
 
-/// Helper to recursively parse a the contents of a box.
-/// Reads a box header from the source given in the first argument
-/// and then calls the passed closure to dispatch further based
-/// on that header.
-fn driver<F, T>(f: &mut T, context: &mut MediaContext, mut action: F) -> Result<()>
-    where F: FnMut(&mut Take<&mut T>, BoxHeader, &mut MediaContext) -> Result<()>,
-          T: BufRead,
-{
-    loop {
-        let r = read_box_header(f).and_then(|h| {
-            let mut content = limit(f, &h);
-            let r = action(&mut content, h, context);
-            if r.is_ok() {
-                if content.limit() > 0 {
-                    // It's possible that this is a parser bug rather than a
-                    // bad file (e.g. if we forgot to read the entire box
-                    // contents).
-                    log!(context, "bad parser state: {} content bytes left", content.limit());
-                    return Err(Error::InvalidData);
-                }
-                log!(context, "read_box context: {:?}", context);
-            }
-            r
-        });
-        match r {
-            Ok(_) => {}
-            Err(Error::UnexpectedEOF) => {
-                // byteorder returns EOF at the end of the buffer.
-                // This isn't an error for us, just an signal to
-                // stop recursion.
-                log!(context, "Caught Error::UnexpectedEOF");
-                break;
-            }
-            Err(Error::InvalidData) => {
-                log!(context, "Invalid data");
-                return Err(Error::InvalidData);
-            }
-            Err(Error::Unsupported) => {
-                log!(context, "Unsupported BMFF construct");
-                return Err(Error::Unsupported);
-            }
-            Err(Error::AssertCaught) => {
-                log!(context, "Unrecoverable error or assertion");
-                return Err(Error::AssertCaught);
-            }
-            Err(Error::Io(e)) => {
-                log!(context,
-                     "I/O Error '{:?}' reading box: {:?}",
-                     e.kind(),
-                     e.description());
-                return Err(Error::Io(e));
-            }
+macro_rules! check_parser_state {
+    ( $src:expr ) => {
+        if $src.limit() > 0 {
+            log!("bad parser state: {} content bytes left", $src.limit());
+            return Err(Error::InvalidData("unread box content or bad parser sync"));
         }
     }
-    Ok(())
 }
 
 /// Read the contents of a box, including sub boxes.
 ///
 /// Metadata is accumulated in the passed-through MediaContext struct,
 /// which can be examined later.
-pub fn read_mp4<T: BufRead>(f: &mut T, context: &mut MediaContext) -> Result<()> {
-    driver(f, context, |mut content, h, context| {
-        match h.name.as_bytes() {
-            b"ftyp" => {
-                let ftyp = try!(read_ftyp(&mut content, &h));
-                log!(context, "{:?}", ftyp);
+pub fn read_mp4<T: Read>(f: &mut T, context: &mut MediaContext) -> Result<()> {
+    let mut found_ftyp = false;
+    let mut found_moov = false;
+    // TODO(kinetik): Top-level parsing should handle zero-sized boxes
+    // rather than throwing an error.
+    let mut iter = BoxIter::new(f);
+    while let Some(mut b) = try!(iter.next_box()) {
+        // box ordering: ftyp before any variable length box (inc. moov),
+        // but may not be first box in file if file signatures etc. present
+        // fragmented mp4 order: ftyp, moov, pairs of moof/mdat (1-multiple), mfra
+
+        // "special": uuid, wide (= 8 bytes)
+        // isom: moov, mdat, free, skip, udta, ftyp, moof, mfra
+        // iso2: pdin, meta
+        // iso3: meco
+        // iso5: styp, sidx, ssix, prft
+        // unknown, maybe: id32
+
+        // qt: pnot
+
+        // possibly allow anything where all printable and/or all lowercase printable
+        // "four printable characters from the ISO 8859-1 character set"
+        match b.head.name {
+            BoxType::FileTypeBox => {
+                let ftyp = try!(read_ftyp(&mut b));
+                found_ftyp = true;
+                log!("{:?}", ftyp);
             }
-            b"moov" => try!(read_moov(&mut content, &h, context)),
-            _ => {
-                // Skip the contents of unknown chunks.
-                try!(skip_box_content(&mut content, &h));
+            BoxType::MovieBox => {
+                try!(read_moov(&mut b, context));
+                found_moov = true;
             }
+            _ => try!(skip_box_content(&mut b)),
         };
+        check_parser_state!(b.content);
+        if found_moov {
+            log!("found moov {}, could stop pure 'moov' parser now", if found_ftyp {
+                "and ftyp"
+            } else {
+                "but no ftyp"
+            });
+        }
+    }
+
+    // XXX(kinetik): This isn't perfect, as a "moov" with no contents is
+    // treated as okay but we haven't found anything useful.  Needs more
+    // thought for clearer behaviour here.
+    if found_moov {
         Ok(())
-    })
+    } else {
+        Err(Error::NoMoov)
+    }
 }
 
-fn parse_mvhd<T: BufRead>(f: &mut T, h: &BoxHeader) -> Result<(MovieHeaderBox, Option<MediaTimeScale>)> {
-    let mvhd = try!(read_mvhd(f, &h));
+fn parse_mvhd<T: Read>(f: &mut BMFFBox<T>) -> Result<(MovieHeaderBox, Option<MediaTimeScale>)> {
+    let mvhd = try!(read_mvhd(f));
     if mvhd.timescale == 0 {
-        return Err(Error::InvalidData);
+        return Err(Error::InvalidData("zero timescale in mdhd"));
     }
     let timescale = Some(MediaTimeScale(mvhd.timescale as u64));
     Ok((mvhd, timescale))
 }
 
-fn read_moov<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext) -> Result<()> {
-    driver(f, context, |mut content, h, context| {
-        match h.name.as_bytes() {
-            b"mvhd" => {
-                let (mvhd, timescale) = try!(parse_mvhd(content, &h));
+fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: &mut MediaContext) -> Result<()> {
+    let mut iter = f.box_iter();
+    while let Some(mut b) = try!(iter.next_box()) {
+        match b.head.name {
+            BoxType::MovieHeaderBox => {
+                let (mvhd, timescale) = try!(parse_mvhd(&mut b));
                 context.timescale = timescale;
-                log!(context, "  {:?}", mvhd);
+                log!("{:?}", mvhd);
             }
-            b"trak" => {
+            BoxType::TrackBox => {
                 let mut track = Track::new(context.tracks.len());
-                track.trace = context.trace;
-                try!(read_trak(&mut content, &h, context, &mut track));
+                try!(read_trak(&mut b, &mut track));
                 context.tracks.push(track);
             }
-            _ => {
-                // Skip the contents of unknown chunks.
-                log!(context, "{:?} (skipped)", h);
-                try!(skip_box_content(&mut content, &h));
-            }
+            _ => try!(skip_box_content(&mut b)),
         };
-        Ok(())
-    })
+        check_parser_state!(b.content);
+    }
+    Ok(())
 }
 
-fn read_trak<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext, track: &mut Track) -> Result<()> {
-    driver(f, context, |mut content, h, context| {
-        match h.name.as_bytes() {
-            b"tkhd" => {
-                let tkhd = try!(read_tkhd(&mut content, &h));
+fn read_trak<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+    let mut iter = f.box_iter();
+    while let Some(mut b) = try!(iter.next_box()) {
+        match b.head.name {
+            BoxType::TrackHeaderBox => {
+                let tkhd = try!(read_tkhd(&mut b));
                 track.track_id = Some(tkhd.track_id);
                 track.tkhd = Some(tkhd.clone());
-                log!(context, "  {:?}", tkhd);
+                log!("{:?}", tkhd);
             }
-            b"edts" => try!(read_edts(&mut content, &h, context, track)),
-            b"mdia" => try!(read_mdia(&mut content, &h, context, track)),
-            _ => {
-                // Skip the contents of unknown chunks.
-                log!(context, "{:?} (skipped)", h);
-                try!(skip_box_content(&mut content, &h));
-            }
+            BoxType::EditBox => try!(read_edts(&mut b, track)),
+            BoxType::MediaBox => try!(read_mdia(&mut b, track)),
+            _ => try!(skip_box_content(&mut b)),
         };
-        Ok(())
-    })
+        check_parser_state!(b.content);
+    }
+    Ok(())
 }
 
-fn read_edts<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext, track: &mut Track) -> Result<()> {
-    driver(f, context, |mut content, h, context| {
-        match h.name.as_bytes() {
-            b"elst" => {
-                let elst = try!(read_elst(&mut content, &h));
+fn read_edts<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+    let mut iter = f.box_iter();
+    while let Some(mut b) = try!(iter.next_box()) {
+        match b.head.name {
+            BoxType::EditListBox => {
+                let elst = try!(read_elst(&mut b));
                 let mut empty_duration = 0;
                 let mut idx = 0;
                 if elst.edits.len() > 2 {
-                    return Err(Error::Unsupported);
+                    return Err(Error::Unsupported("more than two edits"));
                 }
                 if elst.edits[idx].media_time == -1 {
-                    empty_duration = elst.edits[0].segment_duration;
+                    empty_duration = elst.edits[idx].segment_duration;
+                    if elst.edits.len() < 2 {
+                        return Err(Error::InvalidData("expected additional edit"));
+                    }
                     idx += 1;
                 }
                 track.empty_duration = Some(MediaScaledTime(empty_duration));
                 if elst.edits[idx].media_time < 0 {
-                    return Err(Error::InvalidData);
+                    return Err(Error::InvalidData("unexpected negative media time in edit"));
                 }
                 track.media_time = Some(TrackScaledTime(elst.edits[idx].media_time as u64,
                                                         track.id));
-                log!(context, "  {:?}", elst);
+                log!("{:?}", elst);
             }
-            _ => {
-                // Skip the contents of unknown chunks.
-                log!(context, "{:?} (skipped)", h);
-                try!(skip_box_content(&mut content, &h));
-            }
+            _ => try!(skip_box_content(&mut b)),
         };
-        Ok(())
-    })
+        check_parser_state!(b.content);
+    }
+    Ok(())
 }
 
-fn parse_mdhd<T: BufRead>(f: &mut T, h: &BoxHeader, track: &mut Track) -> Result<(MediaHeaderBox, Option<TrackScaledTime>, Option<TrackTimeScale>)> {
-    let mdhd = try!(read_mdhd(f, h));
+fn parse_mdhd<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<(MediaHeaderBox, Option<TrackScaledTime>, Option<TrackTimeScale>)> {
+    let mdhd = try!(read_mdhd(f));
     let duration = match mdhd.duration {
         std::u64::MAX => None,
         duration => Some(TrackScaledTime(duration, track.id)),
     };
     if mdhd.timescale == 0 {
-        return Err(Error::InvalidData);
+        return Err(Error::InvalidData("zero timescale in mdhd"));
     }
     let timescale = Some(TrackTimeScale(mdhd.timescale as u64, track.id));
     Ok((mdhd, duration, timescale))
 }
 
-fn read_mdia<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext, track: &mut Track) -> Result<()> {
-    driver(f, context, |mut content, h, context| {
-        match h.name.as_bytes() {
-            b"mdhd" => {
-                let (mdhd, duration, timescale) = try!(parse_mdhd(content, &h, track));
+fn read_mdia<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+    let mut iter = f.box_iter();
+    while let Some(mut b) = try!(iter.next_box()) {
+        match b.head.name {
+            BoxType::MediaHeaderBox => {
+                let (mdhd, duration, timescale) = try!(parse_mdhd(&mut b, track));
                 track.duration = duration;
                 track.timescale = timescale;
-                log!(context, "  {:?}", mdhd);
+                log!("{:?}", mdhd);
             }
-            b"hdlr" => {
-                let hdlr = try!(read_hdlr(&mut content, &h));
-                match &hdlr.handler_type.0 {
-                    b"vide" => track.track_type = TrackType::Video,
-                    b"soun" => track.track_type = TrackType::Audio,
+            BoxType::HandlerBox => {
+                let hdlr = try!(read_hdlr(&mut b));
+                match hdlr.handler_type {
+                    0x76696465 /* 'vide' */ => track.track_type = TrackType::Video,
+                    0x736f756e /* 'soun' */ => track.track_type = TrackType::Audio,
                     _ => (),
                 }
-                log!(context, "  {:?}", hdlr);
+                log!("{:?}", hdlr);
             }
-            b"minf" => try!(read_minf(&mut content, &h, context, track)),
-            _ => {
-                // Skip the contents of unknown chunks.
-                log!(context, "{:?} (skipped)", h);
-                try!(skip_box_content(&mut content, &h));
-            }
+            BoxType::MediaInformationBox => try!(read_minf(&mut b, track)),
+            _ => try!(skip_box_content(&mut b)),
         };
-        Ok(())
-    })
+        check_parser_state!(b.content);
+    }
+    Ok(())
 }
 
-fn read_minf<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext, track: &mut Track) -> Result<()> {
-    driver(f, context, |mut content, h, context| {
-        match h.name.as_bytes() {
-            b"stbl" => try!(read_stbl(&mut content, &h, context, track)),
-            _ => {
-                // Skip the contents of unknown chunks.
-                log!(context, "{:?} (skipped)", h);
-                try!(skip_box_content(&mut content, &h));
-            }
+fn read_minf<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+    let mut iter = f.box_iter();
+    while let Some(mut b) = try!(iter.next_box()) {
+        match b.head.name {
+            BoxType::SampleTableBox => try!(read_stbl(&mut b, track)),
+            _ => try!(skip_box_content(&mut b)),
         };
-        Ok(())
-    })
+        check_parser_state!(b.content);
+    }
+    Ok(())
 }
 
-fn read_stbl<T: BufRead>(f: &mut T, _: &BoxHeader, context: &mut MediaContext, track: &mut Track) -> Result<()> {
-    driver(f, context, |mut content, h, context| {
-        match h.name.as_bytes() {
-            b"stsd" => {
-                let stsd = try!(read_stsd(&mut content, &h, track));
-                log!(context, "  {:?}", stsd);
+fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
+    let mut iter = f.box_iter();
+    while let Some(mut b) = try!(iter.next_box()) {
+        match b.head.name {
+            BoxType::SampleDescriptionBox => {
+                let stsd = try!(read_stsd(&mut b, track));
+                log!("{:?}", stsd);
             }
-            b"stts" => {
-                let stts = try!(read_stts(&mut content, &h));
-                log!(context, "  {:?}", stts);
+            BoxType::TimeToSampleBox => {
+                let stts = try!(read_stts(&mut b));
+                log!("{:?}", stts);
             }
-            b"stsc" => {
-                let stsc = try!(read_stsc(&mut content, &h));
-                log!(context, "  {:?}", stsc);
-            }
-            b"stsz" => {
-                let stsz = try!(read_stsz(&mut content, &h));
-                log!(context, "  {:?}", stsz);
+            BoxType::SampleToChunkBox => {
+                let stsc = try!(read_stsc(&mut b));
+                log!("{:?}", stsc);
             }
-            b"stco" => {
-                let stco = try!(read_stco(&mut content, &h));
-                log!(context, "  {:?}", stco);
+            BoxType::SampleSizeBox => {
+                let stsz = try!(read_stsz(&mut b));
+                log!("{:?}", stsz);
             }
-            b"co64" => {
-                let co64 = try!(read_co64(&mut content, &h));
-                log!(context, "  {:?}", co64);
+            BoxType::ChunkOffsetBox => {
+                let stco = try!(read_stco(&mut b));
+                log!("{:?}", stco);
             }
-            b"stss" => {
-                let stss = try!(read_stss(&mut content, &h));
-                log!(context, "  {:?}", stss);
+            BoxType::ChunkLargeOffsetBox => {
+                let co64 = try!(read_co64(&mut b));
+                log!("{:?}", co64);
             }
-            _ => {
-                // Skip the contents of unknown chunks.
-                log!(context, "{:?} (skipped)", h);
-                try!(skip_box_content(&mut content, &h));
+            BoxType::SyncSampleBox => {
+                let stss = try!(read_stss(&mut b));
+                log!("{:?}", stss);
             }
+            _ => try!(skip_box_content(&mut b)),
         };
-        Ok(())
-    })
+        check_parser_state!(b.content);
+    }
+    Ok(())
 }
 
 /// Parse an ftyp box.
-fn read_ftyp<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<FileTypeBox> {
-    let major = try!(be_fourcc(src));
+fn read_ftyp<T: Read>(src: &mut BMFFBox<T>) -> Result<FileTypeBox> {
+    let major = try!(be_u32(src));
     let minor = try!(be_u32(src));
-    let bytes_left = head.size - head.offset - 8;
+    let bytes_left = src.bytes_left();
     if bytes_left % 4 != 0 {
-        return Err(Error::InvalidData);
+        return Err(Error::InvalidData("invalid ftyp size"));
     }
     // Is a brand_count of zero valid?
     let brand_count = bytes_left / 4;
     let mut brands = Vec::new();
     for _ in 0..brand_count {
-        brands.push(try!(be_fourcc(src)));
+        brands.push(try!(be_u32(src)));
     }
     Ok(FileTypeBox {
-        header: *head,
         major_brand: major,
         minor_version: minor,
         compatible_brands: brands,
     })
 }
 
 /// Parse an mvhd box.
-fn read_mvhd<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> Result<MovieHeaderBox> {
+fn read_mvhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieHeaderBox> {
     let (version, _) = try!(read_fullbox_extra(src));
     match version {
         // 64 bit creation and modification times.
         1 => {
             try!(skip(src, 16));
         }
         // 32 bit creation and modification times.
         0 => {
             try!(skip(src, 8));
         }
-        _ => return Err(Error::InvalidData),
+        _ => return Err(Error::InvalidData("unhandled mvhd version")),
     }
     let timescale = try!(be_u32(src));
     let duration = match version {
         1 => try!(be_u64(src)),
         0 => {
             let d = try!(be_u32(src));
             if d == std::u32::MAX {
                 std::u64::MAX
             } else {
                 d as u64
             }
         }
-        _ => return Err(Error::InvalidData),
+        _ => return Err(Error::InvalidData("unhandled mvhd version")),
     };
     // Skip remaining fields.
     try!(skip(src, 80));
     Ok(MovieHeaderBox {
-        header: *head,
         timescale: timescale,
         duration: duration,
     })
 }
 
 /// Parse a tkhd box.
-fn read_tkhd<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> Result<TrackHeaderBox> {
+fn read_tkhd<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackHeaderBox> {
     let (version, flags) = try!(read_fullbox_extra(src));
     let disabled = flags & 0x1u32 == 0 || flags & 0x2u32 == 0;
     match version {
         // 64 bit creation and modification times.
         1 => {
             try!(skip(src, 16));
         }
         // 32 bit creation and modification times.
         0 => {
             try!(skip(src, 8));
         }
-        _ => return Err(Error::InvalidData),
+        _ => return Err(Error::InvalidData("unhandled tkhd version")),
     }
     let track_id = try!(be_u32(src));
     try!(skip(src, 4));
     let duration = match version {
         1 => try!(be_u64(src)),
         0 => try!(be_u32(src)) as u64,
-        _ => return Err(Error::InvalidData),
+        _ => return Err(Error::InvalidData("unhandled tkhd version")),
     };
     // Skip uninteresting fields.
     try!(skip(src, 52));
     let width = try!(be_u32(src));
     let height = try!(be_u32(src));
     Ok(TrackHeaderBox {
-        header: *head,
         track_id: track_id,
         disabled: disabled,
         duration: duration,
         width: width,
         height: height,
     })
 }
 
 /// Parse a elst box.
-fn read_elst<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<EditListBox> {
+fn read_elst<T: Read>(src: &mut BMFFBox<T>) -> Result<EditListBox> {
     let (version, _) = try!(read_fullbox_extra(src));
     let edit_count = try!(be_u32(src));
+    if edit_count == 0 {
+        return Err(Error::InvalidData("invalid edit count"));
+    }
     let mut edits = Vec::new();
     for _ in 0..edit_count {
         let (segment_duration, media_time) = match version {
             1 => {
                 // 64 bit segment duration and media times.
                 (try!(be_u64(src)), try!(be_i64(src)))
             }
             0 => {
                 // 32 bit segment duration and media times.
                 (try!(be_u32(src)) as u64, try!(be_i32(src)) as i64)
             }
-            _ => return Err(Error::InvalidData),
+            _ => return Err(Error::InvalidData("unhandled elst version")),
         };
         let media_rate_integer = try!(be_i16(src));
         let media_rate_fraction = try!(be_i16(src));
         edits.push(Edit {
             segment_duration: segment_duration,
             media_time: media_time,
             media_rate_integer: media_rate_integer,
             media_rate_fraction: media_rate_fraction,
         })
     }
 
     Ok(EditListBox {
-        header: *head,
         edits: edits,
     })
 }
 
 /// Parse a mdhd box.
-fn read_mdhd<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> Result<MediaHeaderBox> {
+fn read_mdhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MediaHeaderBox> {
     let (version, _) = try!(read_fullbox_extra(src));
     let (timescale, duration) = match version {
         1 => {
             // Skip 64-bit creation and modification times.
             try!(skip(src, 16));
 
             // 64 bit duration.
             (try!(be_u32(src)), try!(be_u64(src)))
@@ -839,140 +834,133 @@ fn read_mdhd<T: ReadBytesExt + BufRead>(
                 if d == std::u32::MAX {
                     std::u64::MAX
                 } else {
                     d as u64
                 }
             };
             (timescale, duration)
         }
-        _ => return Err(Error::InvalidData),
+        _ => return Err(Error::InvalidData("unhandled mdhd version")),
     };
 
     // Skip uninteresting fields.
     try!(skip(src, 4));
 
     Ok(MediaHeaderBox {
-        header: *head,
         timescale: timescale,
         duration: duration,
     })
 }
 
 /// Parse a stco box.
-fn read_stco<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<ChunkOffsetBox> {
+fn read_stco<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> {
     let (_, _) = try!(read_fullbox_extra(src));
     let offset_count = try!(be_u32(src));
     let mut offsets = Vec::new();
     for _ in 0..offset_count {
         offsets.push(try!(be_u32(src)) as u64);
     }
 
     Ok(ChunkOffsetBox {
-        header: *head,
         offsets: offsets,
     })
 }
 
-/// Parse a stco box.
-fn read_co64<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<ChunkOffsetBox> {
+/// Parse a co64 box.
+fn read_co64<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> {
     let (_, _) = try!(read_fullbox_extra(src));
     let offset_count = try!(be_u32(src));
     let mut offsets = Vec::new();
     for _ in 0..offset_count {
         offsets.push(try!(be_u64(src)));
     }
 
     Ok(ChunkOffsetBox {
-        header: *head,
         offsets: offsets,
     })
 }
 
 /// Parse a stss box.
-fn read_stss<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<SyncSampleBox> {
+fn read_stss<T: Read>(src: &mut BMFFBox<T>) -> Result<SyncSampleBox> {
     let (_, _) = try!(read_fullbox_extra(src));
     let sample_count = try!(be_u32(src));
     let mut samples = Vec::new();
     for _ in 0..sample_count {
         samples.push(try!(be_u32(src)));
     }
 
     Ok(SyncSampleBox {
-        header: *head,
         samples: samples,
     })
 }
 
 /// Parse a stsc box.
-fn read_stsc<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<SampleToChunkBox> {
+fn read_stsc<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleToChunkBox> {
     let (_, _) = try!(read_fullbox_extra(src));
     let sample_count = try!(be_u32(src));
     let mut samples = Vec::new();
     for _ in 0..sample_count {
         let first_chunk = try!(be_u32(src));
         let samples_per_chunk = try!(be_u32(src));
         let sample_description_index = try!(be_u32(src));
         samples.push(SampleToChunk {
             first_chunk: first_chunk,
             samples_per_chunk: samples_per_chunk,
             sample_description_index: sample_description_index,
         });
     }
 
     Ok(SampleToChunkBox {
-        header: *head,
         samples: samples,
     })
 }
 
 /// Parse a stsz box.
-fn read_stsz<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<SampleSizeBox> {
+fn read_stsz<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleSizeBox> {
     let (_, _) = try!(read_fullbox_extra(src));
     let sample_size = try!(be_u32(src));
     let sample_count = try!(be_u32(src));
     let mut sample_sizes = Vec::new();
     if sample_size == 0 {
         for _ in 0..sample_count {
             sample_sizes.push(try!(be_u32(src)));
         }
     }
 
     Ok(SampleSizeBox {
-        header: *head,
         sample_size: sample_size,
         sample_sizes: sample_sizes,
     })
 }
 
 /// Parse a stts box.
-fn read_stts<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> Result<TimeToSampleBox> {
+fn read_stts<T: Read>(src: &mut BMFFBox<T>) -> Result<TimeToSampleBox> {
     let (_, _) = try!(read_fullbox_extra(src));
     let sample_count = try!(be_u32(src));
     let mut samples = Vec::new();
     for _ in 0..sample_count {
         let sample_count = try!(be_u32(src));
         let sample_delta = try!(be_u32(src));
         samples.push(Sample {
             sample_count: sample_count,
             sample_delta: sample_delta,
         });
     }
 
     Ok(TimeToSampleBox {
-        header: *head,
         samples: samples,
     })
 }
 
 /// Parse a VPx Config Box.
-fn read_vpcc<T: ReadBytesExt>(src: &mut T) -> Result<VPxConfigBox> {
+fn read_vpcc<T: Read>(src: &mut BMFFBox<T>) -> Result<VPxConfigBox> {
     let (version, _) = try!(read_fullbox_extra(src));
     if version != 0 {
-        return Err(Error::Unsupported);
+        return Err(Error::Unsupported("unknown vpcC version"));
     }
 
     let profile = try!(src.read_u8());
     let level = try!(src.read_u8());
     let (bit_depth, color_space) = {
         let byte = try!(src.read_u8());
         ((byte >> 4) & 0x0f, byte & 0x0f)
     };
@@ -993,20 +981,20 @@ fn read_vpcc<T: ReadBytesExt>(src: &mut 
         chroma_subsampling: chroma_subsampling,
         transfer_function: transfer_function,
         video_full_range: video_full_range,
         codec_init: codec_init,
     })
 }
 
 /// Parse OpusSpecificBox.
-fn read_dops<T: ReadBytesExt>(src: &mut T) -> Result<OpusSpecificBox> {
+fn read_dops<T: Read>(src: &mut BMFFBox<T>) -> Result<OpusSpecificBox> {
     let version = try!(src.read_u8());
     if version != 0 {
-        return Err(Error::Unsupported);
+        return Err(Error::Unsupported("unknown dOps version"));
     }
 
     let output_channel_count = try!(src.read_u8());
     let pre_skip = try!(be_u16(src));
     let input_sample_rate = try!(be_u32(src));
     let output_gain = try!(be_i16(src));
     let channel_mapping_family = try!(src.read_u8());
 
@@ -1031,52 +1019,88 @@ fn read_dops<T: ReadBytesExt>(src: &mut 
         pre_skip: pre_skip,
         input_sample_rate: input_sample_rate,
         output_gain: output_gain,
         channel_mapping_family: channel_mapping_family,
         channel_mapping_table: channel_mapping_table,
     })
 }
 
+/// Re-serialize the Opus codec-specific config data as an OpusHead packet.
+///
+/// Some decoders expect the initialization data in the format used by the
+/// Ogg and WebM encapsulations. To support this we prepend the 'OpusHead'
+/// tag and byte-swap the data from big- to little-endian relative to the
+/// dOps box.
+fn serialize_opus_header<W: byteorder::WriteBytesExt + std::io::Write>(opus: &OpusSpecificBox, dst: &mut W) -> Result<()> {
+    match dst.write(b"OpusHead") {
+        Err(e) => return Err(Error::from(e)),
+        Ok(bytes) => {
+            if bytes != 8 {
+                return Err(Error::InvalidData("Couldn't write OpusHead tag."));
+            }
+        }
+    }
+    // In mp4 encapsulation, the version field is 0, but in ogg
+    // it is 1. While decoders generally accept zero as well, write
+    // out the version of the header we're supporting rather than
+    // whatever we parsed out of mp4.
+    try!(dst.write_u8(1));
+    try!(dst.write_u8(opus.output_channel_count));
+    try!(dst.write_u16::<byteorder::LittleEndian>(opus.pre_skip));
+    try!(dst.write_u32::<byteorder::LittleEndian>(opus.input_sample_rate));
+    try!(dst.write_i16::<byteorder::LittleEndian>(opus.output_gain));
+    try!(dst.write_u8(opus.channel_mapping_family));
+    match opus.channel_mapping_table {
+        None => {}
+        Some(ref table) => {
+            try!(dst.write_u8(table.stream_count));
+            try!(dst.write_u8(table.coupled_count));
+            match dst.write(&table.channel_mapping) {
+                Err(e) => return Err(Error::from(e)),
+                Ok(bytes) => {
+                    if bytes != table.channel_mapping.len() {
+                        return Err(Error::InvalidData("Couldn't write channel mapping table data."));
+                    }
+                }
+            }
+        }
+    };
+    Ok(())
+}
+
 /// Parse a hdlr box.
-fn read_hdlr<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> Result<HandlerBox> {
+fn read_hdlr<T: Read>(src: &mut BMFFBox<T>) -> Result<HandlerBox> {
     let (_, _) = try!(read_fullbox_extra(src));
 
     // Skip uninteresting fields.
     try!(skip(src, 4));
 
-    let handler_type = try!(be_fourcc(src));
+    let handler_type = try!(be_u32(src));
 
     // Skip uninteresting fields.
     try!(skip(src, 12));
 
-    // XXX(kinetik): need to verify if this zero-length string handling
-    // applies to all "null-terminated" strings (in which case
-    // read_null_terminated_string should handle this check) or this is
-    // specific to the hdlr box.
-    let bytes_left = head.size - head.offset - 24;
-    if bytes_left > 0 {
-        let _name = try!(read_null_terminated_string(src));
-    }
+    let bytes_left = src.bytes_left();
+    let _name = try!(read_null_terminated_string(src, bytes_left));
 
     Ok(HandlerBox {
-        header: *head,
         handler_type: handler_type,
     })
 }
 
 /// Parse an video description inside an stsd box.
-fn read_video_desc<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader, track: &mut Track) -> Result<SampleEntry> {
-    let h = try!(read_box_header(src));
-    track.mime_type = match h.name.as_bytes() {
-        b"avc1" | b"avc3" => String::from("video/avc"),
-        b"vp08" => String::from("video/vp8"),
-        b"vp09" => String::from("video/vp9"),
-        // TODO(kinetik): encv here also.
-        _ => return Err(Error::Unsupported),
+fn read_video_desc<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleEntry> {
+    let name = src.get_header().name;
+    track.mime_type = match name {
+        BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => String::from("video/avc"),
+        BoxType::VP8SampleEntry => String::from("video/vp8"),
+        BoxType::VP9SampleEntry => String::from("video/vp9"),
+        BoxType::ProtectedVisualSampleEntry => String::from("video/crypto"),
+        _ => return Err(Error::Unsupported("unhandled video sample entry type")),
     };
 
     // Skip uninteresting fields.
     try!(skip(src, 6));
 
     let data_reference_index = try!(be_u16(src));
 
     // Skip uninteresting fields.
@@ -1088,216 +1112,277 @@ fn read_video_desc<T: ReadBytesExt + Buf
     // Skip uninteresting fields.
     try!(skip(src, 14));
 
     let _compressorname = try!(read_fixed_length_pascal_string(src, 32));
 
     // Skip uninteresting fields.
     try!(skip(src, 4));
 
-    let h = try!(read_box_header(src));
-    let codec_specific = match h.name.as_bytes() {
-        b"avcC" => {
-            // TODO(kinetik): Parse avcC atom?  For now we just stash the data.
-            let avcc = try!(read_buf(src, (h.size - h.offset) as usize));
-            VideoCodecSpecific::AVCConfig(avcc)
+    // Skip clap/pasp/etc. for now.
+    let mut codec_specific = None;
+    let mut iter = src.box_iter();
+    while let Some(mut b) = try!(iter.next_box()) {
+        match b.head.name {
+            BoxType::AVCConfigurationBox => {
+                if (name != BoxType::AVCSampleEntry &&
+                    name != BoxType::AVC3SampleEntry &&
+                    name != BoxType::ProtectedVisualSampleEntry) ||
+                    codec_specific.is_some() {
+                        return Err(Error::InvalidData("malformed video sample entry"));
+                    }
+                let avcc_size = b.head.size - b.head.offset;
+                if avcc_size > BUF_SIZE_LIMIT {
+                    return Err(Error::InvalidData("avcC box exceeds BUF_SIZE_LIMIT"));
+                }
+                let avcc = try!(read_buf(&mut b.content, avcc_size as usize));
+                // TODO(kinetik): Parse avcC box?  For now we just stash the data.
+                codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc));
+            }
+            BoxType::VPCodecConfigurationBox => { // vpcC
+                if (name != BoxType::VP8SampleEntry &&
+                    name != BoxType::VP9SampleEntry) ||
+                    codec_specific.is_some() {
+                        return Err(Error::InvalidData("malformed video sample entry"));
+                    }
+                let vpcc = try!(read_vpcc(&mut b));
+                codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc));
+            }
+            _ => try!(skip_box_content(&mut b)),
         }
-        b"vpcC" => {
-            let vpcc = try!(read_vpcc(src));
-            VideoCodecSpecific::VPxConfig(vpcc)
-        }
-        _ => return Err(Error::Unsupported),
-    };
+        check_parser_state!(b.content);
+    }
 
-    // Skip clap/pasp/etc. for now.
-    try!(skip_remaining_box_content(src, head));
+    if codec_specific.is_none() {
+        return Err(Error::InvalidData("malformed video sample entry"));
+    }
 
     Ok(SampleEntry::Video(VideoSampleEntry {
         data_reference_index: data_reference_index,
         width: width,
         height: height,
-        codec_specific: codec_specific,
+        codec_specific: codec_specific.unwrap(),
     }))
 }
 
 /// Parse an audio description inside an stsd box.
-fn read_audio_desc<T: ReadBytesExt + BufRead>(src: &mut T, _: &BoxHeader, track: &mut Track) -> Result<SampleEntry> {
-    let h = try!(read_box_header(src));
-    // TODO(kinetik): enca here also?
-    match h.name.as_bytes() {
-        b"mp4a" => (),
-        b"Opus" => (),
-        _ => return Err(Error::Unsupported),
-    }
+fn read_audio_desc<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleEntry> {
+    let name = src.get_header().name;
+    track.mime_type = match name {
+        // TODO(kinetik): stagefright inspects ESDS to detect MP3 (audio/mpeg).
+        BoxType::MP4AudioSampleEntry => String::from("audio/mp4a-latm"),
+        // TODO(kinetik): stagefright doesn't have a MIME mapping for this, revisit.
+        BoxType::OpusSampleEntry => String::from("audio/opus"),
+        BoxType::ProtectedAudioSampleEntry => String::from("audio/crypto"),
+        _ => return Err(Error::Unsupported("unhandled audio sample entry type")),
+    };
 
     // Skip uninteresting fields.
     try!(skip(src, 6));
 
     let data_reference_index = try!(be_u16(src));
 
+    // XXX(kinetik): This is "reserved" in BMFF, but some old QT MOV variant
+    // uses it, need to work out if we have to support it.  Without checking
+    // here and reading extra fields after samplerate (or bailing with an
+    // error), the parser loses sync completely.
+    let version = try!(be_u16(src));
+
     // Skip uninteresting fields.
-    try!(skip(src, 8));
+    try!(skip(src, 6));
 
     let channelcount = try!(be_u16(src));
     let samplesize = try!(be_u16(src));
 
     // Skip uninteresting fields.
     try!(skip(src, 4));
 
     let samplerate = try!(be_u32(src));
 
-    let h = try!(read_box_header(src));
-    let codec_specific = match h.name.as_bytes() {
-        b"esds" => {
-            let (_, _) = try!(read_fullbox_extra(src));
-            let esds = try!(read_buf(src, (h.size - h.offset - 4) as usize));
-
-            // TODO(kinetik): stagefright inspects ESDS to detect MP3 (audio/mpeg).
-            track.mime_type = String::from("audio/mp4a-latm");
+    match version {
+        0 => (),
+        _ => return Err(Error::Unsupported("unsupported non-isom audio sample entry")),
+    }
 
-            // TODO(kinetik): Parse esds atom?  For now we just stash the data.
-            AudioCodecSpecific::ES_Descriptor(esds)
+    // Skip chan/etc. for now.
+    let mut codec_specific = None;
+    let mut iter = src.box_iter();
+    while let Some(mut b) = try!(iter.next_box()) {
+        match b.head.name {
+            BoxType::ESDBox => {
+                if (name != BoxType::MP4AudioSampleEntry &&
+                    name != BoxType::ProtectedAudioSampleEntry) ||
+                    codec_specific.is_some() {
+                        return Err(Error::InvalidData("malformed audio sample entry"));
+                    }
+                let (_, _) = try!(read_fullbox_extra(&mut b.content));
+                let esds_size = b.head.size - b.head.offset - 4;
+                if esds_size > BUF_SIZE_LIMIT {
+                    return Err(Error::InvalidData("esds box exceeds BUF_SIZE_LIMIT"));
+                }
+                let esds = try!(read_buf(&mut b.content, esds_size as usize));
+                // TODO(kinetik): Parse esds box?  For now we just stash the data.
+                codec_specific = Some(AudioCodecSpecific::ES_Descriptor(esds));
+            }
+            BoxType::OpusSpecificBox => {
+                if name != BoxType::OpusSampleEntry ||
+                    codec_specific.is_some() {
+                    return Err(Error::InvalidData("malformed audio sample entry"));
+                }
+                let dops = try!(read_dops(&mut b));
+                codec_specific = Some(AudioCodecSpecific::OpusSpecificBox(dops));
+            }
+            _ => try!(skip_box_content(&mut b)),
         }
-        b"dOps" => {
-            let dops = try!(read_dops(src));
-            // TODO(kinetik): stagefright doesn't have a MIME mapping for this, revisit.
-            track.mime_type = String::from("audio/opus");
+        check_parser_state!(b.content);
+    }
 
-            AudioCodecSpecific::OpusSpecificBox(dops)
-        }
-        _ => return Err(Error::Unsupported),
-    };
+    if codec_specific.is_none() {
+        return Err(Error::InvalidData("malformed audio sample entry"));
+    }
+
     Ok(SampleEntry::Audio(AudioSampleEntry {
         data_reference_index: data_reference_index,
         channelcount: channelcount,
         samplesize: samplesize,
         samplerate: samplerate,
-        codec_specific: codec_specific,
+        codec_specific: codec_specific.unwrap(),
     }))
 }
 
 /// Parse a stsd box.
-fn read_stsd<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader, track: &mut Track) -> Result<SampleDescriptionBox> {
+fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleDescriptionBox> {
     let (_, _) = try!(read_fullbox_extra(src));
 
     let description_count = try!(be_u32(src));
     let mut descriptions = Vec::new();
 
     // TODO(kinetik): check if/when more than one desc per track? do we need to support?
-    for _ in 0..description_count {
+    let mut iter = src.box_iter();
+    while let Some(mut b) = try!(iter.next_box()) {
         let description = match track.track_type {
-            TrackType::Video => try!(read_video_desc(src, head, track)),
-            TrackType::Audio => try!(read_audio_desc(src, head, track)),
-            TrackType::Unknown => SampleEntry::Unknown,
+            TrackType::Video => read_video_desc(&mut b, track),
+            TrackType::Audio => read_audio_desc(&mut b, track),
+            TrackType::Unknown => Err(Error::Unsupported("unknown track type")),
+        };
+        let description = match description {
+            Ok(desc) => desc,
+            Err(Error::Unsupported(_)) => {
+                // read_{audio,video}_desc may have returned Unsupported
+                // after partially reading the box content, so we can't
+                // simply use skip_box_content here.
+                let to_skip = b.bytes_left();
+                try!(skip(&mut b, to_skip));
+                SampleEntry::Unknown
+            }
+            Err(e) => return Err(e),
         };
         if track.data.is_none() {
             track.data = Some(description.clone());
         } else {
-            return Err(Error::InvalidData);
+            log!("** don't know how to handle multiple descriptions **");
         }
         descriptions.push(description);
+        check_parser_state!(b.content);
+        if descriptions.len() == description_count as usize {
+            break;
+        }
     }
 
     Ok(SampleDescriptionBox {
-        header: *head,
         descriptions: descriptions,
     })
 }
 
 /// Skip a number of bytes that we don't care to parse.
-fn skip<T: BufRead>(src: &mut T, bytes: usize) -> Result<usize> {
-    let mut bytes_to_skip = bytes;
-    while bytes_to_skip > 0 {
-        let len = try!(src.fill_buf()).len();
+fn skip<T: Read>(src: &mut T, mut bytes: usize) -> Result<()> {
+    const BUF_SIZE: usize = 64 * 1024;
+    let mut buf = vec![0; BUF_SIZE];
+    while bytes > 0 {
+        let buf_size = cmp::min(bytes, BUF_SIZE);
+        let len = try!(src.take(buf_size as u64).read(&mut buf));
         if len == 0 {
             return Err(Error::UnexpectedEOF);
         }
-        let discard = cmp::min(len, bytes_to_skip);
-        src.consume(discard);
-        bytes_to_skip -= discard;
+        bytes -= len;
     }
-    assert!(bytes_to_skip == 0);
-    Ok(bytes)
+    Ok(())
 }
 
 /// Read size bytes into a Vector or return error.
 fn read_buf<T: ReadBytesExt>(src: &mut T, size: usize) -> Result<Vec<u8>> {
     let mut buf = vec![0; size];
     let r = try!(src.read(&mut buf));
     if r != size {
-        return Err(Error::InvalidData);
+        return Err(Error::InvalidData("failed buffer read"));
     }
     Ok(buf)
 }
 
 // TODO(kinetik): Find a copy of ISO/IEC 14496-1 to confirm various string encodings.
-fn read_null_terminated_string<T: ReadBytesExt>(src: &mut T) -> Result<String> {
+// XXX(kinetik): definition of "null-terminated" string is fuzzy, we have:
+// - zero or more byte strings, with a single null terminating the string.
+// - zero byte strings with no null terminator (i.e. zero space in the box for the string)
+// - length-prefixed strings with no null terminator (e.g. bear_rotate_0.mp4)
+fn read_null_terminated_string<T: ReadBytesExt>(src: &mut T, mut size: usize) -> Result<String> {
     let mut buf = Vec::new();
-    loop {
+    while size > 0 {
         let c = try!(src.read_u8());
         if c == 0 {
             break;
         }
         buf.push(c);
+        size -= 1;
     }
-    Ok(try!(String::from_utf8(buf)))
+    String::from_utf8(buf).map_err(From::from)
 }
 
+#[allow(dead_code)]
 fn read_pascal_string<T: ReadBytesExt>(src: &mut T) -> Result<String> {
     let len = try!(src.read_u8());
     let buf = try!(read_buf(src, len as usize));
-    Ok(try!(String::from_utf8(buf)))
+    String::from_utf8(buf).map_err(From::from)
 }
 
 // Weird string encoding with a length prefix and a fixed sized buffer which
 // contains padding if the string doesn't fill the buffer.
-fn read_fixed_length_pascal_string<T: BufRead>(src: &mut T, size: usize) -> Result<String> {
-    let s = try!(read_pascal_string(src));
-    try!(skip(src, size - 1 - s.len()));
-    Ok(s)
+fn read_fixed_length_pascal_string<T: Read>(src: &mut T, size: usize) -> Result<String> {
+    assert!(size > 0);
+    let len = cmp::min(try!(src.read_u8()) as usize, size - 1);
+    let buf = try!(read_buf(src, len));
+    try!(skip(src, size - 1 - buf.len()));
+    String::from_utf8(buf).map_err(From::from)
 }
 
 fn media_time_to_ms(time: MediaScaledTime, scale: MediaTimeScale) -> u64 {
     assert!(scale.0 != 0);
     time.0 * 1000000 / scale.0
 }
 
 fn track_time_to_ms(time: TrackScaledTime, scale: TrackTimeScale) -> u64 {
     assert!(time.1 == scale.1);
     assert!(scale.0 != 0);
     time.0 * 1000000 / scale.0
 }
 
-fn be_i16<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<i16> {
-    src.read_i16::<byteorder::BigEndian>()
+fn be_i16<T: ReadBytesExt>(src: &mut T) -> Result<i16> {
+    src.read_i16::<byteorder::BigEndian>().map_err(From::from)
 }
 
-fn be_i32<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<i32> {
-    src.read_i32::<byteorder::BigEndian>()
+fn be_i32<T: ReadBytesExt>(src: &mut T) -> Result<i32> {
+    src.read_i32::<byteorder::BigEndian>().map_err(From::from)
 }
 
-fn be_i64<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<i64> {
-    src.read_i64::<byteorder::BigEndian>()
-}
-
-fn be_u16<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u16> {
-    src.read_u16::<byteorder::BigEndian>()
+fn be_i64<T: ReadBytesExt>(src: &mut T) -> Result<i64> {
+    src.read_i64::<byteorder::BigEndian>().map_err(From::from)
 }
 
-fn be_u32<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u32> {
-    src.read_u32::<byteorder::BigEndian>()
-}
-
-fn be_u64<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u64> {
-    src.read_u64::<byteorder::BigEndian>()
+fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> {
+    src.read_u16::<byteorder::BigEndian>().map_err(From::from)
 }
 
-fn be_fourcc<T: Read>(src: &mut T) -> Result<FourCC> {
-    let mut fourcc = [0; 4];
-    match src.read(&mut fourcc) {
-        // Expect all 4 bytes read.
-        Ok(4) => Ok(FourCC(fourcc)),
-        // Short read means EOF.
-        Ok(_) => Err(Error::UnexpectedEOF),
-        // Propagate std::io errors.
-        Err(e) => Err(Error::Io(e)),
-    }
+fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
+    src.read_u32::<byteorder::BigEndian>().map_err(From::from)
 }
+
+fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
+    src.read_u64::<byteorder::BigEndian>().map_err(From::from)
+}
--- a/media/libstagefright/binding/mp4parse/tests.rs
+++ b/media/libstagefright/binding/mp4parse/tests.rs
@@ -5,25 +5,27 @@
 // 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 std::io::Cursor;
 use super::*;
 extern crate test_assembler;
 use self::test_assembler::*;
 
+use boxes::BoxType;
+
 enum BoxSize {
     Short(u32),
     Long(u64),
     UncheckedShort(u32),
     UncheckedLong(u64),
     Auto,
 }
 
-fn make_box_raw<F>(size: BoxSize, name: &[u8; 4], func: F) -> Cursor<Vec<u8>>
+fn make_box<F>(size: BoxSize, name: &[u8; 4], func: F) -> Cursor<Vec<u8>>
     where F: Fn(Section) -> Section
 {
     let mut section = Section::new();
     let box_size = Label::new();
     section = match size {
         BoxSize::Short(size) | BoxSize::UncheckedShort(size) => section.B32(size),
         BoxSize::Long(_) | BoxSize::UncheckedLong(_) => section.B32(1),
         BoxSize::Auto => section.B32(&box_size),
@@ -45,135 +47,156 @@ fn make_box_raw<F>(size: BoxSize, name: 
         BoxSize::Short(size) => {
             if size > 0 {
                 assert_eq!(size as u64, section.size())
             }
         }
         BoxSize::Long(size) => assert_eq!(size, section.size()),
         BoxSize::Auto => {
             assert!(section.size() <= u32::max_value() as u64,
-              "Tried to use a long box with BoxSize::Auto");
+                    "Tried to use a long box with BoxSize::Auto");
             box_size.set_const(section.size());
-        },
+        }
         // Skip checking BoxSize::Unchecked* cases.
         _ => (),
     }
     Cursor::new(section.get_contents().unwrap())
 }
 
-fn make_box<F>(size: u32, name: &[u8; 4], func: F) -> Cursor<Vec<u8>>
-    where F: Fn(Section) -> Section
-{
-    make_box_raw(BoxSize::Short(size), name, func)
-}
-
 fn make_fullbox<F>(size: BoxSize, name: &[u8; 4], version: u8, func: F) -> Cursor<Vec<u8>>
     where F: Fn(Section) -> Section
 {
-    make_box_raw(size, name, |s| {
+    make_box(size, name, |s| {
         func(s.B8(version)
               .B8(0)
               .B8(0)
               .B8(0))
     })
 }
 
 #[test]
 fn read_box_header_short() {
-    let mut stream = make_box_raw(BoxSize::Short(8), b"test", |s| s);
-    let parsed = read_box_header(&mut stream).unwrap();
-    assert_eq!(parsed.name, FourCC(*b"test"));
-    assert_eq!(parsed.size, 8);
+    let mut stream = make_box(BoxSize::Short(8), b"test", |s| s);
+    let header = super::read_box_header(&mut stream).unwrap();
+    assert_eq!(header.name, BoxType::UnknownBox(0x74657374)); // "test"
+    assert_eq!(header.size, 8);
 }
 
 #[test]
 fn read_box_header_long() {
-    let mut stream = make_box_raw(BoxSize::Long(16), b"test", |s| s);
-    let parsed = read_box_header(&mut stream).unwrap();
-    assert_eq!(parsed.name, FourCC(*b"test"));
-    assert_eq!(parsed.size, 16);
+    let mut stream = make_box(BoxSize::Long(16), b"test", |s| s);
+    let header = super::read_box_header(&mut stream).unwrap();
+    assert_eq!(header.name, BoxType::UnknownBox(0x74657374)); // "test"
+    assert_eq!(header.size, 16);
 }
 
 #[test]
 fn read_box_header_short_unknown_size() {
-    let mut stream = make_box_raw(BoxSize::Short(0), b"test", |s| s);
-    match read_box_header(&mut stream) {
-        Err(Error::Unsupported) => (),
+    let mut stream = make_box(BoxSize::Short(0), b"test", |s| s);
+    match super::read_box_header(&mut stream) {
+        Err(Error::Unsupported(s)) => assert_eq!(s, "unknown sized box"),
         _ => panic!("unexpected result reading box with unknown size"),
     };
 }
 
 #[test]
 fn read_box_header_short_invalid_size() {
-    let mut stream = make_box_raw(BoxSize::UncheckedShort(2), b"test", |s| s);
-    match read_box_header(&mut stream) {
-        Err(Error::InvalidData) => (),
+    let mut stream = make_box(BoxSize::UncheckedShort(2), b"test", |s| s);
+    match super::read_box_header(&mut stream) {
+        Err(Error::InvalidData(s)) => assert_eq!(s, "malformed size"),
         _ => panic!("unexpected result reading box with invalid size"),
     };
 }
 
 #[test]
 fn read_box_header_long_invalid_size() {
-    let mut stream = make_box_raw(BoxSize::UncheckedLong(2), b"test", |s| s);
-    match read_box_header(&mut stream) {
-        Err(Error::InvalidData) => (),
+    let mut stream = make_box(BoxSize::UncheckedLong(2), b"test", |s| s);
+    match super::read_box_header(&mut stream) {
+        Err(Error::InvalidData(s)) => assert_eq!(s, "malformed wide size"),
         _ => panic!("unexpected result reading box with invalid size"),
     };
 }
 
 #[test]
 fn read_ftyp() {
-    let mut stream = make_box(24, b"ftyp", |s| {
+    let mut stream = make_box(BoxSize::Short(24), b"ftyp", |s| {
         s.append_bytes(b"mp42")
          .B32(0) // minor version
          .append_bytes(b"isom")
          .append_bytes(b"mp42")
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_ftyp(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"ftyp"));
-    assert_eq!(parsed.header.size, 24);
-    assert_eq!(parsed.major_brand, FourCC(*b"mp42"));
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    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, 0x6d703432); // mp42
     assert_eq!(parsed.minor_version, 0);
     assert_eq!(parsed.compatible_brands.len(), 2);
-    assert_eq!(parsed.compatible_brands[0], FourCC(*b"isom"));
-    assert_eq!(parsed.compatible_brands[1], FourCC(*b"mp42"));
+    assert_eq!(parsed.compatible_brands[0], 0x69736f6d); // isom
+    assert_eq!(parsed.compatible_brands[1], 0x6d703432); // mp42
 }
 
 #[test]
-#[should_panic(expected = "expected an error result")]
 fn read_truncated_ftyp() {
     // We declare a 24 byte box, but only write 20 bytes.
-    let mut stream = make_box_raw(BoxSize::UncheckedShort(24), b"ftyp", |s| {
+    let mut stream = make_box(BoxSize::UncheckedShort(24), b"ftyp", |s| {
         s.append_bytes(b"mp42")
             .B32(0) // minor version
             .append_bytes(b"isom")
     });
     let mut context = MediaContext::new();
     match read_mp4(&mut stream, &mut context) {
         Err(Error::UnexpectedEOF) => (),
         Ok(_) => assert!(false, "expected an error result"),
         _ => assert!(false, "expected a different error result"),
     }
 }
 
 #[test]
+fn read_ftyp_case() {
+    // Brands in BMFF are represented as a u32, so it would seem clear that
+    // 0x6d703432 ("mp42") is not equal to 0x4d503432 ("MP42"), but some
+    // demuxers treat these as case-insensitive strings, e.g. street.mp4's
+    // major brand is "MP42".  I haven't seen case-insensitive
+    // compatible_brands (which we also test here), but it doesn't seem
+    // unlikely given the major_brand behaviour.
+    let mut stream = make_box(BoxSize::Auto, b"ftyp", |s| {
+        s.append_bytes(b"MP42")
+         .B32(0) // minor version
+         .append_bytes(b"ISOM")
+         .append_bytes(b"MP42")
+    });
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    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, 0x4d503432);
+    assert_eq!(parsed.minor_version, 0);
+    assert_eq!(parsed.compatible_brands.len(), 2);
+    assert_eq!(parsed.compatible_brands[0], 0x49534f4d); // ISOM
+    assert_eq!(parsed.compatible_brands[1], 0x4d503432); // MP42
+}
+
+#[test]
 fn read_elst_v0() {
     let mut stream = make_fullbox(BoxSize::Short(28), b"elst", 0, |s| {
         s.B32(1) // list count
           // first entry
          .B32(1234) // duration
          .B32(5678) // time
          .B16(12) // rate integer
          .B16(34) // rate fraction
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_elst(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"elst"));
-    assert_eq!(parsed.header.size, 28);
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::EditListBox);
+    assert_eq!(stream.head.size, 28);
+    let parsed = super::read_elst(&mut stream).unwrap();
     assert_eq!(parsed.edits.len(), 1);
     assert_eq!(parsed.edits[0].segment_duration, 1234);
     assert_eq!(parsed.edits[0].media_time, 5678);
     assert_eq!(parsed.edits[0].media_rate_integer, 12);
     assert_eq!(parsed.edits[0].media_rate_fraction, 34);
 }
 
 #[test]
@@ -186,216 +209,453 @@ fn read_elst_v1() {
          .B16(12) // rate integer
          .B16(34) // rate fraction
          // second entry
          .B64(1234) // duration
          .B64(5678) // time
          .B16(12) // rate integer
          .B16(34) // rate fraction
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_elst(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"elst"));
-    assert_eq!(parsed.header.size, 56);
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::EditListBox);
+    assert_eq!(stream.head.size, 56);
+    let parsed = super::read_elst(&mut stream).unwrap();
     assert_eq!(parsed.edits.len(), 2);
     assert_eq!(parsed.edits[1].segment_duration, 1234);
     assert_eq!(parsed.edits[1].media_time, 5678);
     assert_eq!(parsed.edits[1].media_rate_integer, 12);
     assert_eq!(parsed.edits[1].media_rate_fraction, 34);
 }
 
 #[test]
 fn read_mdhd_v0() {
     let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| {
         s.B32(0)
          .B32(0)
          .B32(1234) // timescale
          .B32(5678) // duration
          .B32(0)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_mdhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"mdhd"));
-    assert_eq!(parsed.header.size, 32);
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
+    assert_eq!(stream.head.size, 32);
+    let parsed = super::read_mdhd(&mut stream).unwrap();
     assert_eq!(parsed.timescale, 1234);
     assert_eq!(parsed.duration, 5678);
 }
 
 #[test]
 fn read_mdhd_v1() {
     let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| {
         s.B64(0)
          .B64(0)
          .B32(1234) // timescale
          .B64(5678) // duration
          .B32(0)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_mdhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"mdhd"));
-    assert_eq!(parsed.header.size, 44);
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
+    assert_eq!(stream.head.size, 44);
+    let parsed = super::read_mdhd(&mut stream).unwrap();
     assert_eq!(parsed.timescale, 1234);
     assert_eq!(parsed.duration, 5678);
 }
 
 #[test]
 fn read_mdhd_unknown_duration() {
     let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| {
         s.B32(0)
          .B32(0)
          .B32(1234) // timescale
          .B32(::std::u32::MAX) // duration
          .B32(0)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_mdhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"mdhd"));
-    assert_eq!(parsed.header.size, 32);
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
+    assert_eq!(stream.head.size, 32);
+    let parsed = super::read_mdhd(&mut stream).unwrap();
     assert_eq!(parsed.timescale, 1234);
     assert_eq!(parsed.duration, ::std::u64::MAX);
 }
 
 #[test]
 fn read_mdhd_invalid_timescale() {
     let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| {
         s.B64(0)
          .B64(0)
          .B32(0) // timescale
          .B64(5678) // duration
          .B32(0)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let r = super::parse_mdhd(&mut stream, &header, &mut super::Track::new(0));
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
+    assert_eq!(stream.head.size, 44);
+    let r = super::parse_mdhd(&mut stream, &mut super::Track::new(0));
     assert_eq!(r.is_err(), true);
 }
 
 #[test]
 fn read_mvhd_v0() {
     let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| {
         s.B32(0)
          .B32(0)
          .B32(1234)
          .B32(5678)
          .append_repeated(0, 80)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_mvhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"mvhd"));
-    assert_eq!(parsed.header.size, 108);
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+    assert_eq!(stream.head.size, 108);
+    let parsed = super::read_mvhd(&mut stream).unwrap();
     assert_eq!(parsed.timescale, 1234);
     assert_eq!(parsed.duration, 5678);
 }
 
 #[test]
 fn read_mvhd_v1() {
     let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| {
         s.B64(0)
          .B64(0)
          .B32(1234)
          .B64(5678)
          .append_repeated(0, 80)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_mvhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"mvhd"));
-    assert_eq!(parsed.header.size, 120);
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+    assert_eq!(stream.head.size, 120);
+    let parsed = super::read_mvhd(&mut stream).unwrap();
     assert_eq!(parsed.timescale, 1234);
     assert_eq!(parsed.duration, 5678);
 }
 
 #[test]
 fn read_mvhd_invalid_timescale() {
     let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| {
         s.B64(0)
          .B64(0)
          .B32(0)
          .B64(5678)
          .append_repeated(0, 80)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let r = super::parse_mvhd(&mut stream, &header);
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+    assert_eq!(stream.head.size, 120);
+    let r = super::parse_mvhd(&mut stream);
     assert_eq!(r.is_err(), true);
 }
 
 #[test]
 fn read_mvhd_unknown_duration() {
     let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| {
         s.B32(0)
          .B32(0)
          .B32(1234)
          .B32(::std::u32::MAX)
          .append_repeated(0, 80)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_mvhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"mvhd"));
-    assert_eq!(parsed.header.size, 108);
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+    assert_eq!(stream.head.size, 108);
+    let parsed = super::read_mvhd(&mut stream).unwrap();
     assert_eq!(parsed.timescale, 1234);
     assert_eq!(parsed.duration, ::std::u64::MAX);
 }
 
 #[test]
 fn read_vpcc() {
     let data_length = 12u16;
     let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 0, |s| {
         s.B8(2)
          .B8(0)
          .B8(0x82)
          .B8(0)
          .B16(data_length)
          .append_repeated(42, data_length as usize)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    assert_eq!(header.name.as_bytes(), b"vpcC");
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::VPCodecConfigurationBox);
     let r = super::read_vpcc(&mut stream);
     assert!(r.is_ok());
 }
 
 #[test]
 fn read_hdlr() {
-    let mut stream = make_fullbox(BoxSize::Short(45), b"mvhd", 0, |s| {
+    let mut stream = make_fullbox(BoxSize::Short(45), b"hdlr", 0, |s| {
         s.B32(0)
          .append_bytes(b"vide")
          .B32(0)
          .B32(0)
          .B32(0)
          .append_bytes(b"VideoHandler")
          .B8(0) // null-terminate string
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_hdlr(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"mvhd"));
-    assert_eq!(parsed.header.size, 45);
-    assert_eq!(parsed.handler_type, FourCC(*b"vide"));
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    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, 0x76696465); // vide
 }
 
 #[test]
 fn read_hdlr_short_name() {
-    let mut stream = make_fullbox(BoxSize::Short(33), b"mvhd", 0, |s| {
+    let mut stream = make_fullbox(BoxSize::Short(33), b"hdlr", 0, |s| {
         s.B32(0)
          .append_bytes(b"vide")
          .B32(0)
          .B32(0)
          .B32(0)
          .B8(0) // null-terminate string
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_hdlr(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"mvhd"));
-    assert_eq!(parsed.header.size, 33);
-    assert_eq!(parsed.handler_type, FourCC(*b"vide"));
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    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, 0x76696465); // vide
 }
 
 #[test]
 fn read_hdlr_zero_length_name() {
-    let mut stream = make_fullbox(BoxSize::Short(32), b"mvhd", 0, |s| {
+    let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| {
         s.B32(0)
          .append_bytes(b"vide")
          .B32(0)
          .B32(0)
          .B32(0)
     });
-    let header = read_box_header(&mut stream).unwrap();
-    let parsed = super::read_hdlr(&mut stream, &header).unwrap();
-    assert_eq!(parsed.header.name, FourCC(*b"mvhd"));
-    assert_eq!(parsed.header.size, 32);
-    assert_eq!(parsed.handler_type, FourCC(*b"vide"));
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    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, 0x76696465); // vide
+}
+
+#[test]
+fn read_opus() {
+    let mut stream = make_box(BoxSize::Auto, b"Opus", |s| {
+        s.append_repeated(0, 6)
+         .B16(1) // data reference index
+         .B32(0)
+         .B32(0)
+         .B16(2) // channel count
+         .B16(16) // bits per sample
+         .B16(0)
+         .B16(0)
+         .B32(48000 << 16) // Sample rate is always 48 kHz for Opus.
+         .append_bytes(&make_dops().into_inner())
+    });
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    let mut track = super::Track::new(0);
+    let r = super::read_audio_desc(&mut stream, &mut track);
+    assert!(r.is_ok());
+}
+
+fn make_dops() -> Cursor<Vec<u8>> {
+    make_box(BoxSize::Auto, b"dOps", |s| {
+        s.B8(0) // version
+         .B8(2) // channel count
+         .B16(348) // pre-skip
+         .B32(44100) // original sample rate
+         .B16(0) // gain
+         .B8(0) // channel mapping
+    })
+}
+
+#[test]
+fn read_dops() {
+    let mut stream = make_dops();
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    assert_eq!(stream.head.name, BoxType::OpusSpecificBox);
+    let r = super::read_dops(&mut stream);
+    assert!(r.is_ok());
+}
+
+#[test]
+fn serialize_opus_header() {
+    let opus = super::OpusSpecificBox {
+        version: 0,
+        output_channel_count: 1,
+        pre_skip: 342,
+        input_sample_rate: 24000,
+        output_gain: 0,
+        channel_mapping_family: 0,
+        channel_mapping_table: None,
+    };
+    let mut v = Vec::<u8>::new();
+    super::serialize_opus_header(&opus, &mut v).unwrap();
+    assert!(v.len() == 19);
+    assert!(v == vec![
+            0x4f, 0x70, 0x75, 0x73, 0x48,0x65, 0x61, 0x64,
+            0x01, 0x01, 0x56, 0x01,
+            0xc0, 0x5d, 0x00, 0x00,
+            0x00, 0x00, 0x00,
+    ]);
+    let opus = super::OpusSpecificBox {
+        version: 0,
+        output_channel_count: 6,
+        pre_skip: 152,
+        input_sample_rate: 48000,
+        output_gain: 0,
+        channel_mapping_family: 1,
+        channel_mapping_table: Some(super::ChannelMappingTable {
+            stream_count: 4,
+            coupled_count: 2,
+            channel_mapping: vec![0, 4, 1, 2, 3, 5],
+        }),
+    };
+    let mut v = Vec::<u8>::new();
+    super::serialize_opus_header(&opus, &mut v).unwrap();
+    assert!(v.len() == 27);
+    assert!(v == vec![
+            0x4f, 0x70, 0x75, 0x73, 0x48,0x65, 0x61, 0x64,
+            0x01, 0x06, 0x98, 0x00,
+            0x80, 0xbb, 0x00, 0x00,
+            0x00, 0x00, 0x01, 0x04, 0x02,
+            0x00, 0x04, 0x01, 0x02, 0x03, 0x05,
+    ]);
 }
+
+#[test]
+fn avcc_limit() {
+    let mut stream = make_box(BoxSize::Auto, b"avc1", |s| {
+        s.append_repeated(0, 6)
+         .B16(1)
+         .append_repeated(0, 16)
+         .B16(320)
+         .B16(240)
+         .append_repeated(0, 14)
+         .append_repeated(0, 32)
+         .append_repeated(0, 4)
+         .B32(0xffffffff)
+         .append_bytes(b"avcC")
+         .append_repeated(0, 100)
+    });
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    let mut track = super::Track::new(0);
+    match super::read_video_desc(&mut stream, &mut track) {
+        Err(Error::InvalidData(s)) => assert_eq!(s, "avcC box exceeds BUF_SIZE_LIMIT"),
+        Ok(_) => assert!(false, "expected an error result"),
+        _ => assert!(false, "expected a different error result"),
+    }
+}
+
+#[test]
+fn esds_limit() {
+    let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| {
+        s.append_repeated(0, 6)
+         .B16(1)
+         .B32(0)
+         .B32(0)
+         .B16(2)
+         .B16(16)
+         .B16(0)
+         .B16(0)
+         .B32(48000 << 16)
+         .B32(0xffffffff)
+         .append_bytes(b"esds")
+         .append_repeated(0, 100)
+    });
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    let mut track = super::Track::new(0);
+    match super::read_audio_desc(&mut stream, &mut track) {
+        Err(Error::InvalidData(s)) => assert_eq!(s, "esds box exceeds BUF_SIZE_LIMIT"),
+        Ok(_) => assert!(false, "expected an error result"),
+        _ => assert!(false, "expected a different error result"),
+    }
+}
+
+#[test]
+fn esds_limit_2() {
+    let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| {
+        s.append_repeated(0, 6)
+         .B16(1)
+         .B32(0)
+         .B32(0)
+         .B16(2)
+         .B16(16)
+         .B16(0)
+         .B16(0)
+         .B32(48000 << 16)
+         .B32(8)
+         .append_bytes(b"esds")
+         .append_repeated(0, 4)
+    });
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    let mut track = super::Track::new(0);
+    match super::read_audio_desc(&mut stream, &mut track) {
+        Err(Error::UnexpectedEOF) => (),
+        Ok(_) => assert!(false, "expected an error result"),
+        _ => assert!(false, "expected a different error result"),
+    }
+}
+
+#[test]
+fn read_elst_zero_entries() {
+    let mut stream = make_fullbox(BoxSize::Auto, b"elst", 0, |s| {
+        s.B32(0)
+         .B16(12)
+         .B16(34)
+    });
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    match super::read_elst(&mut stream) {
+        Err(Error::InvalidData(s)) => assert_eq!(s, "invalid edit count"),
+        Ok(_) => assert!(false, "expected an error result"),
+        _ => assert!(false, "expected a different error result"),
+    }
+}
+
+fn make_elst() -> Cursor<Vec<u8>> {
+    make_fullbox(BoxSize::Auto, b"elst", 1, |s| {
+        s.B32(1)
+        // first entry
+         .B64(1234) // duration
+         .B64(0xffffffffffffffff) // time
+         .B16(12) // rate integer
+         .B16(34) // rate fraction
+    })
+}
+
+#[test]
+fn read_edts_bogus() {
+    // First edit list entry has a media_time of -1, so we expect a second
+    // edit list entry to be present to provide a valid media_time.
+    let mut stream = make_box(BoxSize::Auto, b"edts", |s| {
+        s.append_bytes(&make_elst().into_inner())
+    });
+    let mut iter = super::BoxIter::new(&mut stream);
+    let mut stream = iter.next_box().unwrap().unwrap();
+    let mut track = super::Track::new(0);
+    match super::read_edts(&mut stream, &mut track) {
+        Err(Error::InvalidData(s)) => assert_eq!(s, "expected additional edit"),
+        Ok(_) => assert!(false, "expected an error result"),
+        _ => assert!(false, "expected a different error result"),
+    }
+}
+
+#[test]
+fn invalid_pascal_string() {
+    // String claims to be 32 bytes long (we provide 33 bytes to account for
+    // the 1 byte length prefix).
+    let pstr = "\x20xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+    let mut stream = Cursor::new(pstr);
+    // Reader wants to limit the total read length to 32 bytes, so any
+    // returned string must be no longer than 31 bytes.
+    let s = super::read_fixed_length_pascal_string(&mut stream, 32).unwrap();
+    assert_eq!(s.len(), 31);
+}