diff --git a/garnet/public/lib/fidl/rust/fidl/src/encoding.rs b/garnet/public/lib/fidl/rust/fidl/src/encoding.rs
index 6cbc732215e41dd37c197cb1378bc06a4cbbdb50..ed04812c0bc2da9bd79e7c0514a13c43a68573d9 100644
--- a/garnet/public/lib/fidl/rust/fidl/src/encoding.rs
+++ b/garnet/public/lib/fidl/rust/fidl/src/encoding.rs
@@ -136,12 +136,21 @@ pub struct Decoder<'a> {
     /// The maximum remaining number of recursive steps.
     remaining_depth: usize,
 
-    /// Buffer from which to read data.
+    /// Buffer from which to read data for the inline part of the message.
     buf: &'a [u8],
 
+    /// Original length of the buf slice. Used to report error messages. A difference between this
+    /// value and buf.len() is the current decoding position for the inline part of the message.
+    initial_buf_len: usize,
+
     /// Buffer from which to read out-of-line data.
     out_of_line_buf: &'a [u8],
 
+    /// Original length of the out_of_line_buf slice. Used to report error messages. A difference
+    /// between this value and out_of_line_buf.len() is the current decoding position for th
+    /// out-of-line part of the message.
+    initial_out_of_line_buf_len: usize,
+
     /// Buffer from which to read handles.
     handles: &'a mut [zx::Handle],
 }
@@ -186,6 +195,27 @@ impl<'a> Encoder<'a> {
         Ok(ret)
     }
 
+    /// Adds specified number of zero bytes as padding.  Effectively, just increases `offset` by
+    /// `len`.
+    pub fn padding(&mut self, len: usize) -> Result<()> {
+        if self.offset + len > self.buf.len() {
+            return Err(Error::OutOfRange);
+        }
+        self.offset += len;
+        Ok(())
+    }
+
+    /// Adds as many zero bytes as padding as necessary to make sure that the `target` object size
+    /// is equal to `inline_size()`. `start_pos` is the position in the `encoder` buffer of where
+    /// the encoding started.  See `Encoder::offset`.
+    pub fn tail_padding<Target>(&mut self, target: &mut Target, start_pos: usize) -> Result<()>
+    where
+        Target: Encodable
+    {
+        debug_assert!(start_pos <= self.offset);
+        self.padding(target.inline_size() - (self.offset - start_pos))
+    }
+
     /// Runs the provided closure inside an encoder modified
     /// to write the data out-of-line.
     ///
@@ -234,7 +264,14 @@ impl<'a> Decoder<'a> {
 
         let (buf, out_of_line_buf) = buf.split_at(out_of_line_offset);
 
-        let mut decoder = Decoder { remaining_depth: MAX_RECURSION, buf, out_of_line_buf, handles };
+        let mut decoder = Decoder {
+            remaining_depth: MAX_RECURSION,
+            buf,
+            initial_buf_len: buf.len(),
+            out_of_line_buf,
+            initial_out_of_line_buf_len: out_of_line_buf.len(),
+            handles,
+        };
         value.decode(&mut decoder)?;
         if decoder.out_of_line_buf.len() != 0 {
             return Err(Error::ExtraBytes);
@@ -279,9 +316,14 @@ impl<'a> Decoder<'a> {
         //
         // [---------------------------------]
         //     ^old--buf^      ^--buf--^^ool^
+        //
+        // We also adjust the initial_buf_len, so that the index of an invalid byte can still be
+        // comupted using the same formula: initial_buf_len - buf.len().
 
         // We split off the first `len` bytes from `out_of_line`.
         let new_buf = split_off_front(&mut self.out_of_line_buf, len)?;
+        let new_initial_buf_len =
+            self.initial_buf_len + self.initial_out_of_line_buf_len - self.out_of_line_buf.len();
         // Split off any trailing bytes up to the alignment and discard them.
         if len % 8 != 0 {
             let trailer = 8 - (len % 8);
@@ -290,7 +332,9 @@ impl<'a> Decoder<'a> {
 
         // Store the current `buf` slice and shift the `buf` slice to point at the out-of-line data.
         let old_buf = take_slice(&mut self.buf);
+        let old_initial_buf_len = self.initial_buf_len;
         self.buf = new_buf;
+        self.initial_buf_len = new_initial_buf_len;
         let res = f(self);
 
         // Set the current `buf` back to its original position.
@@ -300,6 +344,7 @@ impl<'a> Decoder<'a> {
         //     ^---buf--^               ^ool^ (slices)
         //                              ^out-of-line-advanced (index)
         self.buf = old_buf;
+        self.initial_buf_len = old_initial_buf_len;
         res
     }
 
@@ -308,6 +353,18 @@ impl<'a> Decoder<'a> {
         self.buf.is_empty()
     }
 
+    /// Current decoding position in the inline part of the message, counting from the original
+    /// first byte passed to `decode_into`.
+    pub fn inline_pos(&self) -> usize {
+        self.initial_buf_len - self.buf.len()
+    }
+
+    /// Current decoding position in the out-of-line part of the message, counting from the
+    /// original first byte passed to `decode_into`.
+    pub fn out_of_line_pos(&self) -> usize {
+        self.initial_out_of_line_buf_len - self.out_of_line_buf.len()
+    }
+
     /// The number of out-of-line bytes not yet accounted for by a `read_out_of_line`
     /// call.
     pub fn remaining_out_of_line(&self) -> usize {
@@ -333,6 +390,33 @@ impl<'a> Decoder<'a> {
     pub fn take_handle(&mut self) -> Result<zx::Handle> {
         split_off_first_mut(&mut self.handles).map(take_handle)
     }
+
+    /// A convenience method to skip over the specified number of zero bytes used for padding, also
+    /// checking that all those bytes are in fact zeroes.
+    pub fn skip_padding(&mut self, len: usize) -> Result<()> {
+        let padding_start = self.inline_pos();
+        let padding = self.next_slice(len)?;
+        for i in 0..padding.len() {
+            if padding[i] != 0 {
+                return Err(Error::NonZeroPadding {
+                    padding_start,
+                    non_zero_pos: padding_start + i,
+                });
+            }
+        }
+        Ok(())
+    }
+
+    /// Skips padding at the end of the object, by decoding as many bytes as necessary to decode
+    /// `inline_size()` bytes starting at the `start_pos`. Uses `Decoder::skip_padding`, so will
+    /// check that all the skipped bytes are indeed zeroes.
+    pub fn skip_tail_padding<Target>(&mut self, _target: &Target, start_pos: usize) -> Result<()>
+    where
+        Target: Decodable + Sized,
+    {
+        debug_assert!(start_pos <= self.inline_pos());
+        self.skip_padding(Target::inline_size() - (self.inline_pos() - start_pos))
+    }
 }
 
 /// A type which can be FIDL2-encoded into a buffer.
@@ -1404,6 +1488,7 @@ impl<T: AutonullContainer + Decodable> Decodable for Option<T> {
             *self = None;
             // Eat the full `inline_size` bytes including the
             // ALLOC_ABSENT that we only peeked at before
+            // TODO(FIDL-598) Switch to `skip_padding` when other bindings are ready.
             decoder.next_slice(inline_size)?;
             Ok(())
         }
@@ -1505,13 +1590,13 @@ macro_rules! fidl_struct {
                     let mut cur_offset = 0;
                     $(
                         // Skip to the start of the next field
-                        encoder.next_slice($member_offset - cur_offset)?;
+                        encoder.padding($member_offset - cur_offset)?;
                         cur_offset = $member_offset;
                         $crate::fidl_encode!(&mut self.$member_name, encoder)?;
                         cur_offset += $crate::fidl_inline_size!($member_ty);
                     )*
                     // Skip to the end of the struct's size
-                    encoder.next_slice($size - cur_offset)?;
+                    encoder.padding($size - cur_offset)?;
                     Ok(())
                 })
             }
@@ -1539,12 +1624,14 @@ macro_rules! fidl_struct {
                     let mut cur_offset = 0;
                     $(
                         // Skip to the start of the next field
+                        // TODO(FIDL-598) Switch to `skip_padding` when other bindings are ready.
                         decoder.next_slice($member_offset - cur_offset)?;
                         cur_offset = $member_offset;
                         $crate::fidl_decode!(&mut self.$member_name, decoder)?;
                         cur_offset += $crate::fidl_inline_size!($member_ty);
                     )*
                     // Skip to the end of the struct's size
+                    // TODO(FIDL-598) Switch to `skip_padding` when other bindings are ready.
                     decoder.next_slice($size - cur_offset)?;
                     Ok(())
                 })
@@ -1860,6 +1947,7 @@ where
     fn decode(&mut self, decoder: &mut Decoder) -> Result<()> {
         let mut tag: u32 = 0;
         fidl_decode!(&mut tag, decoder)?;
+        // TODO(FIDL-598) Switch to `skip_padding` when other bindings are ready.
         decoder.next_slice(fidl_inline_align!(Self) - 4)?;
 
         match tag {
@@ -1957,11 +2045,11 @@ macro_rules! fidl_union {
                     match self { $(
                         $name::$member_name ( val ) => {
                             // Jump to offset minus 4-byte tag
-                            encoder.next_slice($member_offset - 4)?;
+                            encoder.padding($member_offset - 4)?;
                             // Encode value
                             $crate::fidl_encode!(val, encoder)?;
                             // Skip to the end of the union's size
-                            encoder.next_slice($size - (
+                            encoder.padding($size - (
                                 $crate::fidl_inline_size!($member_ty) + $member_offset
                             ))?;
                             Ok(())
@@ -1997,6 +2085,7 @@ macro_rules! fidl_union {
                     $(
                         if index == tag {
                             // Jump to offset minus 4-byte tag
+                            // TODO(FIDL-598) Switch to `skip_padding` when other bindings are ready.
                             decoder.next_slice($member_offset - 4)?;
                             // Loop will only ever run once-- if the variant is not correct,
                             // it is fixed up.
@@ -2011,6 +2100,7 @@ macro_rules! fidl_union {
                                 *self = $name::$member_name($crate::fidl_new_empty!($member_ty));
                             }
                             // Skip to the end of the union's size
+                            // TODO(FIDL-598) Switch to `skip_padding` when other bindings are ready.
                             decoder.next_slice($size - ($crate::fidl_inline_size!($member_ty) + $member_offset))?;
                             return Ok(());
                         }
@@ -2326,13 +2416,13 @@ macro_rules! tuple_impls {
                     $(
                         // Skip to the start of the next field
                         let member_offset = round_up_to_align(cur_offset, self.$nidx.inline_align());
-                        encoder.next_slice(member_offset - cur_offset)?;
+                        encoder.padding(member_offset - cur_offset)?;
                         cur_offset = member_offset;
                         self.$nidx.encode(encoder)?;
                         cur_offset += self.$nidx.inline_size();
                     )*
                     // Skip to the end of the struct's size
-                    encoder.next_slice(self.inline_size() - cur_offset)?;
+                    encoder.padding(self.inline_size() - cur_offset)?;
                     Ok(())
                 })
             }
@@ -2382,12 +2472,14 @@ macro_rules! tuple_impls {
                     $(
                         // Skip to the start of the next field
                         let member_offset = round_up_to_align(cur_offset, $ntyp::inline_align());
+                        // TODO(FIDL-598) Switch to `skip_padding` when other bindings are ready.
                         decoder.next_slice(member_offset - cur_offset)?;
                         cur_offset = member_offset;
                         self.$nidx.decode(decoder)?;
                         cur_offset += $ntyp::inline_size();
                     )*
                     // Skip to the end of the struct's size
+                    // TODO(FIDL-598) Switch to `skip_padding` when other bindings are ready.
                     decoder.next_slice(Self::inline_size() - cur_offset)?;
                     Ok(())
                 })
diff --git a/garnet/public/lib/fidl/rust/fidl/src/error.rs b/garnet/public/lib/fidl/rust/fidl/src/error.rs
index b0b7312f5f815c8b6ef80aa01cd6b853e51f2e75..e1ce1c5ff390ea898f6ff8137e00b79d0f46725e 100644
--- a/garnet/public/lib/fidl/rust/fidl/src/error.rs
+++ b/garnet/public/lib/fidl/rust/fidl/src/error.rs
@@ -43,6 +43,20 @@ pub enum Error {
     #[fail(display = "Decoding the FIDL object did not use all of the handles provided.")]
     ExtraHandles,
 
+    /// Decoding the FIDL object observed non-zero value in a padding byte.
+    #[fail(
+        display = "Decoding the FIDL object observed non-zero value in the padding at byte {}. \
+                   Padding starts at byte {}.",
+        non_zero_pos, padding_start
+    )]
+    NonZeroPadding {
+        /// Index of the first byte of the padding, relative to the beginning of the message.
+        padding_start: usize,
+        /// Index of the byte in the padding that was non-zero, relative to the beginning of the
+        /// message.
+        non_zero_pos: usize,
+    },
+
     /// The FIDL object had too many layers of structural recursion.
     #[fail(display = "The FIDL object had too many layers of structural recursion.")]
     MaxRecursionDepth,