[recovery_netstack] Use 'packet' crate for parsing/serialization

Test: Existing unit tests

Change-Id: Id0c14b87179e53c3fd1c94bf18e714f7bfd233dc
diff --git a/bin/recovery_netstack/core/BUILD.gn b/bin/recovery_netstack/core/BUILD.gn
index 8c6a77c..7b57629 100644
--- a/bin/recovery_netstack/core/BUILD.gn
+++ b/bin/recovery_netstack/core/BUILD.gn
@@ -11,6 +11,7 @@
   with_unit_tests = true
 
   deps = [
+    "//garnet/public/rust/packet",
     "//garnet/public/rust/zerocopy",
     "//third_party/rust-crates/rustc_deps:byteorder",
     "//third_party/rust-crates/rustc_deps:failure",
diff --git a/bin/recovery_netstack/core/src/device/arp.rs b/bin/recovery_netstack/core/src/device/arp.rs
index 25bd6ad..7e8d684 100644
--- a/bin/recovery_netstack/core/src/device/arp.rs
+++ b/bin/recovery_netstack/core/src/device/arp.rs
@@ -8,13 +8,12 @@
 use std::hash::Hash;
 use std::time::Duration;
 
+use packet::{BufferMut, InnerSerializer, Serializer};
+
 use crate::device::ethernet::EthernetArpDevice;
 use crate::device::DeviceLayerTimerId;
 use crate::ip::Ipv4Addr;
-use crate::wire::{
-    arp::{ArpPacket, ArpPacketSerializer, HType, PType},
-    BufferAndRange, SerializationRequest,
-};
+use crate::wire::arp::{ArpPacket, ArpPacketBuilder, HType, PType};
 use crate::{Context, EventDispatcher, TimerId, TimerIdInner};
 use log::debug;
 
@@ -90,9 +89,9 @@
     /// Send an ARP packet in a device layer frame.
     ///
     /// `send_arp_frame` accepts a device ID, a destination hardware address,
-    /// and a `SerializationRequest`. It computes the routing information and
-    /// serializes the request in a device layer frame and sends it.
-    fn send_arp_frame<D: EventDispatcher, S: SerializationRequest>(
+    /// and a `Serializer`. It computes the routing information, serializes the
+    /// request in a device layer frame, and sends it.
+    fn send_arp_frame<D: EventDispatcher, S: Serializer>(
         ctx: &mut Context<D>, device_id: u64, dst: Self::HardwareAddr, body: S,
     );
 
@@ -135,13 +134,13 @@
     D: EventDispatcher,
     P: PType + Eq + Hash,
     AD: ArpDevice<P>,
-    B: AsRef<[u8]> + AsMut<[u8]>,
+    B: BufferMut,
 >(
     ctx: &mut Context<D>, device_id: u64, src_addr: AD::HardwareAddr, dst_addr: AD::HardwareAddr,
-    mut buffer: BufferAndRange<B>,
+    mut buffer: B,
 ) {
     // TODO(wesleyac) Add support for gratuitous ARP and probe/announce.
-    let packet = if let Ok(packet) = ArpPacket::<_, AD::HardwareAddr, P>::parse(buffer.as_mut()) {
+    let packet = if let Ok(packet) = buffer.parse::<ArpPacket<_, AD::HardwareAddr, P>>() {
         let addressed_to_me =
             Some(packet.target_protocol_address()) == AD::get_protocol_addr(ctx, device_id);
         let table = &mut AD::get_arp_state(ctx, device_id).table;
@@ -209,14 +208,16 @@
                 ctx,
                 device_id,
                 packet.sender_hardware_address(),
-                ArpPacketSerializer::new(
-                    ArpOp::Response,
-                    self_hw_addr,
-                    packet.target_protocol_address(),
-                    packet.sender_hardware_address(),
-                    packet.sender_protocol_address(),
-                )
-                .serialize_outer(),
+                InnerSerializer::new_vec(
+                    ArpPacketBuilder::new(
+                        ArpOp::Response,
+                        self_hw_addr,
+                        packet.target_protocol_address(),
+                        packet.sender_hardware_address(),
+                        packet.sender_protocol_address(),
+                    ),
+                    buffer,
+                ),
             );
         }
     } else {
@@ -256,7 +257,7 @@
             ctx,
             device_id,
             AD::BROADCAST,
-            ArpPacketSerializer::new(
+            ArpPacketBuilder::new(
                 ArpOp::Request,
                 self_hw_addr,
                 sender_protocol_addr,
@@ -265,8 +266,7 @@
                 // address we are sending the packet to.
                 AD::BROADCAST,
                 lookup_addr,
-            )
-            .serialize_outer(),
+            ),
         );
 
         // TODO(wesleyac): Configurable timeout.
@@ -356,14 +356,15 @@
 
 #[cfg(test)]
 mod tests {
+    use packet::ParseBuffer;
+
     use super::*;
     use crate::device::ethernet::{set_ip_addr, EtherType, Mac};
     use crate::ip::{Ipv4Addr, Subnet};
     use crate::testutil;
     use crate::testutil::DummyEventDispatcher;
-    use crate::wire::arp::{peek_arp_types, ArpPacketSerializer};
+    use crate::wire::arp::{peek_arp_types, ArpPacketBuilder};
     use crate::wire::ethernet::EthernetFrame;
-    use crate::wire::{BufferAndRange, InnerSerializationRequest};
     use crate::StackState;
 
     const TEST_LOCAL_IPV4: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]);
@@ -396,8 +397,9 @@
         for packet_num in 0..3 {
             assert_eq!(ctx.dispatcher.frames_sent().len(), packet_num + 1);
 
-            let (frame, _) =
-                EthernetFrame::parse(&ctx.dispatcher.frames_sent()[packet_num].1[..]).unwrap();
+            let mut buf = &ctx.dispatcher.frames_sent()[packet_num].1[..];
+
+            let frame = buf.parse::<EthernetFrame<_>>().unwrap();
             assert_eq!(frame.ethertype(), Some(Ok(EtherType::Arp)));
             assert_eq!(frame.src_mac(), TEST_LOCAL_MAC);
             assert_eq!(EthernetArpDevice::BROADCAST, frame.dst_mac());
@@ -406,7 +408,7 @@
             assert_eq!(hw, ArpHardwareType::Ethernet);
             assert_eq!(proto, EtherType::Ipv4);
 
-            let arp = ArpPacket::<_, Mac, Ipv4Addr>::parse(frame.body()).unwrap();
+            let arp = buf.parse::<ArpPacket<_, Mac, Ipv4Addr>>().unwrap();
             assert_eq!(arp.operation(), ArpOp::Request);
             assert_eq!(arp.sender_hardware_address(), TEST_LOCAL_MAC);
             assert_eq!(arp.target_hardware_address(), EthernetArpDevice::BROADCAST);
@@ -432,13 +434,13 @@
             Subnet::new(TEST_LOCAL_IPV4, 24),
         );
 
-        let mut buf = InnerSerializationRequest::new(ArpPacketSerializer::new(
+        let mut buf = ArpPacketBuilder::new(
             ArpOp::Request,
             TEST_REMOTE_MAC,
             TEST_REMOTE_IPV4,
             TEST_LOCAL_MAC,
             TEST_LOCAL_IPV4,
-        ))
+        )
         .serialize_outer();
         let (hw, proto) = peek_arp_types(buf.as_ref()).unwrap();
         assert_eq!(hw, ArpHardwareType::Ethernet);
@@ -449,7 +451,7 @@
             1,
             TEST_REMOTE_MAC,
             TEST_LOCAL_MAC,
-            BufferAndRange::new_from(&mut buf, ..),
+            buf,
         );
 
         assert_eq!(
@@ -465,7 +467,9 @@
 
         assert_eq!(ctx.dispatcher.frames_sent().len(), 1);
 
-        let (frame, _) = EthernetFrame::parse(&ctx.dispatcher.frames_sent()[0].1[..]).unwrap();
+        let mut buf = &ctx.dispatcher.frames_sent()[0].1[..];
+
+        let frame = buf.parse::<EthernetFrame<_>>().unwrap();
         assert_eq!(frame.ethertype(), Some(Ok(EtherType::Arp)));
         assert_eq!(frame.src_mac(), TEST_LOCAL_MAC);
         assert_eq!(frame.dst_mac(), TEST_REMOTE_MAC);
@@ -474,7 +478,7 @@
         assert_eq!(hw, ArpHardwareType::Ethernet);
         assert_eq!(proto, EtherType::Ipv4);
 
-        let arp = ArpPacket::<_, Mac, Ipv4Addr>::parse(frame.body()).unwrap();
+        let arp = buf.parse::<ArpPacket<_, Mac, Ipv4Addr>>().unwrap();
         assert_eq!(arp.operation(), ArpOp::Response);
         assert_eq!(arp.sender_hardware_address(), TEST_LOCAL_MAC);
         assert_eq!(arp.target_hardware_address(), TEST_REMOTE_MAC);
diff --git a/bin/recovery_netstack/core/src/device/ethernet.rs b/bin/recovery_netstack/core/src/device/ethernet.rs
index 24f702f..55cc15e 100644
--- a/bin/recovery_netstack/core/src/device/ethernet.rs
+++ b/bin/recovery_netstack/core/src/device/ethernet.rs
@@ -7,14 +7,14 @@
 use std::fmt::{self, Display, Formatter};
 
 use log::debug;
+use packet::{Buf, ParseBuffer, Serializer};
 use zerocopy::{AsBytes, FromBytes, Unaligned};
 
 use crate::device::arp::{ArpDevice, ArpHardwareType, ArpState};
 use crate::device::DeviceId;
 use crate::ip::{Ip, IpAddr, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr, Subnet};
 use crate::wire::arp::peek_arp_types;
-use crate::wire::ethernet::{EthernetFrame, EthernetFrameSerializer};
-use crate::wire::{BufferAndRange, SerializationRequest};
+use crate::wire::ethernet::{EthernetFrame, EthernetFrameBuilder};
 use crate::{Context, EventDispatcher};
 
 /// A media access control (MAC) address.
@@ -189,7 +189,7 @@
     ctx: &mut Context<D>, device_id: u64, local_addr: A, body: S,
 ) where
     A: IpAddr,
-    S: SerializationRequest,
+    S: Serializer,
 {
     specialize_ip_addr!(
         fn lookup_dst_mac<D>(ctx: &mut Context<D>, device_id: u64, local_addr: Self) -> Option<Mac>
@@ -216,7 +216,7 @@
     if let Some(dst_mac) = A::lookup_dst_mac(ctx, device_id, local_addr) {
         let src_mac = get_device_state(ctx, device_id).mac;
         let buffer = body
-            .encapsulate(EthernetFrameSerializer::new(
+            .encapsulate(EthernetFrameBuilder::new(
                 src_mac,
                 dst_mac,
                 A::Version::ETHER_TYPE,
@@ -229,7 +229,8 @@
 
 /// Receive an Ethernet frame from the network.
 pub fn receive_frame<D: EventDispatcher>(ctx: &mut Context<D>, device_id: u64, bytes: &mut [u8]) {
-    let (frame, body_range) = if let Ok(frame) = EthernetFrame::parse(&mut bytes[..]) {
+    let mut buffer = Buf::new(bytes, ..);
+    let frame = if let Ok(frame) = buffer.parse::<EthernetFrame<_>>() {
         frame
     } else {
         // TODO(joshlf): Do something else?
@@ -239,7 +240,6 @@
     if let Some(Ok(ethertype)) = frame.ethertype() {
         let (src, dst) = (frame.src_mac(), frame.dst_mac());
         let device = DeviceId::new_ethernet(device_id);
-        let buffer = BufferAndRange::new_from(bytes, body_range);
         match ethertype {
             EtherType::Arp => {
                 let types = if let Ok(types) = peek_arp_types(buffer.as_ref()) {
@@ -257,8 +257,8 @@
                     types => debug!("got ARP packet for unsupported types: {:?}", types),
                 }
             }
-            EtherType::Ipv4 => crate::ip::receive_ip_packet::<D, Ipv4>(ctx, device, buffer),
-            EtherType::Ipv6 => crate::ip::receive_ip_packet::<D, Ipv6>(ctx, device, buffer),
+            EtherType::Ipv4 => crate::ip::receive_ip_packet::<D, _, Ipv4>(ctx, device, buffer),
+            EtherType::Ipv6 => crate::ip::receive_ip_packet::<D, _, Ipv6>(ctx, device, buffer),
         }
     } else {
         // TODO(joshlf): Do something else?
@@ -310,12 +310,12 @@
     type HardwareAddr = Mac;
     const BROADCAST: Mac = Mac::BROADCAST;
 
-    fn send_arp_frame<D: EventDispatcher, S: SerializationRequest>(
+    fn send_arp_frame<D: EventDispatcher, S: Serializer>(
         ctx: &mut Context<D>, device_id: u64, dst: Self::HardwareAddr, body: S,
     ) {
         let src = get_device_state(ctx, device_id).mac;
         let buffer = body
-            .encapsulate(EthernetFrameSerializer::new(src, dst, EtherType::Arp))
+            .encapsulate(EthernetFrameBuilder::new(src, dst, EtherType::Arp))
             .serialize_outer();
         ctx.dispatcher()
             .send_frame(DeviceId::new_ethernet(device_id), buffer.as_ref());
diff --git a/bin/recovery_netstack/core/src/device/mod.rs b/bin/recovery_netstack/core/src/device/mod.rs
index 32c233d..a75738c 100644
--- a/bin/recovery_netstack/core/src/device/mod.rs
+++ b/bin/recovery_netstack/core/src/device/mod.rs
@@ -11,10 +11,10 @@
 use std::fmt::{self, Debug, Display, Formatter};
 
 use log::debug;
+use packet::Serializer;
 
 use crate::device::ethernet::{EthernetDeviceState, Mac};
 use crate::ip::{IpAddr, Ipv4Addr, Subnet};
-use crate::wire::SerializationRequest;
 use crate::{Context, EventDispatcher};
 
 /// An ID identifying a device.
@@ -134,7 +134,7 @@
     ctx: &mut Context<D>, device: DeviceId, local_addr: A, body: S,
 ) where
     A: IpAddr,
-    S: SerializationRequest,
+    S: Serializer,
 {
     match device.protocol {
         DeviceProtocol::Ethernet => self::ethernet::send_ip_frame(ctx, device.id, local_addr, body),
diff --git a/bin/recovery_netstack/core/src/ip/icmp.rs b/bin/recovery_netstack/core/src/ip/icmp.rs
index e87a73c..573b2e9 100644
--- a/bin/recovery_netstack/core/src/ip/icmp.rs
+++ b/bin/recovery_netstack/core/src/ip/icmp.rs
@@ -7,32 +7,27 @@
 use std::mem;
 
 use log::trace;
+use packet::{BufferMut, BufferSerializer, Serializer};
 
 use crate::ip::{send_ip_packet, IpAddr, IpProto, Ipv4, Ipv6};
-use crate::wire::icmp::{IcmpPacketSerializer, Icmpv4Packet, Icmpv6Packet};
-use crate::wire::{BufferAndRange, SerializationRequest};
+use crate::wire::icmp::{IcmpPacketBuilder, IcmpParseArgs, Icmpv4Packet, Icmpv6Packet};
 use crate::{Context, EventDispatcher};
 
 /// Receive an ICMP message in an IP packet.
-pub fn receive_icmp_packet<D: EventDispatcher, A: IpAddr, B: AsRef<[u8]> + AsMut<[u8]>>(
-    ctx: &mut Context<D>, src_ip: A, dst_ip: A, buffer: BufferAndRange<B>,
+pub fn receive_icmp_packet<D: EventDispatcher, A: IpAddr, B: BufferMut>(
+    ctx: &mut Context<D>, src_ip: A, dst_ip: A, buffer: B,
 ) -> bool {
     trace!("receive_icmp_packet({}, {})", src_ip, dst_ip);
 
-    // specialize_ip_addr! can't handle trait bounds with type arguments, so
-    // create AsMutU8 which is equivalent to AsMut<[u8]>, but without the type
-    // arguments. Ew.
-    trait AsU8: AsRef<[u8]> + AsMut<[u8]> {}
-    impl<A: AsRef<[u8]> + AsMut<[u8]>> AsU8 for A {}
     specialize_ip_addr!(
-        fn receive_icmp_packet<D, B>(ctx: &mut Context<D>, src_ip: Self, dst_ip: Self, buffer: BufferAndRange<B>) -> bool
+        fn receive_icmp_packet<D, B>(ctx: &mut Context<D>, src_ip: Self, dst_ip: Self, buffer: B) -> bool
         where
             D: EventDispatcher,
-            B: AsU8,
+            B: BufferMut,
         {
             Ipv4Addr => {
                 let mut buffer = buffer;
-                let (packet, body_range) = match Icmpv4Packet::parse(buffer.as_mut(), src_ip, dst_ip) {
+                let packet = match buffer.parse_with::<_, Icmpv4Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip)) {
                     Ok(packet) => packet,
                     Err(err) => return false,
                 };
@@ -43,21 +38,17 @@
                         let code = echo_request.code();
                         // drop packet so we can re-use the underlying buffer
                         mem::drop(echo_request);
-                        // slice the buffer to be only the body range
-                        buffer.slice(body_range);
 
                         increment_counter!(ctx, "receive_icmp_packet::echo_request");
 
                         // we're responding to the sender, so these are flipped
                         let (src_ip, dst_ip) = (dst_ip, src_ip);
-                        send_ip_packet(ctx, dst_ip, IpProto::Icmp, |src_ip| {
-                            buffer.encapsulate(IcmpPacketSerializer::<Ipv4, B, _>::new(
-                                src_ip,
-                                dst_ip,
-                                code,
-                                req.reply(),
-                            ))
-                        });
+                        send_ip_packet(
+                            ctx,
+                            dst_ip,
+                            IpProto::Icmp,
+                            |src_ip| BufferSerializer::new_vec(buffer).encapsulate(IcmpPacketBuilder::<Ipv4, &[u8], _>::new(src_ip, dst_ip, code, req.reply())),
+                        );
                         true
                     }
                     Icmpv4Packet::EchoReply(echo_reply) => {
@@ -73,11 +64,9 @@
             }
             Ipv6Addr => {
                 let mut buffer = buffer;
-                let (packet, body_range) = match Icmpv6Packet::parse(buffer.as_mut(), src_ip, dst_ip) {
+                let packet = match buffer.parse_with::<_, Icmpv6Packet<_>>(IcmpParseArgs::new(src_ip, dst_ip)) {
                     Ok(packet) => packet,
-                    Err(err) => {
-                        return false
-                    },
+                    Err(err) => return false,
                 };
 
                 match packet {
@@ -86,21 +75,17 @@
                         let code = echo_request.code();
                         // drop packet so we can re-use the underlying buffer
                         mem::drop(echo_request);
-                        // slice the buffer to be only the body range
-                        buffer.slice(body_range);
 
                         increment_counter!(ctx, "receive_icmp_packet::echo_request");
 
                         // we're responding to the sender, so these are flipped
                         let (src_ip, dst_ip) = (dst_ip, src_ip);
-                        send_ip_packet(ctx, dst_ip, IpProto::Icmp, |src_ip| {
-                            buffer.encapsulate(IcmpPacketSerializer::<Ipv6, B, _>::new(
-                                src_ip,
-                                dst_ip,
-                                code,
-                                req.reply(),
-                            ))
-                        });
+                        send_ip_packet(
+                            ctx,
+                            dst_ip,
+                            IpProto::Icmp,
+                            |src_ip| BufferSerializer::new_vec(buffer).encapsulate(IcmpPacketBuilder::<Ipv6, &[u8], _>::new(src_ip, dst_ip, code, req.reply())),
+                        );
                         true
                     }
                     Icmpv6Packet::EchoReply(echo_reply) => {
@@ -122,6 +107,8 @@
 
 #[cfg(test)]
 mod tests {
+    use packet::Buf;
+
     use super::*;
     use crate::ip::{Ip, Ipv4, Ipv4Addr, Ipv6, Ipv6Addr};
     use crate::testutil::DummyEventDispatcher;
@@ -135,10 +122,8 @@
         let src = <Ipv4 as Ip>::LOOPBACK_ADDRESS;
         let dst = Ipv4Addr::new([192, 168, 1, 5]);
         let mut bytes = REQUEST_IP_PACKET_BYTES.to_owned();
-        let len = bytes.len();
-        let buf = BufferAndRange::new_from(&mut bytes, 20..len);
 
-        receive_icmp_packet(&mut ctx, src, dst, buf);
+        receive_icmp_packet(&mut ctx, src, dst, Buf::new(&mut bytes[20..], ..));
         assert_eq!(
             ctx.state()
                 .test_counters
@@ -172,10 +157,8 @@
         let src = <Ipv6 as Ip>::LOOPBACK_ADDRESS;
         let dst = Ipv6Addr::new([0xfe, 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
         let mut bytes = REQUEST_IP_PACKET_BYTES.to_owned();
-        let len = bytes.len();
-        let buf = BufferAndRange::new_from(&mut bytes, 40..len);
 
-        receive_icmp_packet(&mut ctx, src, dst, buf);
+        receive_icmp_packet(&mut ctx, src, dst, Buf::new(&mut bytes[40..], ..));
         assert_eq!(
             ctx.state()
                 .test_counters
diff --git a/bin/recovery_netstack/core/src/ip/mod.rs b/bin/recovery_netstack/core/src/ip/mod.rs
index 1d1b3b6..3c36c28 100644
--- a/bin/recovery_netstack/core/src/ip/mod.rs
+++ b/bin/recovery_netstack/core/src/ip/mod.rs
@@ -15,14 +15,14 @@
 use log::{debug, trace};
 use std::fmt::Debug;
 use std::mem;
-use std::ops::Range;
+
+use packet::{BufferMut, BufferSerializer, ParsablePacket, ParseBufferMut, Serializer};
+use zerocopy::{ByteSlice, ByteSliceMut};
 
 use crate::device::DeviceId;
-use crate::error::ParseError;
 use crate::ip::forwarding::{Destination, ForwardingTable};
-use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketSerializer};
-use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketSerializer};
-use crate::wire::{BufferAndRange, SerializationRequest};
+use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketBuilder};
+use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketBuilder};
 use crate::{Context, EventDispatcher};
 
 // default IPv4 TTL or IPv6 hops
@@ -41,28 +41,29 @@
     table: ForwardingTable<I>,
 }
 
-fn dispatch_receive_ip_packet<D: EventDispatcher, I: IpAddr, B: AsRef<[u8]> + AsMut<[u8]>>(
-    ctx: &mut Context<D>, proto: IpProto, src_ip: I, dst_ip: I, mut buffer: BufferAndRange<B>,
+fn dispatch_receive_ip_packet<D: EventDispatcher, I: IpAddr, B: BufferMut>(
+    ctx: &mut Context<D>, proto: IpProto, src_ip: I, dst_ip: I, mut buffer: B,
 ) -> bool {
     increment_counter!(ctx, "dispatch_receive_ip_packet");
     match proto {
         IpProto::Icmp | IpProto::Icmpv6 => icmp::receive_icmp_packet(ctx, src_ip, dst_ip, buffer),
-        IpProto::Tcp | IpProto::Udp => crate::transport::receive_ip_packet(ctx, src_ip, dst_ip, proto, buffer),
+        IpProto::Tcp | IpProto::Udp => {
+            crate::transport::receive_ip_packet(ctx, src_ip, dst_ip, proto, buffer)
+        }
     }
 }
 
 /// Receive an IP packet from a device.
-pub fn receive_ip_packet<D: EventDispatcher, I: Ip>(
-    ctx: &mut Context<D>, device: DeviceId, mut buffer: BufferAndRange<&mut [u8]>,
+pub fn receive_ip_packet<D: EventDispatcher, B: BufferMut, I: Ip>(
+    ctx: &mut Context<D>, device: DeviceId, mut buffer: B,
 ) {
     trace!("receive_ip_packet({})", device);
-    let (mut packet, body_range) =
-        if let Ok((packet, body_range)) = <I as IpExt>::Packet::parse(buffer.as_mut()) {
-            (packet, body_range)
-        } else {
-            // TODO(joshlf): Do something with ICMP here?
-            return;
-        };
+    let mut packet = if let Ok(packet) = buffer.parse_mut::<<I as IpExt<_>>::Packet>() {
+        packet
+    } else {
+        // TODO(joshlf): Do something with ICMP here?
+        return;
+    };
     trace!("receive_ip_packet: parsed packet: {:?}", packet);
 
     if I::LOOPBACK_SUBNET.contains(packet.dst_ip()) {
@@ -84,8 +85,6 @@
             let dst_ip = packet.dst_ip();
             // drop packet so we can re-use the underlying buffer
             mem::drop(packet);
-            // slice the buffer to be only the body range
-            buffer.slice(body_range);
             dispatch_receive_ip_packet(ctx, proto, src_ip, dst_ip, buffer)
         } else {
             // TODO(joshlf): Log unrecognized protocol number
@@ -96,9 +95,18 @@
         if ttl > 1 {
             trace!("receive_ip_packet: forwarding");
             packet.set_ttl(ttl - 1);
+            let meta = packet.parse_metadata();
             // drop packet so we can re-use the underlying buffer
             mem::drop(packet);
-            crate::device::send_ip_frame(ctx, dest.device, dest.next_hop, buffer);
+            // Undo the effects of parsing so that the body of the buffer
+            // contains the entire IP packet again (not just the body).
+            buffer.undo_parse(meta);
+            crate::device::send_ip_frame(
+                ctx,
+                dest.device,
+                dest.next_hop,
+                BufferSerializer::new_vec(buffer),
+            );
             return;
         } else {
             // TTL is 0 or would become 0 after decrement; see "TTL" section,
@@ -184,7 +192,9 @@
 }
 
 /// Add a route to the forwarding table.
-pub fn add_device_route<D: EventDispatcher, A: IpAddr>(ctx: &mut Context<D>, subnet: Subnet<A>, device: DeviceId) {
+pub fn add_device_route<D: EventDispatcher, A: IpAddr>(
+    ctx: &mut Context<D>, subnet: Subnet<A>, device: DeviceId,
+) {
     specialize_ip_addr!(
         fn generic_add_route(state: &mut IpLayerState, subnet: Subnet<Self>, device: DeviceId) {
             Ipv4Addr => { state.v4.table.add_device_route(subnet, device) }
@@ -212,18 +222,27 @@
     ctx: &mut Context<D>, dst_ip: A, proto: IpProto, get_body: F,
 ) where
     A: IpAddr,
-    S: SerializationRequest,
+    S: Serializer,
     F: FnOnce(A) -> S,
 {
     trace!("send_ip_packet({}, {})", dst_ip, proto);
     increment_counter!(ctx, "send_ip_packet");
     if A::Version::LOOPBACK_SUBNET.contains(dst_ip) {
         increment_counter!(ctx, "send_ip_packet::loopback");
-        let buffer = get_body(A::Version::LOOPBACK_ADDRESS).serialize(0, 0, 0);
+        // TODO(joshlf): Currently, we serialize using the normal Serializer
+        // functionality. I wonder if, in the case of delivering to loopback, we
+        // can do something more efficient?
+        let mut buffer = get_body(A::Version::LOOPBACK_ADDRESS).serialize_outer();
         // TODO(joshlf): Respond with some kind of error if we don't have a
         // handler for that protocol? Maybe simulate what would have happened
         // (w.r.t ICMP) if this were a remote host?
-        dispatch_receive_ip_packet(ctx, proto, A::Version::LOOPBACK_ADDRESS, dst_ip, buffer);
+        dispatch_receive_ip_packet(
+            ctx,
+            proto,
+            A::Version::LOOPBACK_ADDRESS,
+            dst_ip,
+            buffer.as_buf_mut(),
+        );
     } else if let Some(dest) = lookup_route(&ctx.state().ip, dst_ip) {
         let (src_ip, _) = crate::device::get_ip_addr(ctx, dest.device)
             .expect("IP device route set for device without IP address");
@@ -237,7 +256,7 @@
             get_body(src_ip),
         );
     } else {
-        println!("No route to host");
+        debug!("No route to host");
         // TODO(joshlf): No route to host
     }
 }
@@ -256,7 +275,7 @@
     ctx: &mut Context<D>, src_ip: A, dst_ip: A, proto: IpProto, body: S,
 ) where
     A: IpAddr,
-    S: SerializationRequest,
+    S: Serializer,
 {
     // TODO(joshlf): Figure out how to compute a route with the restrictions
     // mentioned in the doc comment.
@@ -280,7 +299,7 @@
     body: S,
 ) where
     A: IpAddr,
-    S: SerializationRequest,
+    S: Serializer,
 {
     assert!(!A::Version::LOOPBACK_SUBNET.contains(src_ip));
     assert!(!A::Version::LOOPBACK_SUBNET.contains(dst_ip));
@@ -291,14 +310,14 @@
         )
         where
             D: EventDispatcher,
-            S: SerializationRequest,
+            S: Serializer,
         {
             Ipv4Addr => {
-                let body = body.encapsulate(Ipv4PacketSerializer::new(src_ip, dst_ip, ttl, proto));
+                let body = body.encapsulate(Ipv4PacketBuilder::new(src_ip, dst_ip, ttl, proto));
                 crate::device::send_ip_frame(ctx, device, next_hop, body);
             }
             Ipv6Addr => {
-                let body = body.encapsulate(Ipv6PacketSerializer::new(src_ip, dst_ip, ttl, proto));
+                let body = body.encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, ttl, proto));
                 crate::device::send_ip_frame(ctx, device, next_hop, body);
             }
         }
@@ -319,36 +338,34 @@
 //
 // This trait adds extra associated types that are useful for our implementation
 // here, but which consumers outside of the ip module do not need to see.
-trait IpExt<'a>: Ip {
-    type Packet: IpPacket<'a, Self>;
+trait IpExt<B: ByteSlice>: Ip {
+    type Packet: IpPacket<B, Self>;
 }
 
-impl<'a, I: Ip> IpExt<'a> for I {
+impl<B: ByteSlice, I: Ip> IpExt<B> for I {
     default type Packet = !;
 }
 
-impl<'a> IpExt<'a> for Ipv4 {
-    type Packet = Ipv4Packet<&'a mut [u8]>;
+impl<B: ByteSlice> IpExt<B> for Ipv4 {
+    type Packet = Ipv4Packet<B>;
 }
 
-impl<'a> IpExt<'a> for Ipv6 {
-    type Packet = Ipv6Packet<&'a mut [u8]>;
+impl<B: ByteSlice> IpExt<B> for Ipv6 {
+    type Packet = Ipv6Packet<B>;
 }
 
 // `Ipv4Packet` or `Ipv6Packet`
-trait IpPacket<'a, I: Ip>: Sized + Debug {
-    fn parse(bytes: &'a mut [u8]) -> Result<(Self, Range<usize>), ParseError>;
+trait IpPacket<B: ByteSlice, I: Ip>: Sized + Debug + ParsablePacket<B, ()> {
     fn src_ip(&self) -> I::Addr;
     fn dst_ip(&self) -> I::Addr;
     fn proto(&self) -> Result<IpProto, u8>;
     fn ttl(&self) -> u8;
-    fn set_ttl(&mut self, ttl: u8);
+    fn set_ttl(&mut self, ttl: u8)
+    where
+        B: ByteSliceMut;
 }
 
-impl<'a> IpPacket<'a, Ipv4> for Ipv4Packet<&'a mut [u8]> {
-    fn parse(bytes: &'a mut [u8]) -> Result<(Ipv4Packet<&'a mut [u8]>, Range<usize>), ParseError> {
-        Ipv4Packet::parse(bytes)
-    }
+impl<B: ByteSlice> IpPacket<B, Ipv4> for Ipv4Packet<B> {
     fn src_ip(&self) -> Ipv4Addr {
         Ipv4Packet::src_ip(self)
     }
@@ -361,15 +378,15 @@
     fn ttl(&self) -> u8 {
         Ipv4Packet::ttl(self)
     }
-    fn set_ttl(&mut self, ttl: u8) {
+    fn set_ttl(&mut self, ttl: u8)
+    where
+        B: ByteSliceMut,
+    {
         Ipv4Packet::set_ttl(self, ttl)
     }
 }
 
-impl<'a> IpPacket<'a, Ipv6> for Ipv6Packet<&'a mut [u8]> {
-    fn parse(bytes: &'a mut [u8]) -> Result<(Ipv6Packet<&'a mut [u8]>, Range<usize>), ParseError> {
-        Ipv6Packet::parse(bytes)
-    }
+impl<B: ByteSlice> IpPacket<B, Ipv6> for Ipv6Packet<B> {
     fn src_ip(&self) -> Ipv6Addr {
         Ipv6Packet::src_ip(self)
     }
@@ -382,7 +399,10 @@
     fn ttl(&self) -> u8 {
         Ipv6Packet::hop_limit(self)
     }
-    fn set_ttl(&mut self, ttl: u8) {
+    fn set_ttl(&mut self, ttl: u8)
+    where
+        B: ByteSliceMut,
+    {
         Ipv6Packet::set_hop_limit(self, ttl)
     }
 }
diff --git a/bin/recovery_netstack/core/src/lib.rs b/bin/recovery_netstack/core/src/lib.rs
index 07210f0..ff0c52c 100644
--- a/bin/recovery_netstack/core/src/lib.rs
+++ b/bin/recovery_netstack/core/src/lib.rs
@@ -21,6 +21,7 @@
 // TODO(joshlf): Remove this once all of the elements in the crate are actually
 // used.
 #![allow(unused)]
+#![deny(unused_imports)]
 
 #[macro_use]
 mod macros;
@@ -35,10 +36,7 @@
 
 pub mod types;
 
-use crate::device::{
-    ethernet::Mac, receive_frame, DeviceId, DeviceLayerEventDispatcher, DeviceLayerTimerId,
-};
-use crate::transport::udp::UdpEventDispatcher;
+use crate::device::{ethernet::Mac, DeviceId, DeviceLayerEventDispatcher, DeviceLayerTimerId};
 use crate::transport::{TransportLayerEventDispatcher, TransportLayerTimerId};
 
 use crate::device::DeviceLayerState;
diff --git a/bin/recovery_netstack/core/src/transport/mod.rs b/bin/recovery_netstack/core/src/transport/mod.rs
index 6d9b72e..8760d6f 100644
--- a/bin/recovery_netstack/core/src/transport/mod.rs
+++ b/bin/recovery_netstack/core/src/transport/mod.rs
@@ -61,9 +61,10 @@
 use std::collections::HashMap;
 use std::hash::Hash;
 
+use packet::BufferMut;
+
 use crate::ip::{IpAddr, IpProto};
 use crate::transport::udp::UdpEventDispatcher;
-use crate::wire::BufferAndRange;
 use crate::{Context, EventDispatcher};
 
 /// The state associated with the transport layer.
@@ -103,8 +104,8 @@
 /// `receive_ip_packet` receives a transport layer packet. If the given protocol
 /// is supported, the packet is delivered to that protocol, and
 /// `receive_ip_packet` returns `true`. Otherwise, it returns `false`.
-pub fn receive_ip_packet<D: EventDispatcher, A: IpAddr, B: AsMut<[u8]>>(
-    ctx: &mut Context<D>, src_ip: A, dst_ip: A, proto: IpProto, buffer: BufferAndRange<B>,
+pub fn receive_ip_packet<D: EventDispatcher, A: IpAddr, B: BufferMut>(
+    ctx: &mut Context<D>, src_ip: A, dst_ip: A, proto: IpProto, buffer: B,
 ) -> bool {
     match proto {
         IpProto::Tcp => {
diff --git a/bin/recovery_netstack/core/src/transport/tcp/mod.rs b/bin/recovery_netstack/core/src/transport/tcp/mod.rs
index f47b599..793e07a 100644
--- a/bin/recovery_netstack/core/src/transport/tcp/mod.rs
+++ b/bin/recovery_netstack/core/src/transport/tcp/mod.rs
@@ -13,9 +13,10 @@
 use std::collections::HashMap;
 use std::num::NonZeroU16;
 
+use packet::BufferMut;
+
 use crate::ip::{Ip, IpAddr, Ipv4, Ipv6};
-use crate::wire::tcp::TcpSegment;
-use crate::wire::BufferAndRange;
+use crate::wire::tcp::{TcpParseArgs, TcpSegment};
 use crate::{Context, EventDispatcher};
 
 use self::conn::Conn;
@@ -55,17 +56,18 @@
 }
 
 /// Receive a TCP segment in an IP packet.
-pub fn receive_ip_packet<D: EventDispatcher, A: IpAddr, B: AsMut<[u8]>>(
-    ctx: &mut Context<D>, src_ip: A, dst_ip: A, mut buffer: BufferAndRange<B>,
+pub fn receive_ip_packet<D: EventDispatcher, A: IpAddr, B: BufferMut>(
+    ctx: &mut Context<D>, src_ip: A, dst_ip: A, mut buffer: B,
 ) {
     println!("received tcp packet: {:x?}", buffer.as_mut());
-    let (segment, body_range) =
-        if let Ok((segment, body_range)) = TcpSegment::parse(buffer.as_mut(), src_ip, dst_ip) {
-            (segment, body_range)
-        } else {
-            // TODO(joshlf): Do something with ICMP here?
-            return;
-        };
+    let segment = if let Ok(segment) =
+        buffer.parse_with::<_, TcpSegment<_>>(TcpParseArgs::new(src_ip, dst_ip))
+    {
+        segment
+    } else {
+        // TODO(joshlf): Do something with ICMP here?
+        return;
+    };
 
     if segment.syn() {
         let _key = TwoTuple {
diff --git a/bin/recovery_netstack/core/src/transport/udp.rs b/bin/recovery_netstack/core/src/transport/udp.rs
index 4343d80..323c838 100644
--- a/bin/recovery_netstack/core/src/transport/udp.rs
+++ b/bin/recovery_netstack/core/src/transport/udp.rs
@@ -7,12 +7,12 @@
 use std::hash::Hash;
 use std::num::NonZeroU16;
 
+use packet::{BufferMut, BufferSerializer, Serializer};
 use zerocopy::ByteSlice;
 
 use crate::ip::{Ip, IpAddr, IpProto, Ipv4Addr, Ipv6Addr};
 use crate::transport::{ConnAddrMap, ListenerAddrMap};
-use crate::wire::udp::{UdpPacket, UdpPacketSerializer};
-use crate::wire::{BufferAndRange, SerializationRequest};
+use crate::wire::udp::{UdpPacket, UdpPacketBuilder, UdpParseArgs};
 use crate::{Context, EventDispatcher, StackState};
 
 /// The state associated with the UDP protocol.
@@ -128,17 +128,18 @@
 }
 
 /// Receive a UDP packet in an IP packet.
-pub fn receive_ip_packet<D: EventDispatcher, A: IpAddr, B: AsMut<[u8]>>(
-    ctx: &mut Context<D>, src_ip: A, dst_ip: A, mut buffer: BufferAndRange<B>,
+pub fn receive_ip_packet<D: EventDispatcher, A: IpAddr, B: BufferMut>(
+    ctx: &mut Context<D>, src_ip: A, dst_ip: A, mut buffer: B,
 ) {
     println!("received udp packet: {:x?}", buffer.as_mut());
-    let (packet, body_range) =
-        if let Ok((packet, body_range)) = UdpPacket::parse(buffer.as_mut(), src_ip, dst_ip) {
-            (packet, body_range)
-        } else {
-            // TODO(joshlf): Do something with ICMP here?
-            return;
-        };
+    let packet = if let Ok(packet) =
+        buffer.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(src_ip, dst_ip))
+    {
+        packet
+    } else {
+        // TODO(joshlf): Do something with ICMP here?
+        return;
+    };
 
     let (state, dispatcher) = ctx.state_and_dispatcher();
     let state = get_inner_state(state);
@@ -167,8 +168,8 @@
 /// # Panics
 ///
 /// `send_udp_conn` panics if `conn` is not associated with a connection for this IP version.
-pub fn send_udp_conn<D: EventDispatcher, I: Ip>(
-    ctx: &mut Context<D>, conn: &D::UdpConn, body: &[u8],
+pub fn send_udp_conn<D: EventDispatcher, I: Ip, B: BufferMut>(
+    ctx: &mut Context<D>, conn: &D::UdpConn, body: B,
 ) {
     let state = get_inner_state::<_, I::Addr>(ctx.state());
     let Conn {
@@ -187,7 +188,7 @@
         local_addr,
         remote_addr,
         IpProto::Udp,
-        body.encapsulate(UdpPacketSerializer::new(
+        BufferSerializer::new_vec(body).encapsulate(UdpPacketBuilder::new(
             local_addr,
             remote_addr,
             Some(local_port),
@@ -207,9 +208,9 @@
 ///
 /// `send_udp_listener` panics if `listener` is not associated with a listener
 /// for this IP version.
-pub fn send_udp_listener<D: EventDispatcher, A: IpAddr>(
+pub fn send_udp_listener<D: EventDispatcher, A: IpAddr, B: BufferMut>(
     ctx: &mut Context<D>, listener: &D::UdpListener, local_addr: A, remote_addr: A,
-    remote_port: NonZeroU16, body: &[u8],
+    remote_port: NonZeroU16, body: B,
 ) {
     if !crate::ip::is_local_addr(ctx, local_addr) {
         // TODO(joshlf): Return error.
@@ -261,7 +262,7 @@
         local_addr,
         remote_addr,
         IpProto::Udp,
-        body.encapsulate(UdpPacketSerializer::new(
+        BufferSerializer::new_vec(body).encapsulate(UdpPacketBuilder::new(
             local_addr,
             remote_addr,
             Some(local_port),
diff --git a/bin/recovery_netstack/core/src/wire/arp.rs b/bin/recovery_netstack/core/src/wire/arp.rs
index 660af61..5608ae4 100644
--- a/bin/recovery_netstack/core/src/wire/arp.rs
+++ b/bin/recovery_netstack/core/src/wire/arp.rs
@@ -11,13 +11,13 @@
 use std::mem;
 
 use byteorder::{ByteOrder, NetworkEndian};
+use packet::{BufferView, BufferViewMut, InnerPacketBuilder, ParsablePacket, ParseMetadata};
 use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified, Unaligned};
 
 use crate::device::arp::{ArpHardwareType, ArpOp};
 use crate::device::ethernet::{EtherType, Mac};
 use crate::error::ParseError;
 use crate::ip::Ipv4Addr;
-use crate::wire::util::{BufferAndRange, InnerPacketSerializer};
 
 // Header has the same memory layout (thanks to repr(C, packed)) as an ARP
 // header. Thus, we can simply reinterpret the bytes of the ARP header as a
@@ -128,7 +128,7 @@
                 Err(ParseError::NotSupported),
                 "unsupported network protocol: {}",
                 proto
-            )
+            );
         }
     };
     if header.hardware_address_len() != hlen || header.protocol_address_len() != plen {
@@ -231,34 +231,27 @@
     body: LayoutVerified<B, Body<HwAddr, ProtoAddr>>,
 }
 
-impl<B: ByteSlice, HwAddr, ProtoAddr> ArpPacket<B, HwAddr, ProtoAddr>
+impl<B: ByteSlice, HwAddr, ProtoAddr> ParsablePacket<B, ()> for ArpPacket<B, HwAddr, ProtoAddr>
 where
     HwAddr: Copy + HType + FromBytes + Unaligned,
     ProtoAddr: Copy + PType + FromBytes + Unaligned,
 {
-    /// Parse an ARP packet.
-    ///
-    /// `parse` parses `bytes` as an ARP packet and validates the header fields.
-    ///
-    /// If `bytes` are a valid ARP packet, but do not match the hardware address
-    /// and protocol address types `HwAddr` and `ProtoAddr`, `parse` will return
-    /// `Err(ParseError::NotExpected)`. If multiple hardware or protocol address
-    /// types are valid in a given context, `peek_arp_types` may be used to
-    /// peek at the header and determine what types are present so that the
-    /// correct types can then be used in a call to `parse`.
-    ///
-    /// The caller may provide more bytes than necessary. This allows the caller
-    /// to call `parse` on a payload which was itself padded to meet a minimum
-    /// length requirement (for example, for Ethernet frames). See the
-    /// `DETAILS.md` file in the repository root for more details.
-    pub fn parse(bytes: B) -> Result<ArpPacket<B, HwAddr, ProtoAddr>, ParseError> {
-        let (header, body) =
-            LayoutVerified::<B, Header>::new_unaligned_from_prefix(bytes).ok_or_else(
-                debug_err_fn!(ParseError::Format, "too few bytes for header"),
-            )?;
-        let (body, _) =
-            LayoutVerified::<B, Body<HwAddr, ProtoAddr>>::new_unaligned_from_prefix(body)
-                .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for body"))?;
+    type Error = ParseError;
+
+    fn parse_metadata(&self) -> ParseMetadata {
+        ParseMetadata::from_inner_packet(self.header.bytes().len() + self.body.bytes().len())
+    }
+
+    fn parse<BV: BufferView<B>>(mut buffer: BV, args: ()) -> Result<Self, ParseError> {
+        let header = buffer.take_obj_front::<Header>().ok_or_else(debug_err_fn!(
+            ParseError::Format,
+            "too few bytes for header"
+        ))?;
+        let body = buffer
+            .take_obj_front::<Body<HwAddr, ProtoAddr>>()
+            .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for body"))?;
+        // Consume any padding bytes added by the previous layer.
+        buffer.take_rest_front();
 
         if header.hardware_protocol() != <HwAddr as HType>::htype() as u16
             || header.network_protocol() != <ProtoAddr as PType>::ptype() as u16
@@ -287,7 +280,13 @@
 
         Ok(ArpPacket { header, body })
     }
+}
 
+impl<B: ByteSlice, HwAddr, ProtoAddr> ArpPacket<B, HwAddr, ProtoAddr>
+where
+    HwAddr: Copy + HType + FromBytes + Unaligned,
+    ProtoAddr: Copy + PType + FromBytes + Unaligned,
+{
     /// The type of ARP packet
     pub fn operation(&self) -> ArpOp {
         // This is verified in `parse`, so should be safe to unwrap
@@ -314,9 +313,9 @@
         self.body.tpa
     }
 
-    /// Construct a serializer with the same contents as this packet.
-    pub fn serializer(&self) -> ArpPacketSerializer<HwAddr, ProtoAddr> {
-        ArpPacketSerializer {
+    /// Construct a builder with the same contents as this packet.
+    pub fn builder(&self) -> ArpPacketBuilder<HwAddr, ProtoAddr> {
+        ArpPacketBuilder {
             op: self.operation(),
             sha: self.sender_hardware_address(),
             spa: self.sender_protocol_address(),
@@ -326,8 +325,8 @@
     }
 }
 
-/// A serializer for ARP packets.
-pub struct ArpPacketSerializer<HwAddr, ProtoAddr> {
+/// A builder for ARP packets.
+pub struct ArpPacketBuilder<HwAddr, ProtoAddr> {
     op: ArpOp,
     sha: HwAddr,
     spa: ProtoAddr,
@@ -335,13 +334,13 @@
     tpa: ProtoAddr,
 }
 
-impl<HwAddr, ProtoAddr> ArpPacketSerializer<HwAddr, ProtoAddr> {
-    /// Construct a new `ArpPacketSerializer`.
+impl<HwAddr, ProtoAddr> ArpPacketBuilder<HwAddr, ProtoAddr> {
+    /// Construct a new `ArpPacketBuilder`.
     pub fn new(
         operation: ArpOp, sender_hardware_addr: HwAddr, sender_protocol_addr: ProtoAddr,
         target_hardware_addr: HwAddr, target_protocol_addr: ProtoAddr,
-    ) -> ArpPacketSerializer<HwAddr, ProtoAddr> {
-        ArpPacketSerializer {
+    ) -> ArpPacketBuilder<HwAddr, ProtoAddr> {
+        ArpPacketBuilder {
             op: operation,
             sha: sender_hardware_addr,
             spa: sender_protocol_addr,
@@ -351,28 +350,27 @@
     }
 }
 
-impl<HwAddr, ProtoAddr> InnerPacketSerializer for ArpPacketSerializer<HwAddr, ProtoAddr>
+impl<HwAddr, ProtoAddr> InnerPacketBuilder for ArpPacketBuilder<HwAddr, ProtoAddr>
 where
     HwAddr: Copy + HType + FromBytes + AsBytes + Unaligned,
     ProtoAddr: Copy + PType + FromBytes + AsBytes + Unaligned,
 {
-    fn size(&self) -> usize {
+    fn bytes(&self) -> usize {
         mem::size_of::<Header>() + mem::size_of::<Body<HwAddr, ProtoAddr>>()
     }
 
-    fn serialize<B: AsRef<[u8]> + AsMut<[u8]>>(self, buffer: &mut BufferAndRange<B>) {
-        assert_eq!(buffer.range().len(), 0);
-        buffer.extend_forwards(self.size());
+    fn serialize(self, mut buffer: &mut [u8]) {
+        // implements BufferViewMut, giving us take_obj_xxx_zero methods
+        let mut buffer = &mut buffer;
 
-        // SECURITY: Use _zeroed constructors to ensure we zero memory to
-        // prevent leaking information from packets previously stored in
-        // this buffer.
-        let (mut header, rest) =
-            LayoutVerified::<_, Header>::new_unaligned_from_prefix_zeroed(buffer.as_mut())
-                .expect("not enough bytes for an ARP packet");
-        let (mut body, _) =
-            LayoutVerified::<_, Body<HwAddr, ProtoAddr>>::new_unaligned_from_prefix_zeroed(rest)
-                .expect("not enough bytes for an ARP packet");
+        // SECURITY: Use _zero constructors to ensure we zero memory to prevent
+        // leaking information from packets previously stored in this buffer.
+        let mut header = buffer
+            .take_obj_front_zero::<Header>()
+            .expect("not enough bytes for an ARP packet");
+        let mut body = buffer
+            .take_obj_front_zero::<Body<HwAddr, ProtoAddr>>()
+            .expect("not enough bytes for an ARP packet");
         header
             .set_hardware_protocol(<HwAddr as HType>::htype(), <HwAddr as HType>::hlen())
             .set_network_protocol(<ProtoAddr as PType>::ptype(), <ProtoAddr as PType>::plen())
@@ -393,10 +391,11 @@
 
 #[cfg(test)]
 mod tests {
+    use packet::{FnSerializer, ParseBuffer, Serializer};
+
     use super::*;
     use crate::ip::Ipv4Addr;
     use crate::wire::ethernet::EthernetFrame;
-    use crate::wire::util::{InnerSerializationRequest, SerializationRequest};
 
     const TEST_SENDER_IPV4: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]);
     const TEST_TARGET_IPV4: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]);
@@ -405,21 +404,22 @@
 
     #[test]
     fn test_parse_serialize_full() {
+        crate::testutil::set_logger_for_test();
         use crate::wire::testdata::*;
 
-        let (frame, _) = EthernetFrame::parse(ARP_REQUEST).unwrap();
+        let mut req = &ARP_REQUEST[..];
+        let frame = req.parse::<EthernetFrame<_>>().unwrap();
         assert_eq!(frame.ethertype(), Some(Ok(EtherType::Arp)));
 
         let (hw, proto) = peek_arp_types(frame.body()).unwrap();
         assert_eq!(hw, ArpHardwareType::Ethernet);
         assert_eq!(proto, EtherType::Ipv4);
-        let arp = ArpPacket::<_, Mac, Ipv4Addr>::parse(frame.body()).unwrap();
+        let mut body = frame.body();
+        let arp = body.parse::<ArpPacket<_, Mac, Ipv4Addr>>().unwrap();
         assert_eq!(arp.operation(), ArpOp::Request);
-        assert_eq!(frame.src_mac(), arp.sender_hardware_address()); // These will be the same
+        assert_eq!(frame.src_mac(), arp.sender_hardware_address());
 
-        let frame_bytes = InnerSerializationRequest::new(arp.serializer())
-            .encapsulate(frame.serializer())
-            .serialize_outer();
+        let frame_bytes = arp.builder().encapsulate(frame.builder()).serialize_outer();
         assert_eq!(frame_bytes.as_ref(), ARP_REQUEST);
     }
 
@@ -459,14 +459,16 @@
 
     #[test]
     fn test_parse() {
-        let mut bytes = [
+        let mut buf = &mut [
             0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 5, 6, 7, 8,
-        ];
-        (&mut bytes[..8]).copy_from_slice(&header_to_bytes(new_header()));
-        let (hw, proto) = peek_arp_types(&bytes[..]).unwrap();
+        ][..];
+        (&mut buf[..8]).copy_from_slice(&header_to_bytes(new_header()));
+        let (hw, proto) = peek_arp_types(&buf[..]).unwrap();
         assert_eq!(hw, ArpHardwareType::Ethernet);
         assert_eq!(proto, EtherType::Ipv4);
-        let packet = ArpPacket::<_, Mac, Ipv4Addr>::parse(&bytes[..]).unwrap();
+
+        let mut buf = &mut buf;
+        let packet = buf.parse::<ArpPacket<_, Mac, Ipv4Addr>>().unwrap();
         assert_eq!(packet.sender_hardware_address(), TEST_SENDER_MAC);
         assert_eq!(packet.sender_protocol_address(), TEST_SENDER_IPV4);
         assert_eq!(packet.target_hardware_address(), TEST_TARGET_MAC);
@@ -476,27 +478,22 @@
 
     #[test]
     fn test_serialize() {
-        let mut buf = [0; 28];
-        {
-            InnerPacketSerializer::serialize(
-                ArpPacketSerializer::new(
-                    ArpOp::Request,
-                    TEST_SENDER_MAC,
-                    TEST_SENDER_IPV4,
-                    TEST_TARGET_MAC,
-                    TEST_TARGET_IPV4,
-                ),
-                &mut BufferAndRange::new_from(&mut buf[..], ..0),
-            );
-        }
+        let mut buf = FnSerializer::new_vec(ArpPacketBuilder::new(
+            ArpOp::Request,
+            TEST_SENDER_MAC,
+            TEST_SENDER_IPV4,
+            TEST_TARGET_MAC,
+            TEST_TARGET_IPV4,
+        ))
+        .serialize_outer();
         assert_eq!(
-            buf,
-            [
+            AsRef::<[u8]>::as_ref(&buf),
+            &[
                 0, 1, 8, 0, 6, 4, 0, 1, 0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 5, 6, 7,
                 8,
             ]
         );
-        let packet = ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..28]).unwrap();
+        let packet = buf.parse::<ArpPacket<_, Mac, Ipv4Addr>>().unwrap();
         assert_eq!(packet.sender_hardware_address(), TEST_SENDER_MAC);
         assert_eq!(packet.sender_protocol_address(), TEST_SENDER_IPV4);
         assert_eq!(packet.target_hardware_address(), TEST_TARGET_MAC);
@@ -545,59 +542,50 @@
 
     #[test]
     fn test_parse_error() {
+        // Assert that parsing a buffer results in an error.
+        fn assert_err(mut buf: &[u8], err: ParseError) {
+            assert_eq!(buf.parse::<ArpPacket<_, Mac, Ipv4Addr>>().unwrap_err(), err);
+        }
+
+        // Assert that parsing a particular header results in an error.
+        fn assert_header_err(header: Header, err: ParseError) {
+            let mut buf = [0; 28];
+            *LayoutVerified::<_, Header>::new_unaligned_from_prefix(&mut buf[..])
+                .unwrap()
+                .0 = header;
+            assert_err(&buf[..], err);
+        }
+
         // Test that a packet which is too short is rejected.
         let buf = [0; 27];
-        assert_eq!(
-            ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(),
-            ParseError::Format
-        );
+        assert_err(&[0; 27][..], ParseError::Format);
 
         let mut buf = [0; 28];
 
         // Test that an unexpected hardware protocol type is rejected.
         let mut header = new_header();
         NetworkEndian::write_u16(&mut header.htype[..], 0);
-        (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]);
-        assert_eq!(
-            ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(),
-            ParseError::NotExpected
-        );
+        assert_header_err(header, ParseError::NotExpected);
 
         // Test that an unexpected network protocol type is rejected.
         let mut header = new_header();
         NetworkEndian::write_u16(&mut header.ptype[..], 0);
-        (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]);
-        assert_eq!(
-            ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(),
-            ParseError::NotExpected
-        );
+        assert_header_err(header, ParseError::NotExpected);
 
         // Test that an incorrect hardware address len is rejected.
         let mut header = new_header();
         header.hlen = 7;
-        (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]);
-        assert_eq!(
-            ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(),
-            ParseError::Format
-        );
+        assert_header_err(header, ParseError::Format);
 
         // Test that an incorrect protocol address len is rejected.
         let mut header = new_header();
         header.plen = 5;
-        (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]);
-        assert_eq!(
-            ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(),
-            ParseError::Format
-        );
+        assert_header_err(header, ParseError::Format);
 
         // Test that an invalid operation is rejected.
         let mut header = new_header();
         NetworkEndian::write_u16(&mut header.oper[..], 3);
-        (&mut buf[..8]).copy_from_slice(&header_to_bytes(header)[..]);
-        assert_eq!(
-            ArpPacket::<_, Mac, Ipv4Addr>::parse(&buf[..]).unwrap_err(),
-            ParseError::Format
-        );
+        assert_header_err(header, ParseError::Format);
     }
 
     #[test]
@@ -605,26 +593,26 @@
         // Test that ArpPacket::serialize properly zeroes memory before
         // serializing the packet.
         let mut buf_0 = [0; 28];
-        InnerPacketSerializer::serialize(
-            ArpPacketSerializer::new(
+        InnerPacketBuilder::serialize(
+            ArpPacketBuilder::new(
                 ArpOp::Request,
                 TEST_SENDER_MAC,
                 TEST_SENDER_IPV4,
                 TEST_TARGET_MAC,
                 TEST_TARGET_IPV4,
             ),
-            &mut BufferAndRange::new_from(&mut buf_0[..], ..0),
+            &mut buf_0[..],
         );
         let mut buf_1 = [0xFF; 28];
-        InnerPacketSerializer::serialize(
-            ArpPacketSerializer::new(
+        InnerPacketBuilder::serialize(
+            ArpPacketBuilder::new(
                 ArpOp::Request,
                 TEST_SENDER_MAC,
                 TEST_SENDER_IPV4,
                 TEST_TARGET_MAC,
                 TEST_TARGET_IPV4,
             ),
-            &mut BufferAndRange::new_from(&mut buf_1[..], ..0),
+            &mut buf_1[..],
         );
         assert_eq!(buf_0, buf_1);
     }
@@ -634,15 +622,15 @@
     fn test_serialize_panic_insufficient_packet_space() {
         // Test that a buffer which doesn't leave enough room for the packet is
         // rejected.
-        InnerPacketSerializer::serialize(
-            ArpPacketSerializer::new(
+        InnerPacketBuilder::serialize(
+            ArpPacketBuilder::new(
                 ArpOp::Request,
                 TEST_SENDER_MAC,
                 TEST_SENDER_IPV4,
                 TEST_TARGET_MAC,
                 TEST_TARGET_IPV4,
             ),
-            &mut BufferAndRange::new_from(&mut [0; 27], ..0),
+            &mut [0; 27],
         );
     }
 }
diff --git a/bin/recovery_netstack/core/src/wire/ethernet.rs b/bin/recovery_netstack/core/src/wire/ethernet.rs
index babb7bb..ef3b977 100644
--- a/bin/recovery_netstack/core/src/wire/ethernet.rs
+++ b/bin/recovery_netstack/core/src/wire/ethernet.rs
@@ -4,14 +4,14 @@
 
 //! Parsing and serialization of Ethernet frames.
 
-use std::ops::Range;
-
 use byteorder::{ByteOrder, NetworkEndian};
+use packet::{
+    BufferView, BufferViewMut, PacketBuilder, ParsablePacket, ParseMetadata, SerializeBuffer,
+};
 use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified, Unaligned};
 
 use crate::device::ethernet::{EtherType, Mac};
 use crate::error::ParseError;
-use crate::wire::util::{BufferAndRange, PacketSerializer};
 
 // HeaderPrefix has the same memory layout (thanks to repr(C, packed)) as an
 // Ethernet header prefix. Thus, we can simply reinterpret the bytes of the
@@ -57,22 +57,26 @@
     body: B,
 }
 
-impl<B: ByteSlice> EthernetFrame<B> {
-    /// Parse an Ethernet frame.
-    ///
-    /// `parse` parses `bytes` as an Ethernet frame. It is assumed that the
-    /// Frame Check Sequence (FCS) footer has already been removed. It returns
-    /// the byte range corresponding to the body within `bytes`. This can be
-    /// useful when extracting the encapsulated payload to send to another layer
-    /// of the stack.
-    pub fn parse(bytes: B) -> Result<(EthernetFrame<B>, Range<usize>), ParseError> {
+impl<B: ByteSlice> ParsablePacket<B, ()> for EthernetFrame<B> {
+    type Error = ParseError;
+
+    fn parse_metadata(&self) -> ParseMetadata {
+        let header_len = self.hdr_prefix.bytes().len()
+            + self.tag.as_ref().map(|tag| tag.bytes().len()).unwrap_or(0)
+            + self.ethertype.bytes().len();
+        ParseMetadata::from_packet(header_len, self.body.len(), 0)
+    }
+
+    fn parse<BV: BufferView<B>>(mut buffer: BV, args: ()) -> Result<Self, ParseError> {
         // See for details: https://en.wikipedia.org/wiki/Ethernet_frame#Frame_%E2%80%93_data_link_layer
 
-        let (hdr_prefix, rest) =
-            LayoutVerified::<B, HeaderPrefix>::new_unaligned_from_prefix(bytes).ok_or_else(
-                debug_err_fn!(ParseError::Format, "too few bytes for header"),
-            )?;
-        if rest.len() < 48 {
+        let hdr_prefix = buffer
+            .take_obj_front::<HeaderPrefix>()
+            .ok_or_else(debug_err_fn!(
+                ParseError::Format,
+                "too few bytes for header"
+            ))?;
+        if buffer.len() < 48 {
             // The minimum frame size (not including the Frame Check Sequence
             // (FCS) footer, which we do not handle in this code) is 60 bytes.
             // We've already consumed 12 bytes for the header prefix, so we must
@@ -86,20 +90,18 @@
         // Identifier (TPID). A TPID of TPID_8021Q implies an 802.1Q tag, a TPID
         // of TPID_8021AD implies an 802.1ad tag, and anything else implies that
         // there is no tag - it's a normal ethertype field.
-        let ethertype_or_tpid = NetworkEndian::read_u16(&rest);
+        let ethertype_or_tpid = NetworkEndian::read_u16(buffer.as_ref());
         let (tag, ethertype, body) = match ethertype_or_tpid {
-            self::TPID_8021Q | self::TPID_8021AD => {
-                let (tag, rest) =
-                    LayoutVerified::<B, [u8; 4]>::new_unaligned_from_prefix(rest).unwrap();
-                let (ethertype, body) =
-                    LayoutVerified::<B, [u8; 2]>::new_unaligned_from_prefix(rest).unwrap();
-                (Some(tag), ethertype, body)
-            }
-            _ => {
-                let (ethertype, body) =
-                    LayoutVerified::<B, [u8; 2]>::new_unaligned_from_prefix(rest).unwrap();
-                (None, ethertype, body)
-            }
+            self::TPID_8021Q | self::TPID_8021AD => (
+                Some(buffer.take_obj_front::<[u8; 4]>().unwrap()),
+                buffer.take_obj_front::<[u8; 2]>().unwrap(),
+                buffer.into_rest(),
+            ),
+            _ => (
+                None,
+                buffer.take_obj_front::<[u8; 2]>().unwrap(),
+                buffer.into_rest(),
+            ),
         };
 
         let frame = EthernetFrame {
@@ -119,14 +121,11 @@
                 et
             );
         }
-
-        let hdr_len = frame.hdr_prefix.bytes().len()
-            + frame.tag.as_ref().map(|tag| tag.bytes().len()).unwrap_or(0)
-            + frame.ethertype.bytes().len();
-        let total_len = hdr_len + frame.body.len();
-        Ok((frame, hdr_len..total_len))
+        Ok(frame)
     }
+}
 
+impl<B: ByteSlice> EthernetFrame<B> {
     /// The frame body.
     pub fn body(&self) -> &[u8] {
         &self.body
@@ -174,9 +173,9 @@
         self.header_len() + self.body.len()
     }
 
-    /// Construct a serializer with the same contents as this frame.
-    pub fn serializer(&self) -> EthernetFrameSerializer {
-        EthernetFrameSerializer {
+    /// Construct a builder with the same contents as this frame.
+    pub fn builder(&self) -> EthernetFrameBuilder {
+        EthernetFrameBuilder {
             src_mac: self.src_mac(),
             dst_mac: self.dst_mac(),
             ethertype: NetworkEndian::read_u16(&self.ethertype[..]),
@@ -184,17 +183,17 @@
     }
 }
 
-/// A serializer for Ethernet frames.
-pub struct EthernetFrameSerializer {
+/// A builder for Ethernet frames.
+pub struct EthernetFrameBuilder {
     src_mac: Mac,
     dst_mac: Mac,
     ethertype: u16,
 }
 
-impl EthernetFrameSerializer {
-    /// Construct a new `EthernetFrameSerializer`.
-    pub fn new(src_mac: Mac, dst_mac: Mac, ethertype: EtherType) -> EthernetFrameSerializer {
-        EthernetFrameSerializer {
+impl EthernetFrameBuilder {
+    /// Construct a new `EthernetFrameBuilder`.
+    pub fn new(src_mac: Mac, dst_mac: Mac, ethertype: EtherType) -> EthernetFrameBuilder {
+        EthernetFrameBuilder {
             src_mac,
             dst_mac,
             ethertype: ethertype as u16,
@@ -211,116 +210,130 @@
 const MIN_HEADER_BYTES: usize = 14;
 const MIN_BODY_BYTES: usize = 46;
 
-impl PacketSerializer for EthernetFrameSerializer {
-    fn max_header_bytes(&self) -> usize {
-        MAX_HEADER_BYTES
-    }
-
-    fn min_header_bytes(&self) -> usize {
+impl PacketBuilder for EthernetFrameBuilder {
+    fn header_len(&self) -> usize {
         MIN_HEADER_BYTES
     }
 
-    fn min_body_and_padding_bytes(&self) -> usize {
+    fn min_body_len(&self) -> usize {
         MIN_BODY_BYTES
     }
 
-    fn serialize<B: AsRef<[u8]> + AsMut<[u8]>>(self, buffer: &mut BufferAndRange<B>) {
+    fn footer_len(&self) -> usize {
+        0
+    }
+
+    fn serialize<'a>(self, mut buffer: SerializeBuffer<'a>) {
         // NOTE: EtherType values of 1500 and below are used to indicate the
         // length of the body in bytes. We don't need to validate this because
         // the EtherType enum has no variants with values in that range.
 
-        let extend_backwards = {
-            let (header, body, _) = buffer.parts_mut();
-            let mut frame = {
-                // SECURITY: Use _zeroed constructors to ensure we zero memory
-                // to prevent leaking information from packets previously stored
-                // in this buffer.
-                let (prefix, ethertype) =
-                    LayoutVerified::<_, [u8; 2]>::new_unaligned_from_suffix_zeroed(header)
-                        .expect("too few bytes for Ethernet header");
-                let (_, hdr_prefix) =
-                    LayoutVerified::<_, HeaderPrefix>::new_unaligned_from_suffix_zeroed(prefix)
-                        .expect("too few bytes for Ethernet header");
-                EthernetFrame {
-                    hdr_prefix,
-                    tag: None,
-                    ethertype,
-                    body,
-                }
-            };
+        let (mut header, mut body, _) = buffer.parts();
+        // implements BufferViewMut, giving us take_obj_xxx_zero methods
+        let mut header = &mut header;
 
-            let total_len = frame.total_frame_len();
-            if total_len < 60 {
-                panic!(
-                    "total frame size of {} bytes is below minimum frame size of 60",
-                    total_len
-                );
+        let mut frame = {
+            // SECURITY: Use _zero constructors to ensure we zero memory to
+            // prevent leaking information from packets previously stored in
+            // this buffer.
+            let hdr_prefix = header
+                .take_obj_front_zero::<HeaderPrefix>()
+                .expect("too few bytes for Ethernet header");
+            let ethertype = header
+                .take_obj_front_zero::<[u8; 2]>()
+                .expect("too few bytes for Ethernet header");
+            EthernetFrame {
+                hdr_prefix,
+                tag: None,
+                ethertype,
+                body,
             }
-
-            frame.hdr_prefix.src_mac = self.src_mac.bytes();
-            frame.hdr_prefix.dst_mac = self.dst_mac.bytes();
-            NetworkEndian::write_u16(&mut frame.ethertype[..], self.ethertype);
-
-            frame.header_len()
         };
 
-        buffer.extend_backwards(extend_backwards);
+        let total_len = frame.total_frame_len();
+        if total_len < 60 {
+            panic!(
+                "total frame size of {} bytes is below minimum frame size of 60",
+                total_len
+            );
+        }
+
+        frame.hdr_prefix.src_mac = self.src_mac.bytes();
+        frame.hdr_prefix.dst_mac = self.dst_mac.bytes();
+        NetworkEndian::write_u16(&mut frame.ethertype[..], self.ethertype);
     }
 }
 
 #[cfg(test)]
 mod tests {
+    use packet::{Buf, BufferSerializer, ParseBuffer, SerializeBuffer, Serializer};
+
     use super::*;
 
     const DEFAULT_DST_MAC: Mac = Mac::new([0, 1, 2, 3, 4, 5]);
     const DEFAULT_SRC_MAC: Mac = Mac::new([6, 7, 8, 9, 10, 11]);
 
-    // Return a test buffer with values 0..60 except for the EtherType field,
-    // which is EtherType::Arp.
-    fn new_buf() -> [u8; 60] {
-        let mut buf = [0u8; 60];
+    // Return a buffer for testing parsing with values 0..60 except for the
+    // EtherType field, which is EtherType::Arp. Also return the contents
+    // of the body.
+    fn new_parse_buf() -> ([u8; 60], [u8; 46]) {
+        let mut buf = [0; 60];
         for i in 0..60 {
             buf[i] = i as u8;
         }
         NetworkEndian::write_u16(&mut buf[12..14], EtherType::Arp as u16);
+        let mut body = [0; 46];
+        (&mut body).copy_from_slice(&buf[14..]);
+        (buf, body)
+    }
+
+    // Return a test buffer with values 0..46 to be used as a test payload for
+    // serialization.
+    fn new_serialize_buf() -> [u8; 46] {
+        let mut buf = [0; 46];
+        for i in 0..46 {
+            buf[i] = i as u8;
+        }
         buf
     }
 
     #[test]
     fn test_parse() {
-        let buf = new_buf();
-        let (frame, body_range) = EthernetFrame::parse(&buf[..]).unwrap();
-        assert_eq!(body_range, 14..60);
+        let (mut buf, body) = new_parse_buf();
+        let mut buf = &mut buf[..];
+        let frame = buf.parse::<EthernetFrame<_>>().unwrap();
         assert_eq!(frame.hdr_prefix.dst_mac, DEFAULT_DST_MAC.bytes());
         assert_eq!(frame.hdr_prefix.src_mac, DEFAULT_SRC_MAC.bytes());
         assert!(frame.tag.is_none());
         assert_eq!(frame.ethertype(), Some(Ok(EtherType::Arp)));
-        assert_eq!(frame.body, &buf[body_range]);
+        assert_eq!(frame.body(), &body[..]);
 
         // For both of the TPIDs that imply the existence of a tag, make sure
         // that the tag is present and correct (and that all of the normal
         // checks succeed).
         for tpid in [TPID_8021Q, TPID_8021AD].iter() {
-            let mut buf = new_buf();
+            let (mut buf, body) = new_parse_buf();
+            let mut buf = &mut buf[..];
 
             const TPID_OFFSET: usize = 12;
             NetworkEndian::write_u16(&mut buf[TPID_OFFSET..], *tpid);
             // write a valid EtherType
             NetworkEndian::write_u16(&mut buf[TPID_OFFSET + 4..], EtherType::Arp as u16);
 
-            let (frame, body_range) = EthernetFrame::parse(&buf[..]).unwrap();
-            assert_eq!(body_range, 18..60);
+            let frame = buf.parse::<EthernetFrame<_>>().unwrap();
             assert_eq!(frame.hdr_prefix.dst_mac, DEFAULT_DST_MAC.bytes());
             assert_eq!(frame.hdr_prefix.src_mac, DEFAULT_SRC_MAC.bytes());
             assert_eq!(frame.ethertype(), Some(Ok(EtherType::Arp)));
 
             // help out with type inference
-            let tag: &[u8; 4] = &frame.tag.unwrap();
+            let tag: &[u8; 4] = frame.tag.as_ref().unwrap();
             let got_tag = NetworkEndian::read_u32(tag);
             let want_tag =
                 (*tpid as u32) << 16 | ((TPID_OFFSET as u32 + 2) << 8) | (TPID_OFFSET as u32 + 3);
             assert_eq!(got_tag, want_tag);
-            assert_eq!(frame.body, &buf[body_range]);
+            // Offset by 4 since new_parse_buf returns a body on the assumption
+            // that there's no tag.
+            assert_eq!(frame.body(), &body[4..]);
         }
     }
 
@@ -330,32 +343,41 @@
         let mut buf = [0u8; 1014];
         // an incorrect length results in error
         NetworkEndian::write_u16(&mut buf[12..], 1001);
-        assert!(EthernetFrame::parse(&buf[..]).is_err());
+        assert!((&mut buf[..]).parse::<EthernetFrame<_>>().is_err());
 
         // a correct length results in success
         NetworkEndian::write_u16(&mut buf[12..], 1000);
-        let (frame, _) = EthernetFrame::parse(&buf[..]).unwrap();
-        // there's no EtherType available
-        assert_eq!(frame.ethertype(), None);
+        assert_eq!(
+            (&mut buf[..])
+                .parse::<EthernetFrame<_>>()
+                .unwrap()
+                .ethertype(),
+            None
+        );
 
         // an unrecognized EtherType is returned numerically
         let mut buf = [0u8; 1014];
         NetworkEndian::write_u16(&mut buf[12..], 1536);
-        let (frame, _) = EthernetFrame::parse(&buf[..]).unwrap();
-        assert_eq!(frame.ethertype(), Some(Err(1536)));
+        assert_eq!(
+            (&mut buf[..])
+                .parse::<EthernetFrame<_>>()
+                .unwrap()
+                .ethertype(),
+            Some(Err(1536))
+        );
     }
 
     #[test]
     fn test_serialize() {
-        let mut buf = new_buf();
-        {
-            let mut buffer = BufferAndRange::new_from(&mut buf[..], (MAX_HEADER_BYTES - 4)..);
-            EthernetFrameSerializer::new(DEFAULT_DST_MAC, DEFAULT_SRC_MAC, EtherType::Arp)
-                .serialize(&mut buffer);
-            assert_eq!(buffer.range(), 0..60);
-        }
+        let buf = (&new_serialize_buf()[..])
+            .encapsulate(EthernetFrameBuilder::new(
+                DEFAULT_DST_MAC,
+                DEFAULT_SRC_MAC,
+                EtherType::Arp,
+            ))
+            .serialize_outer();
         assert_eq!(
-            &buf[..MAX_HEADER_BYTES - 4],
+            &buf.as_ref()[..MAX_HEADER_BYTES - 4],
             [6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 0x08, 0x06]
         );
     }
@@ -365,35 +387,46 @@
         // Test that EthernetFrame::serialize properly zeroes memory before
         // serializing the header.
         let mut buf_0 = [0; 60];
-        EthernetFrameSerializer::new(DEFAULT_SRC_MAC, DEFAULT_DST_MAC, EtherType::Arp)
-            .serialize(&mut BufferAndRange::new_from(&mut buf_0[..], 14..));
+
+        BufferSerializer::new_vec(Buf::new(&mut buf_0[..], 14..))
+            .encapsulate(EthernetFrameBuilder::new(
+                DEFAULT_SRC_MAC,
+                DEFAULT_DST_MAC,
+                EtherType::Arp,
+            ))
+            .serialize_outer();
         let mut buf_1 = [0; 60];
         (&mut buf_1[..14]).copy_from_slice(&[0xFF; 14]);
-        EthernetFrameSerializer::new(DEFAULT_SRC_MAC, DEFAULT_DST_MAC, EtherType::Arp)
-            .serialize(&mut BufferAndRange::new_from(&mut buf_1[..], 14..));
+        BufferSerializer::new_vec(Buf::new(&mut buf_1[..], 14..))
+            .encapsulate(EthernetFrameBuilder::new(
+                DEFAULT_SRC_MAC,
+                DEFAULT_DST_MAC,
+                EtherType::Arp,
+            ))
+            .serialize_outer();
         assert_eq!(&buf_0[..], &buf_1[..]);
     }
 
     #[test]
     fn test_parse_error() {
         // 1 byte shorter than the minimum
-        let buf = [0u8; 59];
-        assert!(EthernetFrame::parse(&buf[..]).is_err());
+        let mut buf = [0u8; 59];
+        assert!((&mut buf[..]).parse::<EthernetFrame<_>>().is_err());
 
         // an ethertype of 1500 should be validated as the length of the body
         let mut buf = [0u8; 60];
         NetworkEndian::write_u16(&mut buf[12..], 1500);
-        assert!(EthernetFrame::parse(&buf[..]).is_err());
+        assert!((&mut buf[..]).parse::<EthernetFrame<_>>().is_err());
 
         // an ethertype of 1501 is illegal because it's in the range [1501, 1535]
         let mut buf = [0u8; 60];
         NetworkEndian::write_u16(&mut buf[12..], 1501);
-        assert!(EthernetFrame::parse(&buf[..]).is_err());
+        assert!((&mut buf[..]).parse::<EthernetFrame<_>>().is_err());
 
         // an ethertype of 1535 is illegal
         let mut buf = [0u8; 60];
         NetworkEndian::write_u16(&mut buf[12..], 1535);
-        assert!(EthernetFrame::parse(&buf[..]).is_err());
+        assert!((&mut buf[..]).parse::<EthernetFrame<_>>().is_err());
     }
 
     #[test]
@@ -401,14 +434,12 @@
     fn test_serialize_panic() {
         // create with a body which is below the minimum length
         let mut buf = [0u8; 60];
-        EthernetFrameSerializer::new(
+        let buffer = SerializeBuffer::new(&mut buf[..], (60 - (MIN_BODY_BYTES - 1))..);
+        EthernetFrameBuilder::new(
             Mac::new([0, 1, 2, 3, 4, 5]),
             Mac::new([6, 7, 8, 9, 10, 11]),
             EtherType::Arp,
         )
-        .serialize(&mut BufferAndRange::new_from(
-            &mut buf[..],
-            (60 - (MIN_BODY_BYTES - 1))..,
-        ));
+        .serialize(buffer);
     }
 }
diff --git a/bin/recovery_netstack/core/src/wire/icmp/common.rs b/bin/recovery_netstack/core/src/wire/icmp/common.rs
index 16fd0d4..d8336b1 100644
--- a/bin/recovery_netstack/core/src/wire/icmp/common.rs
+++ b/bin/recovery_netstack/core/src/wire/icmp/common.rs
@@ -47,7 +47,7 @@
 impl_from_bytes_as_bytes_unaligned!(IcmpEchoRequest);
 impl_from_bytes_as_bytes_unaligned!(IcmpEchoReply);
 
-/// An ICMPv4 Time Exceeded message.
+/// An ICMP Time Exceeded message.
 #[derive(Copy, Clone, Debug)]
 #[repr(C, packed)]
 pub struct IcmpTimeExceeded {
diff --git a/bin/recovery_netstack/core/src/wire/icmp/icmpv4.rs b/bin/recovery_netstack/core/src/wire/icmp/icmpv4.rs
index b1f1c7b..0c18da1 100644
--- a/bin/recovery_netstack/core/src/wire/icmp/icmpv4.rs
+++ b/bin/recovery_netstack/core/src/wire/icmp/icmpv4.rs
@@ -2,19 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-//! ICMP v4
+//! ICMPv4
 
 use std::fmt;
-use std::ops::Range;
 
 use byteorder::{ByteOrder, NetworkEndian};
+use packet::{BufferView, ParsablePacket, ParseMetadata};
 use zerocopy::ByteSlice;
 
 use crate::error::ParseError;
 use crate::ip::{Ipv4, Ipv4Addr};
 
 use super::common::{IcmpDestUnreachable, IcmpEchoReply, IcmpEchoRequest, IcmpTimeExceeded};
-use super::{peek_message_type, IcmpIpExt, IcmpPacket, IcmpUnusedCode, IdAndSeq, OriginalPacket};
+use super::{
+    peek_message_type, IcmpIpExt, IcmpPacket, IcmpParseArgs, IcmpUnusedCode, IdAndSeq,
+    OriginalPacket,
+};
 
 /// An ICMPv4 packet with a dynamic message type.
 ///
@@ -24,7 +27,7 @@
 /// knowing the message type ahead of time while still getting the benefits of a
 /// statically-typed packet struct after parsing is complete.
 #[allow(missing_docs)]
-pub enum Packet<B> {
+pub enum Icmpv4Packet<B> {
     EchoReply(IcmpPacket<Ipv4, B, IcmpEchoReply>),
     DestUnreachable(IcmpPacket<Ipv4, B, IcmpDestUnreachable>),
     Redirect(IcmpPacket<Ipv4, B, Icmpv4Redirect>),
@@ -35,9 +38,9 @@
     TimestampReply(IcmpPacket<Ipv4, B, Icmpv4TimestampReply>),
 }
 
-impl<B: ByteSlice + fmt::Debug> fmt::Debug for Packet<B> {
+impl<B: ByteSlice + fmt::Debug> fmt::Debug for Icmpv4Packet<B> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        use self::Packet::*;
+        use self::Icmpv4Packet::*;
         match self {
             DestUnreachable(ref p) => f.debug_tuple("DestUnreachable").field(p).finish(),
             EchoReply(ref p) => f.debug_tuple("EchoReply").field(p).finish(),
@@ -51,44 +54,40 @@
     }
 }
 
-create_net_enum! {
-    MessageType,
-    EchoReply: ECHO_REPLY = 0,
-    DestUnreachable: DEST_UNREACHABLE = 3,
-    Redirect: REDIRECT = 5,
-    EchoRequest: ECHO_REQUEST = 8,
-    TimeExceeded: TIME_EXCEEDED = 11,
-    ParameterProblem: PARAMETER_PROBLEM = 12,
-    TimestampRequest: TIMESTAMP_REQUEST = 13,
-    TimestampReply: TIMESTAMP_REPLY = 14,
-}
+impl<B: ByteSlice> ParsablePacket<B, IcmpParseArgs<Ipv4Addr>> for Icmpv4Packet<B> {
+    type Error = ParseError;
 
-impl<B: ByteSlice> Packet<B> {
-    /// Parse an ICMP packet.
-    ///
-    /// `parse` parses `bytes` as an ICMP packet and validates the header fields
-    /// and checksum.  It returns the byte range corresponding to the message
-    /// body within `bytes`. This can be useful when extracting the encapsulated
-    /// body to send to another layer of the stack. If the message type has no
-    /// body, then the range is meaningless and should be ignored.
-    pub fn parse(
-        bytes: B, src_ip: Ipv4Addr, dst_ip: Ipv4Addr,
-    ) -> Result<(Packet<B>, Range<usize>), ParseError> {
+    fn parse_metadata(&self) -> ParseMetadata {
+        use self::Icmpv4Packet::*;
+        match self {
+            EchoReply(p) => p.parse_metadata(),
+            DestUnreachable(p) => p.parse_metadata(),
+            Redirect(p) => p.parse_metadata(),
+            EchoRequest(p) => p.parse_metadata(),
+            TimeExceeded(p) => p.parse_metadata(),
+            ParameterProblem(p) => p.parse_metadata(),
+            TimestampRequest(p) => p.parse_metadata(),
+            TimestampReply(p) => p.parse_metadata(),
+        }
+    }
+
+    fn parse<BV: BufferView<B>>(
+        mut buffer: BV, args: IcmpParseArgs<Ipv4Addr>,
+    ) -> Result<Self, ParseError> {
         macro_rules! mtch {
-            ($bytes:expr, $src_ip:expr, $dst_ip:expr, $($variant:ident => $type:ty,)*) => {
-                match peek_message_type(&$bytes)? {
+            ($buffer:expr, $args:expr, $($variant:ident => $type:ty,)*) => {
+                match peek_message_type($buffer.as_ref())? {
                     $(MessageType::$variant => {
-                        let (packet, range) = IcmpPacket::<Ipv4, B, $type>::parse($bytes, $src_ip, $dst_ip)?;
-                        (Packet::$variant(packet), range)
+                        let packet = <IcmpPacket<Ipv4, B, $type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
+                        Icmpv4Packet::$variant(packet)
                     })*
                 }
             }
         }
 
         Ok(mtch!(
-            bytes,
-            src_ip,
-            dst_ip,
+            buffer,
+            args,
             EchoReply => IcmpEchoReply,
             DestUnreachable => IcmpDestUnreachable,
             Redirect => Icmpv4Redirect,
@@ -102,6 +101,18 @@
 }
 
 create_net_enum! {
+    MessageType,
+    EchoReply: ECHO_REPLY = 0,
+    DestUnreachable: DEST_UNREACHABLE = 3,
+    Redirect: REDIRECT = 5,
+    EchoRequest: ECHO_REQUEST = 8,
+    TimeExceeded: TIME_EXCEEDED = 11,
+    ParameterProblem: PARAMETER_PROBLEM = 12,
+    TimestampRequest: TIMESTAMP_REQUEST = 13,
+    TimestampReply: TIMESTAMP_REPLY = 14,
+}
+
+create_net_enum! {
   Icmpv4DestUnreachableCode,
   DestNetworkUnreachable: DEST_NETWORK_UNREACHABLE = 0,
   DestHostUnreachable: DEST_HOST_UNREACHABLE = 1,
@@ -274,148 +285,119 @@
 );
 
 #[cfg(test)]
-mod test {
+mod tests {
+    use packet::{ParseBuffer, Serializer};
+
     use super::*;
     use crate::wire::icmp::{IcmpMessage, MessageBody};
-    use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketSerializer};
-    use crate::wire::util::{BufferAndRange, PacketSerializer, SerializationRequest};
+    use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketBuilder};
 
     fn serialize_to_bytes<B: ByteSlice, M: IcmpMessage<Ipv4, B>>(
         src_ip: Ipv4Addr, dst_ip: Ipv4Addr, icmp: &IcmpPacket<Ipv4, B, M>,
-        serializer: Ipv4PacketSerializer,
+        builder: Ipv4PacketBuilder,
     ) -> Vec<u8> {
-        let icmp_serializer = icmp.serializer(src_ip, dst_ip);
-        let mut data = vec![0; icmp_serializer.max_header_bytes() + icmp.message_body.len()];
-        let body_offset = data.len() - icmp.message_body.len();
-        (&mut data[body_offset..]).copy_from_slice(icmp.message_body.bytes());
-        BufferAndRange::new_from(&mut data[..], body_offset..)
-            .encapsulate(icmp_serializer)
-            .encapsulate(serializer)
+        icmp.message_body
+            .bytes()
+            .encapsulate(icmp.builder(src_ip, dst_ip))
+            .encapsulate(builder)
             .serialize_outer()
             .as_ref()
             .to_vec()
     }
 
+    fn test_parse_and_serialize<
+        M: for<'a> IcmpMessage<Ipv4, &'a [u8]>,
+        F: for<'a> FnOnce(&IcmpPacket<Ipv4, &'a [u8], M>),
+    >(
+        mut req: &[u8], check: F,
+    ) {
+        let orig_req = &req[..];
+
+        let ip = req.parse::<Ipv4Packet<_>>().unwrap();
+        let mut body = ip.body();
+        let icmp = body
+            .parse_with::<_, IcmpPacket<_, _, M>>(IcmpParseArgs::new(ip.src_ip(), ip.dst_ip()))
+            .unwrap();
+        check(&icmp);
+
+        let data = serialize_to_bytes(ip.src_ip(), ip.dst_ip(), &icmp, ip.builder());
+        assert_eq!(&data[..], orig_req);
+    }
+
     #[test]
     fn test_parse_and_serialize_echo_request() {
         use crate::wire::testdata::icmp_echo::*;
-        let (ip, _) = Ipv4Packet::parse(REQUEST_IP_PACKET_BYTES).unwrap();
-        let (src_ip, dst_ip, ttl) = (ip.src_ip(), ip.dst_ip(), ip.ttl());
-        // TODO: Check range
-        let (icmp, _) =
-            IcmpPacket::<_, _, IcmpEchoRequest>::parse(ip.body(), src_ip, dst_ip).unwrap();
-        assert_eq!(icmp.original_packet().bytes(), ECHO_DATA);
-        assert_eq!(icmp.message().id_seq.id(), IDENTIFIER);
-        assert_eq!(icmp.message().id_seq.seq(), SEQUENCE_NUM);
-
-        let data = serialize_to_bytes(src_ip, dst_ip, &icmp, ip.serializer());
-        assert_eq!(&data[..], REQUEST_IP_PACKET_BYTES);
+        test_parse_and_serialize::<IcmpEchoRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
+            assert_eq!(icmp.message_body.bytes(), ECHO_DATA);
+            assert_eq!(icmp.message().id_seq.id(), IDENTIFIER);
+            assert_eq!(icmp.message().id_seq.seq(), SEQUENCE_NUM);
+        });
     }
 
     #[test]
     fn test_parse_and_serialize_echo_response() {
         use crate::wire::testdata::icmp_echo::*;
-        let (ip, _) = Ipv4Packet::parse(RESPONSE_IP_PACKET_BYTES).unwrap();
-        let (src_ip, dst_ip, ttl) = (ip.src_ip(), ip.dst_ip(), ip.ttl());
-        // TODO: Check range
-        let (icmp, _) =
-            IcmpPacket::<_, _, IcmpEchoReply>::parse(ip.body(), src_ip, dst_ip).unwrap();
-        assert_eq!(icmp.original_packet().bytes(), ECHO_DATA);
-        assert_eq!(icmp.message().id_seq.id(), IDENTIFIER);
-        assert_eq!(icmp.message().id_seq.seq(), SEQUENCE_NUM);
-
-        let data = serialize_to_bytes(src_ip, dst_ip, &icmp, ip.serializer());
-        assert_eq!(&data[..], RESPONSE_IP_PACKET_BYTES);
+        test_parse_and_serialize::<IcmpEchoReply, _>(RESPONSE_IP_PACKET_BYTES, |icmp| {
+            assert_eq!(icmp.message_body.bytes(), ECHO_DATA);
+            assert_eq!(icmp.message().id_seq.id(), IDENTIFIER);
+            assert_eq!(icmp.message().id_seq.seq(), SEQUENCE_NUM);
+        });
     }
 
     #[test]
     fn test_parse_and_serialize_timestamp_request() {
         use crate::wire::testdata::icmp_timestamp::*;
-        let (ip, _) = Ipv4Packet::parse(REQUEST_IP_PACKET_BYTES).unwrap();
-        let (src_ip, dst_ip, ttl) = (ip.src_ip(), ip.dst_ip(), ip.ttl());
-        // TODO: Check range
-        let (icmp, _) =
-            IcmpPacket::<_, _, Icmpv4TimestampRequest>::parse(ip.body(), src_ip, dst_ip).unwrap();
-        assert_eq!(
-            icmp.message().0.timestamps.origin_timestamp(),
-            ORIGIN_TIMESTAMP
-        );
-        assert_eq!(
-            icmp.message().0.timestamps.recv_timestamp(),
-            RX_TX_TIMESTAMP
-        );
-        assert_eq!(icmp.message().0.timestamps.tx_timestamp(), RX_TX_TIMESTAMP);
-        assert_eq!(icmp.message().0.id_seq.id(), IDENTIFIER);
-        assert_eq!(icmp.message().0.id_seq.seq(), SEQUENCE_NUM);
-
-        let data = serialize_to_bytes(src_ip, dst_ip, &icmp, ip.serializer());
-        assert_eq!(&data[..], REQUEST_IP_PACKET_BYTES);
+        test_parse_and_serialize::<Icmpv4TimestampRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
+            assert_eq!(
+                icmp.message().0.timestamps.origin_timestamp(),
+                ORIGIN_TIMESTAMP
+            );
+            assert_eq!(icmp.message().0.timestamps.tx_timestamp(), RX_TX_TIMESTAMP);
+            assert_eq!(icmp.message().0.id_seq.id(), IDENTIFIER);
+            assert_eq!(icmp.message().0.id_seq.seq(), SEQUENCE_NUM);
+        });
     }
 
     #[test]
     fn test_parse_and_serialize_timestamp_reply() {
         use crate::wire::testdata::icmp_timestamp::*;
-        let (ip, _) = Ipv4Packet::parse(RESPONSE_IP_PACKET_BYTES).unwrap();
-        let (src_ip, dst_ip, ttl) = (ip.src_ip(), ip.dst_ip(), ip.ttl());
-        // TODO: Check range
-        let (icmp, _) =
-            IcmpPacket::<_, _, Icmpv4TimestampReply>::parse(ip.body(), src_ip, dst_ip).unwrap();
-        assert_eq!(
-            icmp.message().0.timestamps.origin_timestamp(),
-            ORIGIN_TIMESTAMP
-        );
-        // TODO: Assert other values here?
-        // TODO: Check value of recv_timestamp and tx_timestamp
-        assert_eq!(icmp.message().0.id_seq.id(), IDENTIFIER);
-        assert_eq!(icmp.message().0.id_seq.seq(), SEQUENCE_NUM);
-
-        let data = serialize_to_bytes(src_ip, dst_ip, &icmp, ip.serializer());
-        assert_eq!(&data[..], RESPONSE_IP_PACKET_BYTES);
+        test_parse_and_serialize::<Icmpv4TimestampReply, _>(RESPONSE_IP_PACKET_BYTES, |icmp| {
+            assert_eq!(
+                icmp.message().0.timestamps.origin_timestamp(),
+                ORIGIN_TIMESTAMP
+            );
+            // TODO: Assert other values here?
+            // TODO: Check value of recv_timestamp and tx_timestamp
+            assert_eq!(icmp.message().0.id_seq.id(), IDENTIFIER);
+            assert_eq!(icmp.message().0.id_seq.seq(), SEQUENCE_NUM);
+        });
     }
 
     #[test]
     fn test_parse_and_serialize_dest_unreachable() {
         use crate::wire::testdata::icmp_dest_unreachable::*;
-        let (ip, _) = Ipv4Packet::parse(IP_PACKET_BYTES).unwrap();
-        let (src_ip, dst_ip, ttl) = (ip.src_ip(), ip.dst_ip(), ip.ttl());
-        // TODO: Check range
-        let (icmp, _) =
-            IcmpPacket::<Ipv4, _, IcmpDestUnreachable>::parse(ip.body(), src_ip, dst_ip).unwrap();
-        assert_eq!(icmp.code(), Icmpv4DestUnreachableCode::DestHostUnreachable);
-        assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
-
-        let data = serialize_to_bytes(src_ip, dst_ip, &icmp, ip.serializer());
-        assert_eq!(&data[..], IP_PACKET_BYTES);
+        test_parse_and_serialize::<IcmpDestUnreachable, _>(IP_PACKET_BYTES, |icmp| {
+            assert_eq!(icmp.code(), Icmpv4DestUnreachableCode::DestHostUnreachable);
+            assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
+        });
     }
 
     #[test]
     fn test_parse_and_serialize_redirect() {
         use crate::wire::testdata::icmp_redirect::*;
-        let (ip, _) = Ipv4Packet::parse(IP_PACKET_BYTES).unwrap();
-        let (src_ip, dst_ip, ttl) = (ip.src_ip(), ip.dst_ip(), ip.ttl());
-        // TODO: Check range
-        let (icmp, _) =
-            IcmpPacket::<_, _, Icmpv4Redirect>::parse(ip.body(), src_ip, dst_ip).unwrap();
-        assert_eq!(icmp.code(), Icmpv4RedirectCode::RedirectForHost);
-        assert_eq!(icmp.message().gateway, GATEWAY_ADDR);
-
-        let data = serialize_to_bytes(src_ip, dst_ip, &icmp, ip.serializer());
-        assert_eq!(&data[..], IP_PACKET_BYTES);
+        test_parse_and_serialize::<Icmpv4Redirect, _>(IP_PACKET_BYTES, |icmp| {
+            assert_eq!(icmp.code(), Icmpv4RedirectCode::RedirectForHost);
+            assert_eq!(icmp.message().gateway, GATEWAY_ADDR);
+        });
     }
 
     #[test]
     fn test_parse_and_serialize_time_exceeded() {
         use crate::wire::testdata::icmp_time_exceeded::*;
-        let (ip, _) = Ipv4Packet::parse(IP_PACKET_BYTES).unwrap();
-        let (src_ip, dst_ip, ttl) = (ip.src_ip(), ip.dst_ip(), ip.ttl());
-        // TODO: Check range
-        let (icmp, _) =
-            IcmpPacket::<_, _, IcmpTimeExceeded>::parse(ip.body(), src_ip, dst_ip).unwrap();
-        assert_eq!(icmp.code(), Icmpv4TimeExceededCode::TTLExpired);
-        assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
-
-        let data = serialize_to_bytes(src_ip, dst_ip, &icmp, ip.serializer());
-        assert_eq!(&data[..], IP_PACKET_BYTES);
+        test_parse_and_serialize::<IcmpTimeExceeded, _>(IP_PACKET_BYTES, |icmp| {
+            assert_eq!(icmp.code(), Icmpv4TimeExceededCode::TTLExpired);
+            assert_eq!(icmp.original_packet_body(), ORIGIN_DATA);
+        });
     }
 
 }
diff --git a/bin/recovery_netstack/core/src/wire/icmp/icmpv6.rs b/bin/recovery_netstack/core/src/wire/icmp/icmpv6.rs
index 6902bf8..0705f6e 100644
--- a/bin/recovery_netstack/core/src/wire/icmp/icmpv6.rs
+++ b/bin/recovery_netstack/core/src/wire/icmp/icmpv6.rs
@@ -5,15 +5,17 @@
 //! ICMPv6
 
 use std::fmt;
-use std::ops::Range;
 
+use packet::{BufferView, ParsablePacket, ParseMetadata};
 use zerocopy::ByteSlice;
 
 use crate::error::ParseError;
 use crate::ip::{Ipv6, Ipv6Addr};
 
 use super::common::{IcmpDestUnreachable, IcmpEchoReply, IcmpEchoRequest, IcmpTimeExceeded};
-use super::{ndp, peek_message_type, IcmpIpExt, IcmpPacket, IcmpUnusedCode, OriginalPacket};
+use super::{
+    ndp, peek_message_type, IcmpIpExt, IcmpPacket, IcmpParseArgs, IcmpUnusedCode, OriginalPacket,
+};
 
 /// An ICMPv6 packet with a dynamic message type.
 ///
@@ -23,7 +25,7 @@
 /// knowing the message type ahead of time while still getting the benefits of a
 /// statically-typed packet struct after parsing is complete.
 #[allow(missing_docs)]
-pub enum Packet<B> {
+pub enum Icmpv6Packet<B> {
     DestUnreachable(IcmpPacket<Ipv6, B, IcmpDestUnreachable>),
     PacketTooBig(IcmpPacket<Ipv6, B, Icmpv6PacketTooBig>),
     TimeExceeded(IcmpPacket<Ipv6, B, IcmpTimeExceeded>),
@@ -37,9 +39,9 @@
     Redirect(IcmpPacket<Ipv6, B, ndp::Redirect>),
 }
 
-impl<B: ByteSlice + fmt::Debug> fmt::Debug for Packet<B> {
+impl<B: ByteSlice + fmt::Debug> fmt::Debug for Icmpv6Packet<B> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        use self::Packet::*;
+        use self::Icmpv6Packet::*;
         match self {
             DestUnreachable(ref p) => f.debug_tuple("DestUnreachable").field(p).finish(),
             PacketTooBig(ref p) => f.debug_tuple("PacketTooBig").field(p).finish(),
@@ -56,32 +58,43 @@
     }
 }
 
-impl<B: ByteSlice> Packet<B> {
-    /// Parse an ICMP packet.
-    ///
-    /// `parse` parses `bytes` as an ICMP packet and validates the header fields
-    /// and checksum.  It returns the byte range corresponding to the message
-    /// body within `bytes`. This can be useful when extracting the encapsulated
-    /// body to send to another layer of the stack. If the message type has no
-    /// body, then the range is meaningless and should be ignored.
-    pub fn parse(
-        bytes: B, src_ip: Ipv6Addr, dst_ip: Ipv6Addr,
-    ) -> Result<(Packet<B>, Range<usize>), ParseError> {
+impl<B: ByteSlice> ParsablePacket<B, IcmpParseArgs<Ipv6Addr>> for Icmpv6Packet<B> {
+    type Error = ParseError;
+
+    fn parse_metadata(&self) -> ParseMetadata {
+        use self::Icmpv6Packet::*;
+        match self {
+            DestUnreachable(p) => p.parse_metadata(),
+            PacketTooBig(p) => p.parse_metadata(),
+            TimeExceeded(p) => p.parse_metadata(),
+            ParameterProblem(p) => p.parse_metadata(),
+            EchoRequest(p) => p.parse_metadata(),
+            EchoReply(p) => p.parse_metadata(),
+            RouterSolicitation(p) => p.parse_metadata(),
+            RouterAdvertisment(p) => p.parse_metadata(),
+            NeighborSolicitation(p) => p.parse_metadata(),
+            NeighborAdvertisment(p) => p.parse_metadata(),
+            Redirect(p) => p.parse_metadata(),
+        }
+    }
+
+    fn parse<BV: BufferView<B>>(
+        mut buffer: BV, args: IcmpParseArgs<Ipv6Addr>,
+    ) -> Result<Self, ParseError> {
         macro_rules! mtch {
-            ($bytes:expr, $src_ip:expr, $dst_ip:expr, $($variant:ident => $type:ty,)*) => {
-                match peek_message_type(&$bytes)? {
+            ($buffer:expr, $args:expr, $($variant:ident => $type:ty,)*) => {
+                match peek_message_type($buffer.as_ref())? {
                     $(MessageType::$variant => {
-                        let (packet, range) = IcmpPacket::<Ipv6, B, $type>::parse($bytes, $src_ip, $dst_ip)?;
-                        (Packet::$variant(packet), range)
+                        let packet = <IcmpPacket<Ipv6, B, $type> as ParsablePacket<_, _>>::parse($buffer, $args)?;
+                        Icmpv6Packet::$variant(packet)
                     })*
                 }
             }
         }
 
         Ok(mtch!(
-            bytes,
-            src_ip,
-            dst_ip,
+            buffer,
+            args,
             DestUnreachable => IcmpDestUnreachable,
             PacketTooBig => Icmpv6PacketTooBig,
             TimeExceeded => IcmpTimeExceeded,
@@ -204,41 +217,51 @@
 
 #[cfg(test)]
 mod tests {
+    use packet::{ParseBuffer, Serializer};
+
     use super::*;
     use crate::wire::icmp::{IcmpMessage, IcmpPacket, MessageBody};
-    use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketSerializer};
-    use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketSerializer};
-    use crate::wire::util::{BufferAndRange, PacketSerializer, SerializationRequest};
+    use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketBuilder};
 
     fn serialize_to_bytes<B: ByteSlice, M: IcmpMessage<Ipv6, B>>(
         src_ip: Ipv6Addr, dst_ip: Ipv6Addr, icmp: &IcmpPacket<Ipv6, B, M>,
-        serializer: Ipv6PacketSerializer,
+        builder: Ipv6PacketBuilder,
     ) -> Vec<u8> {
-        let icmp_serializer = icmp.serializer(src_ip, dst_ip);
-        let mut data = vec![0; icmp_serializer.max_header_bytes() + icmp.message_body.len()];
-        let body_offset = data.len() - icmp.message_body.len();
-        (&mut data[body_offset..]).copy_from_slice(icmp.message_body.bytes());
-        BufferAndRange::new_from(&mut data[..], body_offset..)
-            .encapsulate(icmp_serializer)
-            .encapsulate(serializer)
+        icmp.message_body
+            .bytes()
+            .encapsulate(icmp.builder(src_ip, dst_ip))
+            .encapsulate(builder)
             .serialize_outer()
             .as_ref()
             .to_vec()
     }
 
-    #[test]
-    fn test_parse_and_serialize_echo_request_ipv6() {
-        use crate::wire::testdata::icmp_echo_v6::*;
-        let (ip, _) = Ipv6Packet::parse(REQUEST_IP_PACKET_BYTES).unwrap();
-        let (src_ip, dst_ip, hop_limit) = (ip.src_ip(), ip.dst_ip(), ip.hop_limit());
-        // TODO: Check range
-        let (icmp, _) =
-            IcmpPacket::<_, _, IcmpEchoRequest>::parse(ip.body(), src_ip, dst_ip).unwrap();
-        assert_eq!(icmp.original_packet().bytes(), ECHO_DATA);
-        assert_eq!(icmp.message().id_seq.id(), IDENTIFIER);
-        assert_eq!(icmp.message().id_seq.seq(), SEQUENCE_NUM);
+    fn test_parse_and_serialize<
+        M: for<'a> IcmpMessage<Ipv6, &'a [u8]>,
+        F: for<'a> FnOnce(&IcmpPacket<Ipv6, &'a [u8], M>),
+    >(
+        mut req: &[u8], check: F,
+    ) {
+        let orig_req = &req[..];
 
-        let data = serialize_to_bytes(src_ip, dst_ip, &icmp, ip.serializer());
-        assert_eq!(&data[..], REQUEST_IP_PACKET_BYTES);
+        let ip = req.parse::<Ipv6Packet<_>>().unwrap();
+        let mut body = ip.body();
+        let icmp = body
+            .parse_with::<_, IcmpPacket<_, _, M>>(IcmpParseArgs::new(ip.src_ip(), ip.dst_ip()))
+            .unwrap();
+        check(&icmp);
+
+        let data = serialize_to_bytes(ip.src_ip(), ip.dst_ip(), &icmp, ip.builder());
+        assert_eq!(&data[..], orig_req);
+    }
+
+    #[test]
+    fn test_parse_and_serialize_echo_request() {
+        use crate::wire::testdata::icmp_echo_v6::*;
+        test_parse_and_serialize::<IcmpEchoRequest, _>(REQUEST_IP_PACKET_BYTES, |icmp| {
+            assert_eq!(icmp.message_body.bytes(), ECHO_DATA);
+            assert_eq!(icmp.message().id_seq.id(), IDENTIFIER);
+            assert_eq!(icmp.message().id_seq.seq(), SEQUENCE_NUM);
+        });
     }
 }
diff --git a/bin/recovery_netstack/core/src/wire/icmp/mod.rs b/bin/recovery_netstack/core/src/wire/icmp/mod.rs
index 7ef2ea2..79d61ae 100644
--- a/bin/recovery_netstack/core/src/wire/icmp/mod.rs
+++ b/bin/recovery_netstack/core/src/wire/icmp/mod.rs
@@ -14,24 +14,25 @@
 #[cfg(test)]
 mod testdata;
 
-pub use self::icmpv4::Packet as Icmpv4Packet;
-pub use self::icmpv6::Packet as Icmpv6Packet;
+pub use self::common::*;
+pub use self::icmpv4::*;
+pub use self::icmpv6::*;
 
 use std::cmp;
 use std::convert::TryFrom;
 use std::fmt;
 use std::marker::PhantomData;
 use std::mem;
-use std::ops::{Deref, Range};
+use std::ops::Deref;
 
 use byteorder::{ByteOrder, NetworkEndian};
+use packet::{BufferView, PacketBuilder, ParsablePacket, ParseMetadata, SerializeBuffer};
 use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified, Unaligned};
 
 use crate::error::ParseError;
 use crate::ip::{Ip, IpAddr, IpProto, Ipv4, Ipv6};
 use crate::wire::ipv4;
-use crate::wire::util::fits_in_u32;
-use crate::wire::util::{BufferAndRange, Checksum, OptionImpl, Options, PacketSerializer};
+use crate::wire::util::{fits_in_u32, Checksum, OptionImpl, Options};
 
 // Header has the same memory layout (thanks to repr(C, packed)) as an ICMP
 // header. Thus, we can simply reinterpret the bytes of the ICMP header as a
@@ -166,6 +167,11 @@
     }
 }
 
+// TODO(joshlf): Once we have generic associated types, refactor this so that we
+// don't have to bind B ahead of time. Removing that requirement would make some
+// APIs (in particular, IcmpPacketBuilder) simpler by removing the B parameter
+// from them as well.
+
 /// `MessageBody` represents the parsed body of the ICMP packet.
 ///
 /// - For messages that expect no body, the `MessageBody` is of type `()`.
@@ -293,7 +299,7 @@
     /// Parse a `Code` from the 8-bit "code" field in the ICMP header. Not all
     /// values for this field are valid. If an invalid value is passed,
     /// `code_from_u8` returns `None`.
-    fn code_from_u8(_: u8) -> Option<Self::Code>;
+    fn code_from_u8(code: u8) -> Option<Self::Code>;
 }
 
 /// An ICMP packet.
@@ -323,32 +329,44 @@
     }
 }
 
-impl<I: IcmpIpExt, B: ByteSlice, M: IcmpMessage<I, B>> IcmpPacket<I, B, M> {
-    /// Parse an ICMP packet.
-    ///
-    /// `parse` parses `bytes` as an ICMP packet and validates the header fields
-    /// and checksum.  It returns the byte range corresponding to the message
-    /// body within `bytes`. This can be useful when extracting the encapsulated
-    /// body to send to another layer of the stack. If the message type has no
-    /// body, then the range is meaningless and should be ignored.
-    ///
-    /// If `bytes` are a valid ICMP packet, but do not match the message type
-    /// `M`, `parse` will return `Err(ParseError::NotExpected)`. If multiple
-    /// message types are valid in a given context, `peek_message_types` may be
-    /// used to peek at the header and determine what type is present so that
-    /// the correct type can then be used in a call to `parse`.
-    pub fn parse(
-        bytes: B, src_ip: I::Addr, dst_ip: I::Addr,
-    ) -> Result<(IcmpPacket<I, B, M>, Range<usize>), ParseError> {
-        let (header, rest) =
-            LayoutVerified::<B, Header>::new_unaligned_from_prefix(bytes).ok_or_else(
-                debug_err_fn!(ParseError::Format, "too few bytes for header"),
-            )?;
-        let (message, message_body) = LayoutVerified::<B, M>::new_unaligned_from_prefix(rest)
-            .ok_or_else(debug_err_fn!(
-                ParseError::Format,
-                "too few bytes for packet"
-            ))?;
+/// Arguments required to parse an ICMP packet.
+pub struct IcmpParseArgs<A: IpAddr> {
+    src_ip: A,
+    dst_ip: A,
+}
+
+impl<A: IpAddr> IcmpParseArgs<A> {
+    /// Construct a new `IcmpParseArgs`.
+    pub fn new(src_ip: A, dst_ip: A) -> IcmpParseArgs<A> {
+        IcmpParseArgs { src_ip, dst_ip }
+    }
+}
+
+impl<B: ByteSlice, I: IcmpIpExt, M: IcmpMessage<I, B>> ParsablePacket<B, IcmpParseArgs<I::Addr>>
+    for IcmpPacket<I, B, M>
+{
+    type Error = ParseError;
+
+    fn parse_metadata(&self) -> ParseMetadata {
+        let header_len = self.header.bytes().len() + self.message.bytes().len();
+        ParseMetadata::from_packet(header_len, self.message_body.len(), 0)
+    }
+
+    fn parse<BV: BufferView<B>>(
+        mut buffer: BV, args: IcmpParseArgs<I::Addr>,
+    ) -> Result<Self, ParseError> {
+        let header = buffer.take_obj_front::<Header>().ok_or_else(debug_err_fn!(
+            ParseError::Format,
+            "too few bytes for header"
+        ))?;
+        let message = buffer.take_obj_front::<M>().ok_or_else(debug_err_fn!(
+            ParseError::Format,
+            "too few bytes for packet"
+        ))?;
+        let message_body = buffer.into_rest();
+        if !M::Body::EXPECTS_BODY && !message_body.is_empty() {
+            return debug_err!(Err(ParseError::Format), "unexpected message body");
+        }
 
         if header.msg_type != M::TYPE.into() {
             return debug_err!(Err(ParseError::NotExpected), "unexpected message type");
@@ -359,25 +377,28 @@
             header.code
         ))?;
         if header.checksum()
-            != Self::compute_checksum(&header, message.bytes(), &message_body, src_ip, dst_ip)
-                .ok_or_else(debug_err_fn!(ParseError::Format, "packet too large"))?
+            != Self::compute_checksum(
+                &header,
+                message.bytes(),
+                &message_body,
+                args.src_ip,
+                args.dst_ip,
+            )
+            .ok_or_else(debug_err_fn!(ParseError::Format, "packet too large"))?
         {
             return debug_err!(Err(ParseError::Checksum), "invalid checksum");
         }
-
         let message_body = M::Body::parse(message_body)?;
-
-        let pre_body_len = header.bytes().len() + message.bytes().len();
-        let total_len = pre_body_len + message_body.len();
-        let packet = IcmpPacket {
+        Ok(IcmpPacket {
             header,
             message,
             message_body,
             _marker: PhantomData,
-        };
-        Ok((packet, pre_body_len..total_len))
+        })
     }
+}
 
+impl<I: IcmpIpExt, B: ByteSlice, M: IcmpMessage<I, B>> IcmpPacket<I, B, M> {
     /// Get the ICMP message.
     pub fn message(&self) -> &M {
         &self.message
@@ -392,9 +413,9 @@
         M::code_from_u8(self.header.code).unwrap()
     }
 
-    /// Construct a serializer with the same contents as this packet.
-    pub fn serializer(&self, src_ip: I::Addr, dst_ip: I::Addr) -> IcmpPacketSerializer<I, B, M> {
-        IcmpPacketSerializer {
+    /// Construct a builder with the same contents as this packet.
+    pub fn builder(&self, src_ip: I::Addr, dst_ip: I::Addr) -> IcmpPacketBuilder<I, B, M> {
+        IcmpPacketBuilder {
             src_ip,
             dst_ip,
             code: self.code(),
@@ -459,20 +480,20 @@
     }
 }
 
-/// A serializer for ICMP packets.
-pub struct IcmpPacketSerializer<I: IcmpIpExt, B, M: IcmpMessage<I, B>> {
+/// A builder for ICMP packets.
+pub struct IcmpPacketBuilder<I: IcmpIpExt, B, M: IcmpMessage<I, B>> {
     src_ip: I::Addr,
     dst_ip: I::Addr,
     code: M::Code,
     msg: M,
 }
 
-impl<I: IcmpIpExt, B, M: IcmpMessage<I, B>> IcmpPacketSerializer<I, B, M> {
-    /// Construct a new `IcmpPacketSerializer`.
+impl<I: IcmpIpExt, B, M: IcmpMessage<I, B>> IcmpPacketBuilder<I, B, M> {
+    /// Construct a new `IcmpPacketBuilder`.
     pub fn new(
         src_ip: I::Addr, dst_ip: I::Addr, code: M::Code, msg: M,
-    ) -> IcmpPacketSerializer<I, B, M> {
-        IcmpPacketSerializer {
+    ) -> IcmpPacketBuilder<I, B, M> {
+        IcmpPacketBuilder {
             src_ip,
             dst_ip,
             code,
@@ -482,54 +503,57 @@
 }
 
 // TODO(joshlf): Figure out a way to split body and non-body message types by
-// trait and implement PacketSerializer for some and InnerPacketSerializer for
-// others.
+// trait and implement PacketBuilder for some and InnerPacketBuilder for others.
 
-impl<I: IcmpIpExt, B, M: IcmpMessage<I, B>> PacketSerializer for IcmpPacketSerializer<I, B, M> {
-    fn max_header_bytes(&self) -> usize {
+impl<I: IcmpIpExt, B, M: IcmpMessage<I, B>> PacketBuilder for IcmpPacketBuilder<I, B, M> {
+    fn header_len(&self) -> usize {
         mem::size_of::<Header>() + mem::size_of::<M>()
     }
 
-    fn min_header_bytes(&self) -> usize {
-        self.max_header_bytes()
+    fn min_body_len(&self) -> usize {
+        0
     }
 
-    fn serialize<B2: AsRef<[u8]> + AsMut<[u8]>>(self, buffer: &mut BufferAndRange<B2>) {
-        let extend_backwards = {
-            let (prefix, message_body, _) = buffer.parts_mut();
-            assert!(
-                M::Body::EXPECTS_BODY || message_body.len() == 0,
-                "body provided for message that doesn't take a body"
-            );
-            // SECURITY: Use _zeroed constructors to ensure we zero memory to prevent
-            // leaking information from packets previously stored in this buffer.
-            let (prefix, mut message) =
-                LayoutVerified::<_, M>::new_unaligned_from_suffix_zeroed(prefix)
-                    .expect("too few bytes for ICMP message");
-            let (_, mut header) =
-                LayoutVerified::<_, Header>::new_unaligned_from_suffix_zeroed(prefix)
-                    .expect("too few bytes for ICMP message");
-            *message = self.msg;
-            header.set_msg_type(M::TYPE);
-            header.code = self.code.into();
-            let checksum = IcmpPacket::<I, B, M>::compute_checksum(
-                &header,
-                message.bytes(),
-                message_body,
-                self.src_ip,
-                self.dst_ip,
-            )
-            .unwrap_or_else(|| {
-                panic!(
-                    "total ICMP packet length of {} overflows 32-bit length field of pseudo-header",
-                    header.bytes().len() + message.bytes().len() + message_body.len(),
-                )
-            });
-            header.set_checksum(checksum);
-            header.bytes().len() + message.bytes().len()
-        };
+    fn footer_len(&self) -> usize {
+        0
+    }
 
-        buffer.extend_backwards(extend_backwards);
+    fn serialize<'a>(self, mut buffer: SerializeBuffer<'a>) {
+        use packet::BufferViewMut;
+
+        let (mut prefix, message_body, _) = buffer.parts();
+        // implements BufferViewMut, giving us take_obj_xxx_zero methods
+        let mut prefix = &mut prefix;
+
+        assert!(
+            M::Body::EXPECTS_BODY || message_body.len() == 0,
+            "body provided for message that doesn't take a body"
+        );
+        // SECURITY: Use _zero constructors to ensure we zero memory to prevent
+        // leaking information from packets previously stored in this buffer.
+        let mut header = prefix
+            .take_obj_front_zero::<Header>()
+            .expect("too few bytes for ICMP message");
+        let mut message = prefix
+            .take_obj_front_zero::<M>()
+            .expect("too few bytes for ICMP message");
+        *message = self.msg;
+        header.set_msg_type(M::TYPE);
+        header.code = self.code.into();
+        let checksum = IcmpPacket::<I, B, M>::compute_checksum(
+            &header,
+            message.bytes(),
+            message_body,
+            self.src_ip,
+            self.dst_ip,
+        )
+        .unwrap_or_else(|| {
+            panic!(
+                "total ICMP packet length of {} overflows 32-bit length field of pseudo-header",
+                header.bytes().len() + message.bytes().len() + message_body.len(),
+            )
+        });
+        header.set_checksum(checksum);
     }
 }
 
diff --git a/bin/recovery_netstack/core/src/wire/icmp/ndp.rs b/bin/recovery_netstack/core/src/wire/icmp/ndp.rs
index a176642..a214b45 100644
--- a/bin/recovery_netstack/core/src/wire/icmp/ndp.rs
+++ b/bin/recovery_netstack/core/src/wire/icmp/ndp.rs
@@ -119,7 +119,7 @@
     use crate::ip::Ipv6Addr;
     use crate::wire::util::{OptionImpl, OptionImplErr};
 
-    create_net_enum!{
+    create_net_enum! {
         NdpOptionType,
         SourceLinkLayerAddress: SOURCE_LINK_LAYER_ADDRESS = 1,
         TargetLinkLayerAddress: TARGET_LINK_LAYER_ADDRESS = 2,
@@ -214,19 +214,24 @@
 }
 
 #[cfg(test)]
-mod test {
-    use super::*;
+mod tests {
+    use packet::{ParsablePacket, ParseBuffer};
 
-    use crate::wire::icmp::{IcmpMessage, IcmpPacket};
-    use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketSerializer};
+    use super::*;
+    use crate::wire::icmp::{IcmpMessage, IcmpPacket, IcmpParseArgs};
+    use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketBuilder};
 
     #[test]
     fn parse_neighbor_solicitation() {
         use crate::wire::icmp::testdata::ndp_neighbor::*;
-        let (ip, _) = Ipv6Packet::parse(SOLICITATION_IP_PACKET_BYTES).unwrap();
+        let mut buf = &SOLICITATION_IP_PACKET_BYTES[..];
+        let ip = buf.parse::<Ipv6Packet<_>>().unwrap();
         let (src_ip, dst_ip, hop_limit) = (ip.src_ip(), ip.dst_ip(), ip.hop_limit());
-        let (icmp, _) =
-            IcmpPacket::<_, _, NeighborSolicitation>::parse(ip.body(), src_ip, dst_ip).unwrap();
+        let icmp = buf
+            .parse_with::<_, IcmpPacket<_, _, NeighborSolicitation>>(IcmpParseArgs::new(
+                src_ip, dst_ip,
+            ))
+            .unwrap();
 
         assert_eq!(icmp.message().target_address.ipv6_bytes(), TARGET_ADDRESS);
         for option in icmp.ndp_options().iter() {
@@ -242,10 +247,14 @@
     #[test]
     fn parse_neighbor_advertisment() {
         use crate::wire::icmp::testdata::ndp_neighbor::*;
-        let (ip, _) = Ipv6Packet::parse(ADVERTISMENT_IP_PACKET_BYTES).unwrap();
+        let mut buf = &ADVERTISMENT_IP_PACKET_BYTES[..];
+        let ip = buf.parse::<Ipv6Packet<_>>().unwrap();
         let (src_ip, dst_ip, hop_limit) = (ip.src_ip(), ip.dst_ip(), ip.hop_limit());
-        let (icmp, _) =
-            IcmpPacket::<_, _, NeighborAdvertisment>::parse(ip.body(), src_ip, dst_ip).unwrap();
+        let icmp = buf
+            .parse_with::<_, IcmpPacket<_, _, NeighborAdvertisment>>(IcmpParseArgs::new(
+                src_ip, dst_ip,
+            ))
+            .unwrap();
         assert_eq!(icmp.message().target_address.ipv6_bytes(), TARGET_ADDRESS);
         assert_eq!(icmp.ndp_options().iter().count(), 0);
     }
@@ -253,10 +262,14 @@
     #[test]
     fn parse_router_advertisment() {
         use crate::wire::icmp::testdata::ndp_router::*;
-        let (ip, _) = Ipv6Packet::parse(ADVERTISMENT_IP_PACKET_BYTES).unwrap();
+        let mut buf = &ADVERTISMENT_IP_PACKET_BYTES[..];
+        let ip = buf.parse::<Ipv6Packet<_>>().unwrap();
         let (src_ip, dst_ip) = (ip.src_ip(), ip.dst_ip());
-        let (icmp, _) =
-            IcmpPacket::<_, _, RouterAdvertisment>::parse(ip.body(), src_ip, dst_ip).unwrap();
+        let icmp = buf
+            .parse_with::<_, IcmpPacket<_, _, RouterAdvertisment>>(IcmpParseArgs::new(
+                src_ip, dst_ip,
+            ))
+            .unwrap();
         assert_eq!(icmp.message().current_hop_limit, HOP_LIMIT);
         assert_eq!(icmp.message().router_lifetime(), LIFETIME);
         assert_eq!(icmp.message().reachable_time(), REACHABLE_TIME);
diff --git a/bin/recovery_netstack/core/src/wire/ipv4.rs b/bin/recovery_netstack/core/src/wire/ipv4.rs
index 3c99995..e661523 100644
--- a/bin/recovery_netstack/core/src/wire/ipv4.rs
+++ b/bin/recovery_netstack/core/src/wire/ipv4.rs
@@ -5,14 +5,16 @@
 //! Parsing and serialization of IPv4 packets.
 
 use std::fmt::{self, Debug, Formatter};
-use std::ops::Range;
 
 use byteorder::{ByteOrder, NetworkEndian};
+use packet::{
+    BufferView, BufferViewMut, PacketBuilder, ParsablePacket, ParseMetadata, SerializeBuffer,
+};
 use zerocopy::{AsBytes, ByteSlice, ByteSliceMut, FromBytes, LayoutVerified, Unaligned};
 
 use crate::error::ParseError;
 use crate::ip::{IpProto, Ipv4Addr, Ipv4Option};
-use crate::wire::util::{BufferAndRange, Checksum, Options, PacketSerializer};
+use crate::wire::util::{Checksum, Options};
 
 use self::options::Ipv4OptionImpl;
 
@@ -83,7 +85,7 @@
 /// necessary.
 ///
 /// An `Ipv4Packet` - whether parsed using `parse` or created using
-/// `Ipv4PacketSerializer` - maintains the invariant that the checksum is always
+/// `Ipv4PacketBuilder` - maintains the invariant that the checksum is always
 /// valid.
 pub struct Ipv4Packet<B> {
     hdr_prefix: LayoutVerified<B, HeaderPrefix>,
@@ -91,27 +93,31 @@
     body: B,
 }
 
-impl<B: ByteSlice> Ipv4Packet<B> {
-    /// Parse an IPv4 packet.
-    ///
-    /// `parse` parses `bytes` as an IPv4 packet and validates the checksum. It
-    /// returns the byte range corresponding to the body within `bytes`. This
-    /// can be useful when extracting the encapsulated payload to send to
-    /// another layer of the stack.
-    pub fn parse(bytes: B) -> Result<(Ipv4Packet<B>, Range<usize>), ParseError> {
+impl<B: ByteSlice> ParsablePacket<B, ()> for Ipv4Packet<B> {
+    type Error = ParseError;
+
+    fn parse_metadata(&self) -> ParseMetadata {
+        let header_len = self.hdr_prefix.bytes().len() + self.options.bytes().len();
+        ParseMetadata::from_packet(header_len, self.body.len(), 0)
+    }
+
+    fn parse<BV: BufferView<B>>(mut buffer: BV, args: ()) -> Result<Self, ParseError> {
         // See for details: https://en.wikipedia.org/wiki/IPv4#Header
 
-        let total_len = bytes.len();
-        let (hdr_prefix, rest) = LayoutVerified::<B, HeaderPrefix>::new_from_prefix(bytes)
+        let total_len = buffer.len();
+        let hdr_prefix = buffer
+            .take_obj_front::<HeaderPrefix>()
             .ok_or_else(debug_err_fn!(
                 ParseError::Format,
                 "too few bytes for header"
             ))?;
         let hdr_bytes = (hdr_prefix.ihl() * 4) as usize;
-        if hdr_bytes > total_len || hdr_bytes < hdr_prefix.bytes().len() {
+        if hdr_bytes < HEADER_PREFIX_SIZE {
             return debug_err!(Err(ParseError::Format), "invalid IHL: {}", hdr_prefix.ihl());
         }
-        let (options, body) = rest.split_at(hdr_bytes - HEADER_PREFIX_SIZE);
+        let options = buffer
+            .take_front(hdr_bytes - HEADER_PREFIX_SIZE)
+            .ok_or_else(debug_err_fn!(ParseError::Format, "IHL larger than buffer"))?;
         let options = Options::parse(options).map_err(|_| ParseError::Format)?;
         if hdr_prefix.version() != 4 {
             return debug_err!(
@@ -121,10 +127,15 @@
             );
         }
         let body = if (hdr_prefix.total_length() as usize) < total_len {
-            let (body, _) = body.split_at(hdr_prefix.total_length() as usize - hdr_bytes);
+            // This unwrap is safe because of the check against total_len.
+            let body = buffer
+                .take_back(hdr_prefix.total_length() as usize - hdr_bytes)
+                .unwrap();
+            // Discard the padding left by the previous layer.
+            buffer.into_rest();
             body
         } else if hdr_prefix.total_length() as usize == total_len {
-            body
+            buffer.into_rest()
         } else {
             // we don't yet support IPv4 fragmentation
             return debug_err!(Err(ParseError::NotSupported), "fragmentation not supported");
@@ -138,10 +149,11 @@
         if packet.compute_header_checksum() != packet.hdr_prefix.hdr_checksum() {
             return debug_err!(Err(ParseError::Checksum), "invalid checksum");
         }
-        let hdr_len = packet.hdr_prefix.bytes().len() + packet.options.bytes().len();
-        Ok((packet, hdr_len..total_len))
+        Ok(packet)
     }
+}
 
+impl<B: ByteSlice> Ipv4Packet<B> {
     /// Iterate over the IPv4 header options.
     pub fn iter_options<'a>(&'a self) -> impl 'a + Iterator<Item = Ipv4Option> {
         self.options.iter()
@@ -230,9 +242,9 @@
         self.header_len() + self.body.len()
     }
 
-    /// Construct a serializer with the same contents as this packet.
-    pub fn serializer(&self) -> Ipv4PacketSerializer {
-        let mut s = Ipv4PacketSerializer {
+    /// Construct a builder with the same contents as this packet.
+    pub fn builder(&self) -> Ipv4PacketBuilder {
+        let mut s = Ipv4PacketBuilder {
             dscp: self.dscp(),
             ecn: self.ecn(),
             id: self.id(),
@@ -289,8 +301,8 @@
     }
 }
 
-/// A serializer for IPv4 packets.
-pub struct Ipv4PacketSerializer {
+/// A builder for IPv4 packets.
+pub struct Ipv4PacketBuilder {
     dscp: u8,
     ecn: u8,
     id: u16,
@@ -302,12 +314,10 @@
     dst_ip: Ipv4Addr,
 }
 
-impl Ipv4PacketSerializer {
-    /// Construct a new `Ipv4PacketSerializer`.
-    pub fn new(
-        src_ip: Ipv4Addr, dst_ip: Ipv4Addr, ttl: u8, proto: IpProto,
-    ) -> Ipv4PacketSerializer {
-        Ipv4PacketSerializer {
+impl Ipv4PacketBuilder {
+    /// Construct a new `Ipv4PacketBuilder`.
+    pub fn new(src_ip: Ipv4Addr, dst_ip: Ipv4Addr, ttl: u8, proto: IpProto) -> Ipv4PacketBuilder {
+        Ipv4PacketBuilder {
             dscp: 0,
             ecn: 0,
             id: 0,
@@ -382,61 +392,61 @@
 // used by wire::icmp
 pub const MIN_HEADER_BYTES: usize = 20;
 
-impl PacketSerializer for Ipv4PacketSerializer {
-    fn max_header_bytes(&self) -> usize {
-        MAX_HEADER_BYTES
-    }
-
-    fn min_header_bytes(&self) -> usize {
+impl PacketBuilder for Ipv4PacketBuilder {
+    fn header_len(&self) -> usize {
         MIN_HEADER_BYTES
     }
 
-    fn serialize<B: AsRef<[u8]> + AsMut<[u8]>>(self, buffer: &mut BufferAndRange<B>) {
-        let extend_backwards = {
-            let (header, body, _) = buffer.parts_mut();
-            // create a 0-byte slice for the options since we don't support
-            // serializing options yet (NET-955)
-            let (options, body) = body.split_at_mut(0);
-            // SECURITY: Use _zeroed constructor to ensure we zero memory to prevent
-            // leaking information from packets previously stored in this buffer.
-            let (_, hdr_prefix) =
-                LayoutVerified::<_, HeaderPrefix>::new_unaligned_from_suffix_zeroed(header)
-                    .expect("too few bytes for IPv4 header");
-            let options =
-                Options::parse(options).expect("parsing an empty options slice should not fail");
-            let mut packet = Ipv4Packet {
-                hdr_prefix,
-                options,
-                body,
-            };
+    fn min_body_len(&self) -> usize {
+        0
+    }
 
-            packet.hdr_prefix.version_ihl = (4u8 << 4) | 5;
-            packet.hdr_prefix.dscp_ecn = (self.dscp << 2) | self.ecn;
-            let total_len = packet.total_packet_len();
-            if total_len >= 1 << 16 {
-                panic!(
-                    "packet length of {} exceeds maximum of {}",
-                    total_len,
-                    1 << 16 - 1,
-                );
-            }
-            NetworkEndian::write_u16(&mut packet.hdr_prefix.total_len, total_len as u16);
-            NetworkEndian::write_u16(&mut packet.hdr_prefix.id, self.id);
-            NetworkEndian::write_u16(
-                &mut packet.hdr_prefix.flags_frag_off,
-                ((u16::from(self.flags)) << 13) | self.frag_off,
-            );
-            packet.hdr_prefix.ttl = self.ttl;
-            packet.hdr_prefix.proto = self.proto;
-            packet.hdr_prefix.src_ip = self.src_ip.ipv4_bytes();
-            packet.hdr_prefix.dst_ip = self.dst_ip.ipv4_bytes();
-            let checksum = packet.compute_header_checksum();
-            NetworkEndian::write_u16(&mut packet.hdr_prefix.hdr_checksum, checksum);
+    fn footer_len(&self) -> usize {
+        0
+    }
 
-            packet.header_len()
+    fn serialize<'a>(self, mut buffer: SerializeBuffer<'a>) {
+        let (mut header, body, _) = buffer.parts();
+        // implements BufferViewMut, giving us take_obj_xxx_zero methods
+        let mut header = &mut header;
+
+        // SECURITY: Use _zero constructor to ensure we zero memory to prevent
+        // leaking information from packets previously stored in this buffer.
+        let hdr_prefix = header
+            .take_obj_front_zero::<HeaderPrefix>()
+            .expect("too few bytes for IPv4 header");
+        // create a 0-byte slice for the options since we don't support
+        // serializing options yet (NET-955)
+        let options =
+            Options::parse(&mut [][..]).expect("parsing an empty options slice should not fail");
+        let mut packet = Ipv4Packet {
+            hdr_prefix,
+            options,
+            body,
         };
 
-        buffer.extend_backwards(extend_backwards);
+        packet.hdr_prefix.version_ihl = (4u8 << 4) | 5;
+        packet.hdr_prefix.dscp_ecn = (self.dscp << 2) | self.ecn;
+        let total_len = packet.total_packet_len();
+        if total_len >= 1 << 16 {
+            panic!(
+                "packet length of {} exceeds maximum of {}",
+                total_len,
+                1 << 16 - 1,
+            );
+        }
+        NetworkEndian::write_u16(&mut packet.hdr_prefix.total_len, total_len as u16);
+        NetworkEndian::write_u16(&mut packet.hdr_prefix.id, self.id);
+        NetworkEndian::write_u16(
+            &mut packet.hdr_prefix.flags_frag_off,
+            ((u16::from(self.flags)) << 13) | self.frag_off,
+        );
+        packet.hdr_prefix.ttl = self.ttl;
+        packet.hdr_prefix.proto = self.proto;
+        packet.hdr_prefix.src_ip = self.src_ip.ipv4_bytes();
+        packet.hdr_prefix.dst_ip = self.dst_ip.ipv4_bytes();
+        let checksum = packet.compute_header_checksum();
+        NetworkEndian::write_u16(&mut packet.hdr_prefix.hdr_checksum, checksum);
     }
 }
 
@@ -487,10 +497,11 @@
 
 #[cfg(test)]
 mod tests {
+    use packet::{Buf, BufferSerializer, ParseBuffer, Serializer};
+
     use super::*;
     use crate::device::ethernet::EtherType;
     use crate::wire::ethernet::EthernetFrame;
-    use crate::wire::util::SerializationRequest;
 
     const DEFAULT_SRC_IP: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]);
     const DEFAULT_DST_IP: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]);
@@ -499,14 +510,14 @@
     fn test_parse_serialize_full_tcp() {
         use crate::wire::testdata::tls_client_hello::*;
 
-        let (frame, body_range) = EthernetFrame::parse(ETHERNET_FRAME_BYTES).unwrap();
-        assert_eq!(body_range, ETHERNET_BODY_RANGE);
+        let mut buf = &ETHERNET_FRAME_BYTES[..];
+        let frame = buf.parse::<EthernetFrame<_>>().unwrap();
         assert_eq!(frame.src_mac(), ETHERNET_SRC_MAC);
         assert_eq!(frame.dst_mac(), ETHERNET_DST_MAC);
         assert_eq!(frame.ethertype(), Some(Ok(EtherType::Ipv4)));
 
-        let (packet, body_range) = Ipv4Packet::parse(frame.body()).unwrap();
-        assert_eq!(body_range, IP_BODY_RANGE);
+        let mut body = frame.body();
+        let packet = body.parse::<Ipv4Packet<_>>().unwrap();
         assert_eq!(packet.proto(), Ok(IpProto::Tcp));
         assert_eq!(packet.dscp(), IP_DSCP);
         assert_eq!(packet.ecn(), IP_ECN);
@@ -518,9 +529,10 @@
         assert_eq!(packet.src_ip(), IP_SRC_IP);
         assert_eq!(packet.dst_ip(), IP_DST_IP);
 
-        let buffer = (&frame.body()[body_range])
-            .encapsulate(packet.serializer())
-            .encapsulate(frame.serializer())
+        let buffer = packet
+            .body()
+            .encapsulate(packet.builder())
+            .encapsulate(frame.builder())
             .serialize_outer();
         assert_eq!(buffer.as_ref(), ETHERNET_FRAME_BYTES);
     }
@@ -529,14 +541,14 @@
     fn test_parse_serialize_full_udp() {
         use crate::wire::testdata::dns_request::*;
 
-        let (frame, body_range) = EthernetFrame::parse(ETHERNET_FRAME_BYTES).unwrap();
-        assert_eq!(body_range, ETHERNET_BODY_RANGE);
+        let mut buf = &ETHERNET_FRAME_BYTES[..];
+        let frame = buf.parse::<EthernetFrame<_>>().unwrap();
         assert_eq!(frame.src_mac(), ETHERNET_SRC_MAC);
         assert_eq!(frame.dst_mac(), ETHERNET_DST_MAC);
         assert_eq!(frame.ethertype(), Some(Ok(EtherType::Ipv4)));
 
-        let (packet, body_range) = Ipv4Packet::parse(frame.body()).unwrap();
-        assert_eq!(body_range, IP_BODY_RANGE);
+        let mut body = frame.body();
+        let packet = body.parse::<Ipv4Packet<_>>().unwrap();
         assert_eq!(packet.proto(), Ok(IpProto::Udp));
         assert_eq!(packet.dscp(), IP_DSCP);
         assert_eq!(packet.ecn(), IP_ECN);
@@ -548,9 +560,10 @@
         assert_eq!(packet.src_ip(), IP_SRC_IP);
         assert_eq!(packet.dst_ip(), IP_DST_IP);
 
-        let buffer = (&frame.body()[body_range])
-            .encapsulate(packet.serializer())
-            .encapsulate(frame.serializer())
+        let buffer = packet
+            .body()
+            .encapsulate(packet.builder())
+            .encapsulate(frame.builder())
             .serialize_outer();
         assert_eq!(buffer.as_ref(), ETHERNET_FRAME_BYTES);
     }
@@ -581,9 +594,8 @@
 
     #[test]
     fn test_parse() {
-        let bytes = hdr_prefix_to_bytes(new_hdr_prefix());
-        let (packet, body_range) = Ipv4Packet::parse(&bytes[..]).unwrap();
-        assert_eq!(body_range, 20..20);
+        let mut bytes = &hdr_prefix_to_bytes(new_hdr_prefix())[..];
+        let packet = bytes.parse::<Ipv4Packet<_>>().unwrap();
         assert_eq!(packet.id(), 0x0102);
         assert_eq!(packet.ttl(), 0x03);
         assert_eq!(packet.proto(), Ok(IpProto::Tcp));
@@ -598,7 +610,9 @@
         let mut hdr_prefix = new_hdr_prefix();
         hdr_prefix.version_ihl = (5 << 4) | 5;
         assert_eq!(
-            Ipv4Packet::parse(&hdr_prefix_to_bytes(hdr_prefix)[..]).unwrap_err(),
+            (&hdr_prefix_to_bytes(hdr_prefix)[..])
+                .parse::<Ipv4Packet<_>>()
+                .unwrap_err(),
             ParseError::Format
         );
 
@@ -607,7 +621,9 @@
         let mut hdr_prefix = new_hdr_prefix();
         hdr_prefix.version_ihl = (4 << 4) | 4;
         assert_eq!(
-            Ipv4Packet::parse(&hdr_prefix_to_bytes(hdr_prefix)[..]).unwrap_err(),
+            (&hdr_prefix_to_bytes(hdr_prefix)[..])
+                .parse::<Ipv4Packet<_>>()
+                .unwrap_err(),
             ParseError::Format
         );
 
@@ -616,46 +632,39 @@
         let mut hdr_prefix = new_hdr_prefix();
         hdr_prefix.version_ihl = (4 << 4) | 6;
         assert_eq!(
-            Ipv4Packet::parse(&hdr_prefix_to_bytes(hdr_prefix)[..]).unwrap_err(),
+            (&hdr_prefix_to_bytes(hdr_prefix)[..])
+                .parse::<Ipv4Packet<_>>()
+                .unwrap_err(),
             ParseError::Format
         );
     }
 
-    // Return a stock Ipv4PacketSerializer with reasonable default values.
-    fn new_serializer() -> Ipv4PacketSerializer {
-        Ipv4PacketSerializer::new(DEFAULT_DST_IP, DEFAULT_DST_IP, 64, IpProto::Tcp)
+    // Return a stock Ipv4PacketBuilder with reasonable default values.
+    fn new_builder() -> Ipv4PacketBuilder {
+        Ipv4PacketBuilder::new(DEFAULT_DST_IP, DEFAULT_DST_IP, 64, IpProto::Tcp)
     }
 
     #[test]
     fn test_serialize() {
-        let mut buf = [0; 30];
-        let mut serializer = new_serializer();
-        serializer.dscp(0x12);
-        serializer.ecn(3);
-        serializer.id(0x0405);
-        serializer.df_flag(true);
-        serializer.mf_flag(true);
-        serializer.fragment_offset(0x0607);
-        {
-            // set the body
-            (&mut buf[20..]).copy_from_slice(&[0, 1, 2, 3, 3, 4, 5, 7, 8, 9]);
-            let mut buffer = BufferAndRange::new_from(&mut buf[..], 20..);
-            serializer.serialize(&mut buffer);
-            assert_eq!(buffer.range(), 0..30);
-        }
+        let mut builder = new_builder();
+        builder.dscp(0x12);
+        builder.ecn(3);
+        builder.id(0x0405);
+        builder.df_flag(true);
+        builder.mf_flag(true);
+        builder.fragment_offset(0x0607);
 
-        // assert that we get the literal bytes we expected
+        let mut buf = (&[0, 1, 2, 3, 3, 4, 5, 7, 8, 9])
+            .encapsulate(builder)
+            .serialize_outer();
         assert_eq!(
-            buf,
+            buf.as_ref(),
             [
                 69, 75, 0, 30, 4, 5, 102, 7, 64, 6, 248, 103, 5, 6, 7, 8, 5, 6, 7, 8, 0, 1, 2, 3,
                 3, 4, 5, 7, 8, 9
             ],
         );
-        let (packet, body_range) = Ipv4Packet::parse(&buf[..]).unwrap();
-        // assert that when we parse those bytes, we get the values we set in
-        // the serializer
-        assert_eq!(body_range, 20..30);
+        let packet = buf.parse::<Ipv4Packet<_>>().unwrap();
         assert_eq!(packet.dscp(), 0x12);
         assert_eq!(packet.ecn(), 3);
         assert_eq!(packet.id(), 0x0405);
@@ -666,12 +675,16 @@
 
     #[test]
     fn test_serialize_zeroes() {
-        // Test that Ipv4PacketSerializer::serialize properly zeroes memory before
+        // Test that Ipv4PacketBuilder::serialize properly zeroes memory before
         // serializing the header.
         let mut buf_0 = [0; 20];
-        new_serializer().serialize(&mut BufferAndRange::new_from(&mut buf_0[..], 20..));
+        BufferSerializer::new_vec(Buf::new(&mut buf_0[..], 20..))
+            .encapsulate(new_builder())
+            .serialize_outer();
         let mut buf_1 = [0xFF; 20];
-        new_serializer().serialize(&mut BufferAndRange::new_from(&mut buf_1[..], 20..));
+        BufferSerializer::new_vec(Buf::new(&mut buf_1[..], 20..))
+            .encapsulate(new_builder())
+            .serialize_outer();
         assert_eq!(buf_0, buf_1);
     }
 
@@ -679,14 +692,8 @@
     #[should_panic]
     fn test_serialize_panic_packet_length() {
         // Test that a packet which is longer than 2^16 - 1 bytes is rejected.
-        new_serializer().serialize(&mut BufferAndRange::new_from(&mut [0; 1 << 16][..], 20..));
-    }
-
-    #[test]
-    #[should_panic]
-    fn test_serialize_panic_insufficient_header_space() {
-        // Test that a body range which doesn't leave enough room for the header
-        // is rejected.
-        new_serializer().serialize(&mut BufferAndRange::new_from(&mut [0; 20], ..));
+        BufferSerializer::new_vec(Buf::new(&mut [0; (1 << 16) - 20][..], ..))
+            .encapsulate(new_builder())
+            .serialize_outer();
     }
 }
diff --git a/bin/recovery_netstack/core/src/wire/ipv6.rs b/bin/recovery_netstack/core/src/wire/ipv6.rs
index e522246..61ab650 100644
--- a/bin/recovery_netstack/core/src/wire/ipv6.rs
+++ b/bin/recovery_netstack/core/src/wire/ipv6.rs
@@ -5,15 +5,16 @@
 //! Parsing and serialization of IPv6 packets.
 
 use std::fmt::{self, Debug, Formatter};
-use std::ops::Range;
 
 use byteorder::{ByteOrder, NetworkEndian};
 use log::debug;
+use packet::{
+    BufferView, BufferViewMut, PacketBuilder, ParsablePacket, ParseMetadata, SerializeBuffer,
+};
 use zerocopy::{AsBytes, ByteSlice, ByteSliceMut, FromBytes, LayoutVerified, Unaligned};
 
 use crate::error::ParseError;
 use crate::ip::{IpProto, Ipv6Addr};
-use crate::wire::util::{BufferAndRange, PacketSerializer};
 
 // FixedHeader has the same memory layout (thanks to repr(C, packed)) as an IPv6
 // fixed header. Thus, we can simply reinterpret the bytes of the IPv6 fixed
@@ -82,16 +83,23 @@
     body: B,
 }
 
-impl<B: ByteSlice> Ipv6Packet<B> {
-    /// Parse an IPv6 packet.
-    ///
-    /// `parse` parses `bytes` as an IPv6 packet. It returns the byte range
-    /// corresponding to the body within `bytes`. This can be useful when
-    /// extracting the encapsulated payload to send to another layer of the
-    /// stack.
-    pub fn parse(bytes: B) -> Result<(Ipv6Packet<B>, Range<usize>), ParseError> {
-        let total_len = bytes.len();
-        let (fixed_hdr, rest) = LayoutVerified::<B, FixedHeader>::new_from_prefix(bytes)
+impl<B: ByteSlice> ParsablePacket<B, ()> for Ipv6Packet<B> {
+    type Error = ParseError;
+
+    fn parse_metadata(&self) -> ParseMetadata {
+        let header_len = self.fixed_hdr.bytes().len()
+            + self
+                .extension_hdrs
+                .as_ref()
+                .map(|hdrs| hdrs.len())
+                .unwrap_or(0);
+        ParseMetadata::from_packet(header_len, self.body.len(), 0)
+    }
+
+    fn parse<BV: BufferView<B>>(mut buffer: BV, args: ()) -> Result<Self, ParseError> {
+        let total_len = buffer.len();
+        let fixed_hdr = buffer
+            .take_obj_front::<FixedHeader>()
             .ok_or_else(debug_err_fn!(
                 ParseError::Format,
                 "too few bytes for header"
@@ -100,7 +108,7 @@
         let packet = Ipv6Packet {
             fixed_hdr,
             extension_hdrs: None,
-            body: rest,
+            body: buffer.into_rest(),
         };
         if packet.fixed_hdr.version() != 6 {
             return debug_err!(
@@ -115,11 +123,11 @@
                 "payload length does not match header"
             );
         }
-        let extensions_len = packet.extension_hdrs.as_ref().map(|b| b.len()).unwrap_or(0);
-        let hdr_len = packet.fixed_hdr.bytes().len() + extensions_len;
-        Ok((packet, hdr_len..total_len))
+        Ok(packet)
     }
+}
 
+impl<B: ByteSlice> Ipv6Packet<B> {
     /// The packet body.
     pub fn body(&self) -> &[u8] {
         &self.body
@@ -170,9 +178,9 @@
         self.extension_hdrs.as_ref().map(|b| b.len()).unwrap_or(0) + self.body.len()
     }
 
-    /// Construct a serializer with the same contents as this packet.
-    pub fn serializer(&self) -> Ipv6PacketSerializer {
-        Ipv6PacketSerializer {
+    /// Construct a builder with the same contents as this packet.
+    pub fn builder(&self) -> Ipv6PacketBuilder {
+        Ipv6PacketBuilder {
             ds: self.ds(),
             ecn: self.ecn(),
             flowlabel: self.flowlabel(),
@@ -207,8 +215,8 @@
     }
 }
 
-/// A serializer for IPv6 packets.
-pub struct Ipv6PacketSerializer {
+/// A builder for IPv6 packets.
+pub struct Ipv6PacketBuilder {
     ds: u8,
     ecn: u8,
     flowlabel: u32,
@@ -218,12 +226,12 @@
     dst_ip: Ipv6Addr,
 }
 
-impl Ipv6PacketSerializer {
-    /// Construct a new `Ipv6PacketSerializer`.
+impl Ipv6PacketBuilder {
+    /// Construct a new `Ipv6PacketBuilder`.
     pub fn new(
         src_ip: Ipv6Addr, dst_ip: Ipv6Addr, hop_limit: u8, proto: IpProto,
-    ) -> Ipv6PacketSerializer {
-        Ipv6PacketSerializer {
+    ) -> Ipv6PacketBuilder {
+        Ipv6PacketBuilder {
             ds: 0,
             ecn: 0,
             flowlabel: 0,
@@ -267,60 +275,63 @@
 
 const FIXED_HEADER_BYTES: usize = 40;
 
-impl PacketSerializer for Ipv6PacketSerializer {
-    fn max_header_bytes(&self) -> usize {
+impl PacketBuilder for Ipv6PacketBuilder {
+    fn header_len(&self) -> usize {
         // TODO(joshlf): Update when we support serializing extension headers
         FIXED_HEADER_BYTES
     }
 
-    fn min_header_bytes(&self) -> usize {
-        FIXED_HEADER_BYTES
+    fn min_body_len(&self) -> usize {
+        0
     }
 
-    fn serialize<B: AsRef<[u8]> + AsMut<[u8]>>(self, buffer: &mut BufferAndRange<B>) {
-        let extend_backwards = {
-            let (header, body, _) = buffer.parts_mut();
-            // TODO(tkilbourn): support extension headers
-            let (_, fixed_hdr) =
-                LayoutVerified::<_, FixedHeader>::new_unaligned_from_suffix_zeroed(header)
-                    .expect("too few bytes for IPv6 header");
-            let extension_hdrs = None;
-            let mut packet = Ipv6Packet {
-                fixed_hdr,
-                extension_hdrs,
-                body,
-            };
+    fn footer_len(&self) -> usize {
+        0
+    }
 
-            packet.fixed_hdr.version_tc_flowlabel = [
-                (6u8 << 4) | self.ds >> 2,
-                ((self.ds & 0b11) << 6) | (self.ecn << 4) | (self.flowlabel >> 16) as u8,
-                ((self.flowlabel >> 8) & 0xFF) as u8,
-                (self.flowlabel & 0xFF) as u8,
-            ];
-            let payload_len = packet.payload_len();
-            debug!("serialize: payload_len={}", payload_len);
-            if payload_len >= 1 << 16 {
-                panic!(
-                    "packet length of {} exceeds maximum of {}",
-                    payload_len,
-                    1 << 16 - 1,
-                );
-            }
-            NetworkEndian::write_u16(&mut packet.fixed_hdr.payload_len, payload_len as u16);
-            packet.fixed_hdr.next_hdr = self.proto;
-            packet.fixed_hdr.hop_limit = self.hop_limit;
-            packet.fixed_hdr.src_ip = self.src_ip.ipv6_bytes();
-            packet.fixed_hdr.dst_ip = self.dst_ip.ipv6_bytes();
+    fn serialize<'a>(self, mut buffer: SerializeBuffer<'a>) {
+        let (mut header, body, _) = buffer.parts();
+        // implements BufferViewMut, giving us take_obj_xxx_zero methods
+        let mut header = &mut header;
 
-            packet.header_len()
+        // TODO(tkilbourn): support extension headers
+        let fixed_hdr = header
+            .take_obj_front_zero::<FixedHeader>()
+            .expect("too few bytes for IPv6 header");
+        let extension_hdrs = None;
+        let mut packet = Ipv6Packet {
+            fixed_hdr,
+            extension_hdrs,
+            body,
         };
 
-        buffer.extend_backwards(extend_backwards);
+        packet.fixed_hdr.version_tc_flowlabel = [
+            (6u8 << 4) | self.ds >> 2,
+            ((self.ds & 0b11) << 6) | (self.ecn << 4) | (self.flowlabel >> 16) as u8,
+            ((self.flowlabel >> 8) & 0xFF) as u8,
+            (self.flowlabel & 0xFF) as u8,
+        ];
+        let payload_len = packet.payload_len();
+        debug!("serialize: payload_len={}", payload_len);
+        if payload_len >= 1 << 16 {
+            panic!(
+                "packet length of {} exceeds maximum of {}",
+                payload_len,
+                1 << 16 - 1,
+            );
+        }
+        NetworkEndian::write_u16(&mut packet.fixed_hdr.payload_len, payload_len as u16);
+        packet.fixed_hdr.next_hdr = self.proto;
+        packet.fixed_hdr.hop_limit = self.hop_limit;
+        packet.fixed_hdr.src_ip = self.src_ip.ipv6_bytes();
+        packet.fixed_hdr.dst_ip = self.dst_ip.ipv6_bytes();
     }
 }
 
 #[cfg(test)]
 mod tests {
+    use packet::{Buf, BufferSerializer, ParseBuffer, Serializer};
+
     use super::*;
 
     const DEFAULT_SRC_IP: Ipv6Addr =
@@ -354,9 +365,8 @@
 
     #[test]
     fn test_parse() {
-        let bytes = fixed_hdr_to_bytes(new_fixed_hdr());
-        let (packet, body_range) = Ipv6Packet::parse(&bytes[..]).unwrap();
-        assert_eq!(body_range, 40..40);
+        let mut buf = &fixed_hdr_to_bytes(new_fixed_hdr())[..];
+        let packet = buf.parse::<Ipv6Packet<_>>().unwrap();
         assert_eq!(packet.ds(), 0);
         assert_eq!(packet.ecn(), 2);
         assert_eq!(packet.flowlabel(), 0x77);
@@ -373,7 +383,9 @@
         let mut fixed_hdr = new_fixed_hdr();
         fixed_hdr.version_tc_flowlabel[0] = 0x50;
         assert_eq!(
-            Ipv6Packet::parse(&fixed_hdr_to_bytes(fixed_hdr)[..]).unwrap_err(),
+            (&fixed_hdr_to_bytes(fixed_hdr)[..])
+                .parse::<Ipv6Packet<_>>()
+                .unwrap_err(),
             ParseError::Format
         );
 
@@ -381,34 +393,30 @@
         let mut fixed_hdr = new_fixed_hdr();
         NetworkEndian::write_u16(&mut fixed_hdr.payload_len[..], 2);
         assert_eq!(
-            Ipv6Packet::parse(&fixed_hdr_to_bytes(fixed_hdr)[..]).unwrap_err(),
+            (&fixed_hdr_to_bytes(fixed_hdr)[..])
+                .parse::<Ipv6Packet<_>>()
+                .unwrap_err(),
             ParseError::Format
         );
     }
 
-    // Return a stock Ipv6PacketSerializer with reasonable default values.
-    fn new_serializer() -> Ipv6PacketSerializer {
-        Ipv6PacketSerializer::new(DEFAULT_SRC_IP, DEFAULT_DST_IP, 64, IpProto::Tcp)
+    // Return a stock Ipv6PacketBuilder with reasonable default values.
+    fn new_builder() -> Ipv6PacketBuilder {
+        Ipv6PacketBuilder::new(DEFAULT_SRC_IP, DEFAULT_DST_IP, 64, IpProto::Tcp)
     }
 
     #[test]
     fn test_serialize() {
-        let mut buf = [0; 50];
-        let mut serializer = new_serializer();
-        serializer.ds(0x12);
-        serializer.ecn(3);
-        serializer.flowlabel(0x10405);
-        {
-            // set the body
-            (&mut buf[40..]).copy_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
-            let mut buffer = BufferAndRange::new_from(&mut buf[..], 40..);
-            serializer.serialize(&mut buffer);
-            assert_eq!(buffer.range(), 0..50);
-        }
-
+        let mut builder = new_builder();
+        builder.ds(0x12);
+        builder.ecn(3);
+        builder.flowlabel(0x10405);
+        let mut buf = (&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
+            .encapsulate(builder)
+            .serialize_outer();
         // assert that we get the literal bytes we expected
         assert_eq!(
-            &buf[..],
+            buf.as_ref(),
             &[
                 100, 177, 4, 5, 0, 10, 6, 64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
                 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 0, 1, 2, 3, 4,
@@ -416,10 +424,9 @@
             ][..],
         );
 
-        let (packet, body_range) = Ipv6Packet::parse(&buf[..]).unwrap();
+        let packet = buf.parse::<Ipv6Packet<_>>().unwrap();
         // assert that when we parse those bytes, we get the values we set in
-        // the serializer
-        assert_eq!(body_range, 40..50);
+        // the builder
         assert_eq!(packet.ds(), 0x12);
         assert_eq!(packet.ecn(), 3);
         assert_eq!(packet.flowlabel(), 0x10405);
@@ -427,30 +434,26 @@
 
     #[test]
     fn test_serialize_zeroes() {
-        // Test that Ipv6PacketSerializer::serialize properly zeroes memory before
+        // Test that Ipv6PacketBuilder::serialize properly zeroes memory before
         // serializing the header.
         let mut buf_0 = [0; 40];
-        new_serializer().serialize(&mut BufferAndRange::new_from(&mut buf_0[..], 40..));
+        BufferSerializer::new_vec(Buf::new(&mut buf_0[..], 40..))
+            .encapsulate(new_builder())
+            .serialize_outer();
         let mut buf_1 = [0xFF; 40];
-        new_serializer().serialize(&mut BufferAndRange::new_from(&mut buf_1[..], 40..));
+        BufferSerializer::new_vec(Buf::new(&mut buf_1[..], 40..))
+            .encapsulate(new_builder())
+            .serialize_outer();
         assert_eq!(&buf_0[..], &buf_1[..]);
     }
 
     #[test]
     #[should_panic]
     fn test_serialize_panic_packet_length() {
-        // Test that a packet which is longer than 2^16 - 1 bytes is rejected.
-        new_serializer().serialize(&mut BufferAndRange::new_from(
-            &mut [0; (1 << 16) + 40][..],
-            40..,
-        ));
-    }
-
-    #[test]
-    #[should_panic]
-    fn test_serialize_panic_insufficient_header_space() {
-        // Test that a body range which doesn't leave enough room for the header
-        // is rejected.
-        new_serializer().serialize(&mut BufferAndRange::new_from(&mut [0u8; 40][..], ..));
+        // Test that a packet whose payload is longer than 2^16 - 1 bytes is
+        // rejected.
+        BufferSerializer::new_vec(Buf::new(&mut [0; 1 << 16][..], ..))
+            .encapsulate(new_builder())
+            .serialize_outer();
     }
 }
diff --git a/bin/recovery_netstack/core/src/wire/mod.rs b/bin/recovery_netstack/core/src/wire/mod.rs
index 3ba9bf0..a2a100c 100644
--- a/bin/recovery_netstack/core/src/wire/mod.rs
+++ b/bin/recovery_netstack/core/src/wire/mod.rs
@@ -61,7 +61,3 @@
 mod testdata;
 pub mod udp;
 mod util;
-
-pub use self::ethernet::*;
-pub use self::udp::*;
-pub use self::util::{BufferAndRange, InnerSerializationRequest, SerializationRequest};
diff --git a/bin/recovery_netstack/core/src/wire/tcp.rs b/bin/recovery_netstack/core/src/wire/tcp.rs
index 50485e0..47457d3 100644
--- a/bin/recovery_netstack/core/src/wire/tcp.rs
+++ b/bin/recovery_netstack/core/src/wire/tcp.rs
@@ -7,17 +7,17 @@
 #[cfg(test)]
 use std::fmt::{self, Debug, Formatter};
 use std::num::NonZeroU16;
-use std::ops::Range;
 
 use byteorder::{ByteOrder, NetworkEndian};
+use packet::{
+    BufferView, BufferViewMut, PacketBuilder, ParsablePacket, ParseMetadata, SerializeBuffer,
+};
 use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified, Unaligned};
 
 use crate::error::ParseError;
 use crate::ip::{Ip, IpAddr, IpProto};
 use crate::transport::tcp::TcpOption;
-use crate::wire::util::{
-    fits_in_u16, fits_in_u32, BufferAndRange, Checksum, Options, PacketSerializer,
-};
+use crate::wire::util::{fits_in_u16, fits_in_u32, Checksum, Options};
 
 use self::options::TcpOptionImpl;
 
@@ -78,7 +78,7 @@
 /// necessary.
 ///
 /// A `TcpSegment` - whether parsed using `parse` or created using
-/// `TcpSegmentSerializer` - maintains the invariant that the checksum is always
+/// `TcpSegmentBuilder` - maintains the invariant that the checksum is always
 /// valid.
 pub struct TcpSegment<B> {
     hdr_prefix: LayoutVerified<B, HeaderPrefix>,
@@ -86,38 +86,55 @@
     body: B,
 }
 
-impl<B: ByteSlice> TcpSegment<B> {
-    /// Parse a TCP segment.
-    ///
-    /// `parse` parses `bytes` as a TCP segment and validates the checksum. It
-    /// returns the byte range corresponding to the body within `bytes`. This
-    /// can be useful when extracting the encapsulated payload to send to
-    /// another layer of the stack.
-    pub fn parse<A: IpAddr>(
-        bytes: B, src_ip: A, dst_ip: A,
-    ) -> Result<(TcpSegment<B>, Range<usize>), ParseError> {
+/// Arguments required to parse a TCP segment.
+pub struct TcpParseArgs<A: IpAddr> {
+    src_ip: A,
+    dst_ip: A,
+}
+
+impl<A: IpAddr> TcpParseArgs<A> {
+    /// Construct a new `TcpParseArgs`.
+    pub fn new(src_ip: A, dst_ip: A) -> TcpParseArgs<A> {
+        TcpParseArgs { src_ip, dst_ip }
+    }
+}
+
+impl<B: ByteSlice, A: IpAddr> ParsablePacket<B, TcpParseArgs<A>> for TcpSegment<B> {
+    type Error = ParseError;
+
+    fn parse_metadata(&self) -> ParseMetadata {
+        let header_len = self.hdr_prefix.bytes().len() + self.options.bytes().len();
+        ParseMetadata::from_packet(header_len, self.body.len(), 0)
+    }
+
+    fn parse<BV: BufferView<B>>(mut buffer: BV, args: TcpParseArgs<A>) -> Result<Self, ParseError> {
         // See for details: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure
 
-        let (hdr_prefix, rest) = LayoutVerified::<B, HeaderPrefix>::new_from_prefix(bytes)
+        let hdr_prefix = buffer
+            .take_obj_front::<HeaderPrefix>()
             .ok_or_else(debug_err_fn!(
                 ParseError::Format,
                 "too few bytes for header"
             ))?;
         let hdr_bytes = (hdr_prefix.data_offset() * 4) as usize;
-        if hdr_bytes > hdr_prefix.bytes().len() + rest.len() || hdr_bytes < hdr_prefix.bytes().len()
-        {
+        if hdr_bytes < hdr_prefix.bytes().len() {
             return debug_err!(
                 Err(ParseError::Format),
                 "invalid data offset: {}",
                 hdr_prefix.data_offset()
             );
         }
-        let (options, body) = rest.split_at(hdr_bytes - hdr_prefix.bytes().len());
+        let options = buffer
+            .take_front(hdr_bytes - hdr_prefix.bytes().len())
+            .ok_or_else(debug_err_fn!(
+                ParseError::Format,
+                "data offset larger than buffer"
+            ))?;
         let options = Options::parse(options).map_err(|_| ParseError::Format)?;
         let segment = TcpSegment {
             hdr_prefix,
             options,
-            body,
+            body: buffer.into_rest(),
         };
 
         if segment.hdr_prefix.src_port() == 0 || segment.hdr_prefix.dst_port() == 0 {
@@ -126,18 +143,17 @@
 
         let checksum = NetworkEndian::read_u16(&segment.hdr_prefix.checksum);
         if segment
-            .compute_checksum(src_ip, dst_ip)
+            .compute_checksum(args.src_ip, args.dst_ip)
             .ok_or_else(debug_err_fn!(ParseError::Format, "segment too large"))?
             != checksum
         {
             return debug_err!(Err(ParseError::Checksum), "invalid checksum");
         }
-
-        let hdr_len = segment.hdr_prefix.bytes().len() + segment.options.bytes().len();
-        let total_len = hdr_len + segment.body.len();
-        Ok((segment, hdr_len..total_len))
+        Ok(segment)
     }
+}
 
+impl<B: ByteSlice> TcpSegment<B> {
     /// Iterate over the TCP header options.
     pub fn iter_options<'a>(&'a self) -> impl 'a + Iterator<Item = TcpOption> {
         self.options.iter()
@@ -244,9 +260,9 @@
         self.header_len() + self.body.len()
     }
 
-    /// Construct a serializer with the same contents as this packet.
-    pub fn serializer<A: IpAddr>(&self, src_ip: A, dst_ip: A) -> TcpSegmentSerializer<A> {
-        let mut s = TcpSegmentSerializer {
+    /// Construct a builder with the same contents as this packet.
+    pub fn builder<A: IpAddr>(&self, src_ip: A, dst_ip: A) -> TcpSegmentBuilder<A> {
+        let mut s = TcpSegmentBuilder {
             src_ip,
             dst_ip,
             src_port: self.src_port().get(),
@@ -265,12 +281,12 @@
 
 // NOTE(joshlf): In order to ensure that the checksum is always valid, we don't
 // expose any setters for the fields of the TCP segment; the only way to set
-// them is via TcpSegmentSerializer. This, combined with checksum validation
+// them is via TcpSegmentBuilder. This, combined with checksum validation
 // performed in TcpSegment::parse, provides the invariant that a UdpPacket
 // always has a valid checksum.
 
-/// A serializer for TCP segments.
-pub struct TcpSegmentSerializer<A: IpAddr> {
+/// A builder for TCP segments.
+pub struct TcpSegmentBuilder<A: IpAddr> {
     src_ip: A,
     dst_ip: A,
     src_port: u16,
@@ -281,16 +297,16 @@
     window_size: u16,
 }
 
-impl<A: IpAddr> TcpSegmentSerializer<A> {
-    /// Construct a new `TcpSegmentSerializer`.
+impl<A: IpAddr> TcpSegmentBuilder<A> {
+    /// Construct a new `TcpSegmentBuilder`.
     ///
     /// If `ack_num` is `Some`, then the ACK flag will be set.
     pub fn new(
         src_ip: A, dst_ip: A, src_port: NonZeroU16, dst_port: NonZeroU16, seq_num: u32,
         ack_num: Option<u32>, window_size: u16,
-    ) -> TcpSegmentSerializer<A> {
+    ) -> TcpSegmentBuilder<A> {
         let flags = if ack_num.is_some() { 1 << 4 } else { 0 };
-        TcpSegmentSerializer {
+        TcpSegmentBuilder {
             src_ip,
             dst_ip,
             src_port: src_port.get(),
@@ -329,62 +345,62 @@
 const MAX_HEADER_BYTES: usize = 60;
 const MIN_HEADER_BYTES: usize = 20;
 
-impl<A: IpAddr> PacketSerializer for TcpSegmentSerializer<A> {
-    fn max_header_bytes(&self) -> usize {
-        MAX_HEADER_BYTES
-    }
-
-    fn min_header_bytes(&self) -> usize {
+impl<A: IpAddr> PacketBuilder for TcpSegmentBuilder<A> {
+    fn header_len(&self) -> usize {
         MIN_HEADER_BYTES
     }
 
-    fn serialize<B: AsRef<[u8]> + AsMut<[u8]>>(self, buffer: &mut BufferAndRange<B>) {
-        let extend_backwards = {
-            let (header, body, _) = buffer.parts_mut();
-            // create a 0-byte slice for the options since we don't support
-            // serializing options yet (NET-955)
-            let (options, body) = body.split_at_mut(0);
-            // SECURITY: Use _zeroed constructor to ensure we zero memory to prevent
-            // leaking information from packets previously stored in this buffer.
-            let (_, hdr_prefix) =
-                LayoutVerified::<_, HeaderPrefix>::new_unaligned_from_suffix_zeroed(header)
-                    .expect("too few bytes for TCP header");
-            let options =
-                Options::parse(options).expect("parsing an empty options slice should not fail");
-            let mut segment = TcpSegment {
-                hdr_prefix,
-                options,
-                body,
-            };
+    fn min_body_len(&self) -> usize {
+        0
+    }
 
-            NetworkEndian::write_u16(&mut segment.hdr_prefix.src_port, self.src_port);
-            NetworkEndian::write_u16(&mut segment.hdr_prefix.dst_port, self.dst_port);
-            NetworkEndian::write_u32(&mut segment.hdr_prefix.seq_num, self.seq_num);
-            NetworkEndian::write_u32(&mut segment.hdr_prefix.ack, self.ack_num);
-            // Data Offset is hard-coded to 5 until we support serializing options
-            NetworkEndian::write_u16(
-                &mut segment.hdr_prefix.data_offset_reserved_flags,
-                (5u16 << 12) | self.flags,
-            );
-            NetworkEndian::write_u16(&mut segment.hdr_prefix.window_size, self.window_size);
-            // we don't support setting the Urgent Pointer
-            NetworkEndian::write_u16(&mut segment.hdr_prefix.urg_ptr, 0);
+    fn footer_len(&self) -> usize {
+        0
+    }
 
-            let segment_len = segment.total_segment_len();
-            // This ignores the checksum field in the header, so it's fine that we
-            // haven't set it yet, and so it could be filled with arbitrary bytes.
-            let checksum = segment
-                .compute_checksum(self.src_ip, self.dst_ip)
-                .expect(&format!(
-                    "total TCP segment length of {} bytes overflows length field of pseudo-header",
-                    segment_len
-                ));
-            NetworkEndian::write_u16(&mut segment.hdr_prefix.checksum, checksum);
+    fn serialize<'a>(self, mut buffer: SerializeBuffer<'a>) {
+        let (mut header, body, _) = buffer.parts();
+        // implements BufferViewMut, giving us take_obj_xxx_zero methods
+        let mut header = &mut header;
 
-            segment.header_len()
+        // SECURITY: Use _zero constructor to ensure we zero memory to prevent
+        // leaking information from packets previously stored in this buffer.
+        let hdr_prefix = header
+            .take_obj_front_zero::<HeaderPrefix>()
+            .expect("too few bytes for TCP header");
+        // create a 0-byte slice for the options since we don't support
+        // serializing options yet (NET-955)
+        let options =
+            Options::parse(&mut [][..]).expect("parsing an empty options slice should not fail");
+        let mut segment = TcpSegment {
+            hdr_prefix,
+            options,
+            body,
         };
 
-        buffer.extend_backwards(extend_backwards);
+        NetworkEndian::write_u16(&mut segment.hdr_prefix.src_port, self.src_port);
+        NetworkEndian::write_u16(&mut segment.hdr_prefix.dst_port, self.dst_port);
+        NetworkEndian::write_u32(&mut segment.hdr_prefix.seq_num, self.seq_num);
+        NetworkEndian::write_u32(&mut segment.hdr_prefix.ack, self.ack_num);
+        // Data Offset is hard-coded to 5 until we support serializing options
+        NetworkEndian::write_u16(
+            &mut segment.hdr_prefix.data_offset_reserved_flags,
+            (5u16 << 12) | self.flags,
+        );
+        NetworkEndian::write_u16(&mut segment.hdr_prefix.window_size, self.window_size);
+        // we don't support setting the Urgent Pointer
+        NetworkEndian::write_u16(&mut segment.hdr_prefix.urg_ptr, 0);
+
+        let segment_len = segment.total_segment_len();
+        // This ignores the checksum field in the header, so it's fine that we
+        // haven't set it yet, and so it could be filled with arbitrary bytes.
+        let checksum = segment
+            .compute_checksum(self.src_ip, self.dst_ip)
+            .expect(&format!(
+                "total TCP segment length of {} bytes overflows length field of pseudo-header",
+                segment_len
+            ));
+        NetworkEndian::write_u16(&mut segment.hdr_prefix.checksum, checksum);
     }
 }
 
@@ -494,12 +510,13 @@
 
 #[cfg(test)]
 mod tests {
+    use packet::{Buf, BufferSerializer, ParseBuffer, Serializer};
+
     use super::*;
     use crate::device::ethernet::EtherType;
     use crate::ip::{IpProto, Ipv4Addr, Ipv6Addr};
     use crate::wire::ethernet::EthernetFrame;
     use crate::wire::ipv4::Ipv4Packet;
-    use crate::wire::util::SerializationRequest;
 
     const TEST_SRC_IPV4: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]);
     const TEST_DST_IPV4: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]);
@@ -513,14 +530,14 @@
     fn test_parse_serialize_full() {
         use crate::wire::testdata::tls_client_hello::*;
 
-        let (frame, body_range) = EthernetFrame::parse(ETHERNET_FRAME_BYTES).unwrap();
-        assert_eq!(body_range, ETHERNET_BODY_RANGE);
+        let mut buf = &ETHERNET_FRAME_BYTES[..];
+        let frame = buf.parse::<EthernetFrame<_>>().unwrap();
         assert_eq!(frame.src_mac(), ETHERNET_SRC_MAC);
         assert_eq!(frame.dst_mac(), ETHERNET_DST_MAC);
         assert_eq!(frame.ethertype(), Some(Ok(EtherType::Ipv4)));
 
-        let (packet, body_range) = Ipv4Packet::parse(frame.body()).unwrap();
-        assert_eq!(body_range, IP_BODY_RANGE);
+        let mut body = frame.body();
+        let packet = body.parse::<Ipv4Packet<_>>().unwrap();
         assert_eq!(packet.proto(), Ok(IpProto::Tcp));
         assert_eq!(packet.dscp(), IP_DSCP);
         assert_eq!(packet.ecn(), IP_ECN);
@@ -532,9 +549,10 @@
         assert_eq!(packet.src_ip(), IP_SRC_IP);
         assert_eq!(packet.dst_ip(), IP_DST_IP);
 
-        let (segment, body_range) =
-            TcpSegment::parse(packet.body(), packet.src_ip(), packet.dst_ip()).unwrap();
-        assert_eq!(body_range, TCP_BODY_RANGE);
+        let mut body = packet.body();
+        let segment = body
+            .parse_with::<_, TcpSegment<_>>(TcpParseArgs::new(packet.src_ip(), packet.dst_ip()))
+            .unwrap();
         assert_eq!(segment.src_port().get(), TCP_SRC_PORT);
         assert_eq!(segment.dst_port().get(), TCP_DST_PORT);
         assert_eq!(segment.ack_num().is_some(), TCP_ACK_FLAG);
@@ -548,9 +566,9 @@
 
         // TODO(joshlf): Uncomment once we support serializing options
         // let buffer = segment.body()
-        //     .encapsulate(segment.serializer(packet.src_ip(), packet.dst_ip()))
-        //     .encapsulate(packet.serializer())
-        //     .encapsulate(frame.serializer())
+        //     .encapsulate(segment.builder(packet.src_ip(), packet.dst_ip()))
+        //     .encapsulate(packet.builder())
+        //     .encapsulate(frame.builder())
         //     .serialize_outer();
         // assert_eq!(buffer.as_ref(), ETHERNET_FRAME_BYTES);
     }
@@ -579,10 +597,10 @@
 
     #[test]
     fn test_parse() {
-        let bytes = hdr_prefix_to_bytes(new_hdr_prefix());
-        let (segment, body_range) =
-            TcpSegment::parse(&bytes[..], TEST_SRC_IPV4, TEST_DST_IPV4).unwrap();
-        assert_eq!(body_range, 20..20);
+        let mut buf = &hdr_prefix_to_bytes(new_hdr_prefix())[..];
+        let segment = buf
+            .parse_with::<_, TcpSegment<_>>(TcpParseArgs::new(TEST_SRC_IPV4, TEST_DST_IPV4))
+            .unwrap();
         assert_eq!(segment.src_port().get(), 1);
         assert_eq!(segment.dst_port().get(), 2);
         assert_eq!(segment.body(), []);
@@ -590,64 +608,42 @@
 
     #[test]
     fn test_parse_error() {
+        // Assert that parsing a particular header prefix results in an error.
+        fn assert_header_err(hdr_prefix: HeaderPrefix, err: ParseError) {
+            let mut buf = &mut hdr_prefix_to_bytes(hdr_prefix)[..];
+            assert_eq!(
+                buf.parse_with::<_, TcpSegment<_>>(TcpParseArgs::new(TEST_SRC_IPV4, TEST_DST_IPV4))
+                    .unwrap_err(),
+                err
+            );
+        }
+
         // Set the source port to 0, which is illegal.
         let mut hdr_prefix = new_hdr_prefix();
         hdr_prefix.src_port = [0, 0];
-        assert_eq!(
-            TcpSegment::parse(
-                &hdr_prefix_to_bytes(hdr_prefix)[..],
-                TEST_SRC_IPV4,
-                TEST_DST_IPV4
-            )
-            .unwrap_err(),
-            ParseError::Format
-        );
+        assert_header_err(hdr_prefix, ParseError::Format);
 
         // Set the destination port to 0, which is illegal.
         let mut hdr_prefix = new_hdr_prefix();
         hdr_prefix.dst_port = [0, 0];
-        assert_eq!(
-            TcpSegment::parse(
-                &hdr_prefix_to_bytes(hdr_prefix)[..],
-                TEST_SRC_IPV4,
-                TEST_DST_IPV4
-            )
-            .unwrap_err(),
-            ParseError::Format
-        );
+        assert_header_err(hdr_prefix, ParseError::Format);
 
         // Set the data offset to 4, implying a header length of 16. This is
         // smaller than the minimum of 20.
         let mut hdr_prefix = new_hdr_prefix();
         NetworkEndian::write_u16(&mut hdr_prefix.data_offset_reserved_flags, 4u16 << 12);
-        assert_eq!(
-            TcpSegment::parse(
-                &hdr_prefix_to_bytes(hdr_prefix)[..],
-                TEST_SRC_IPV4,
-                TEST_DST_IPV4
-            )
-            .unwrap_err(),
-            ParseError::Format
-        );
+        assert_header_err(hdr_prefix, ParseError::Format);
 
         // Set the data offset to 6, implying a header length of 24. This is
         // larger than the actual segment length of 20.
         let mut hdr_prefix = new_hdr_prefix();
         NetworkEndian::write_u16(&mut hdr_prefix.data_offset_reserved_flags, 6u16 << 12);
-        assert_eq!(
-            TcpSegment::parse(
-                &hdr_prefix_to_bytes(hdr_prefix)[..],
-                TEST_SRC_IPV4,
-                TEST_DST_IPV4
-            )
-            .unwrap_err(),
-            ParseError::Format
-        );
+        assert_header_err(hdr_prefix, ParseError::Format);
     }
 
-    // Return a stock TcpSegmentSerializer with reasonable default values.
-    fn new_serializer<A: IpAddr>(src_ip: A, dst_ip: A) -> TcpSegmentSerializer<A> {
-        TcpSegmentSerializer::new(
+    // Return a stock TcpSegmentBuilder with reasonable default values.
+    fn new_builder<A: IpAddr>(src_ip: A, dst_ip: A) -> TcpSegmentBuilder<A> {
+        TcpSegmentBuilder::new(
             src_ip,
             dst_ip,
             NonZeroU16::new(1).unwrap(),
@@ -660,32 +656,27 @@
 
     #[test]
     fn test_serialize() {
-        let mut buf = [0; 30];
-        let mut serializer = new_serializer(TEST_SRC_IPV4, TEST_DST_IPV4);
-        serializer.fin(true);
-        serializer.rst(true);
-        serializer.syn(true);
-        {
-            // set the body
-            (&mut buf[20..]).copy_from_slice(&[0, 1, 2, 3, 3, 4, 5, 7, 8, 9]);
-            let mut buffer = BufferAndRange::new_from(&mut buf[..], 20..);
-            serializer.serialize(&mut buffer);
-            assert_eq!(buffer.range(), 0..30);
-        }
+        let mut builder = new_builder(TEST_SRC_IPV4, TEST_DST_IPV4);
+        builder.fin(true);
+        builder.rst(true);
+        builder.syn(true);
 
+        let mut buf = (&[0, 1, 2, 3, 3, 4, 5, 7, 8, 9])
+            .encapsulate(builder)
+            .serialize_outer();
         // assert that we get the literal bytes we expected
         assert_eq!(
-            buf,
+            buf.as_ref(),
             [
                 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 80, 23, 0, 5, 141, 137, 0, 0, 0, 1, 2, 3, 3, 4,
                 5, 7, 8, 9
             ]
         );
-        let (segment, body_range) =
-            TcpSegment::parse(&buf[..], TEST_SRC_IPV4, TEST_DST_IPV4).unwrap();
+        let segment = buf
+            .parse_with::<_, TcpSegment<_>>(TcpParseArgs::new(TEST_SRC_IPV4, TEST_DST_IPV4))
+            .unwrap();
         // assert that when we parse those bytes, we get the values we set in
-        // the serializer
-        assert_eq!(body_range, 20..30);
+        // the builder
         assert_eq!(segment.src_port().get(), 1);
         assert_eq!(segment.dst_port().get(), 2);
         assert_eq!(segment.seq_num(), 3);
@@ -696,33 +687,17 @@
 
     #[test]
     fn test_serialize_zeroes() {
-        // Test that TcpSegmentSerializer::serialize properly zeroes memory before
+        // Test that TcpSegmentBuilder::serialize properly zeroes memory before
         // serializing the header.
         let mut buf_0 = [0; 20];
-        new_serializer(TEST_SRC_IPV4, TEST_DST_IPV4)
-            .serialize(&mut BufferAndRange::new_from(&mut buf_0[..], 20..));
+        BufferSerializer::new_vec(Buf::new(&mut buf_0[..], 20..))
+            .encapsulate(new_builder(TEST_SRC_IPV4, TEST_DST_IPV4))
+            .serialize_outer();
         let mut buf_1 = [0xFF; 20];
-        new_serializer(TEST_SRC_IPV4, TEST_DST_IPV4)
-            .serialize(&mut BufferAndRange::new_from(&mut buf_1[..], 20..));
-        assert_eq!(buf_0, buf_1);
-    }
-
-    #[test]
-    #[should_panic]
-    fn test_serialize_panic_body_range() {
-        // Test that a body range which is out of bounds of the buffer is
-        // rejected.
-        new_serializer(TEST_SRC_IPV4, TEST_DST_IPV4)
-            .serialize(&mut BufferAndRange::new_from(&mut [0; 20][..], ..21));
-    }
-
-    #[test]
-    #[should_panic]
-    fn test_serialize_panic_insufficient_header_space() {
-        // Test that a body range which doesn't leave enough room for the header
-        // is rejected.
-        new_serializer(TEST_SRC_IPV4, TEST_DST_IPV4)
-            .serialize(&mut BufferAndRange::new_from(&mut [0; 20][..], ..));
+        BufferSerializer::new_vec(Buf::new(&mut buf_1[..], 20..))
+            .encapsulate(new_builder(TEST_SRC_IPV4, TEST_DST_IPV4))
+            .serialize_outer();
+        assert_eq!(&buf_0[..], &buf_1[..]);
     }
 
     #[test]
@@ -730,8 +705,9 @@
     fn test_serialize_panic_segment_too_long_ipv4() {
         // Test that a segment length which overflows u16 is rejected because it
         // can't fit in the length field in the IPv4 pseudo-header.
-        new_serializer(TEST_SRC_IPV4, TEST_DST_IPV4)
-            .serialize(&mut BufferAndRange::new_from(&mut [0; 1 << 16][..], 20..));
+        BufferSerializer::new_vec(Buf::new(&mut [0; (1 << 16) - 20][..], ..))
+            .encapsulate(new_builder(TEST_SRC_IPV4, TEST_DST_IPV4))
+            .serialize_outer();
     }
 
     #[test]
@@ -741,7 +717,8 @@
     fn test_serialize_panic_segment_too_long_ipv6() {
         // Test that a segment length which overflows u32 is rejected because it
         // can't fit in the length field in the IPv4 pseudo-header.
-        new_serializer(TEST_SRC_IPV6, TEST_DST_IPV6)
-            .serialize(&mut BufferAndRange::new_from(&mut [0; 1 << 32][..], 20..));
+        BufferSerializer::new_vec(Buf::new(&mut [0; (1 << 32) - 20][..], ..))
+            .encapsulate(new_builder(TEST_SRC_IPV6, TEST_DST_IPV6))
+            .serialize_outer();
     }
 }
diff --git a/bin/recovery_netstack/core/src/wire/udp.rs b/bin/recovery_netstack/core/src/wire/udp.rs
index 3148863..812f8f8 100644
--- a/bin/recovery_netstack/core/src/wire/udp.rs
+++ b/bin/recovery_netstack/core/src/wire/udp.rs
@@ -7,14 +7,16 @@
 #[cfg(test)]
 use std::fmt::{self, Debug, Formatter};
 use std::num::NonZeroU16;
-use std::ops::Range;
 
 use byteorder::{ByteOrder, NetworkEndian};
+use packet::{
+    BufferView, BufferViewMut, PacketBuilder, ParsablePacket, ParseMetadata, SerializeBuffer,
+};
 use zerocopy::{AsBytes, ByteSlice, FromBytes, LayoutVerified, Unaligned};
 
 use crate::error::ParseError;
 use crate::ip::{Ip, IpAddr, IpProto};
-use crate::wire::util::{fits_in_u16, fits_in_u32, BufferAndRange, Checksum, PacketSerializer};
+use crate::wire::util::{fits_in_u16, fits_in_u32, Checksum};
 
 // Header has the same memory layout (thanks to repr(C, packed)) as a UDP
 // header. Thus, we can simply reinterpret the bytes of the UDP header as a
@@ -85,44 +87,48 @@
     body: B,
 }
 
-impl<B: ByteSlice> UdpPacket<B> {
-    /// Parse a UDP packet.
-    ///
-    /// `parse` parses `bytes` as a UDP packet and validates the checksum. It
-    /// returns the byte range corresponding to the body within `bytes`. This
-    /// can be useful when extracting the encapsulated payload to send to
-    /// another layer of the stack.
-    ///
-    /// `src_ip` is the source address in the IP header. In IPv4, `dst_ip` is
-    /// the destination address in the IPv4 header. In IPv6, it's more
-    /// complicated:
-    /// - If there's no routing header, the destination is the one in the IPv6
-    ///   header.
-    /// - If there is a routing header, then the sender will compute the
-    ///   checksum using the last address in the routing header, while the
-    ///   receiver will compute the checksum using the destination address in
-    ///   the IPv6 header.
-    pub fn parse<A: IpAddr>(
-        bytes: B, src_ip: A, dst_ip: A,
-    ) -> Result<(UdpPacket<B>, Range<usize>), ParseError> {
+/// Arguments required to parse a UDP packet.
+pub struct UdpParseArgs<A: IpAddr> {
+    src_ip: A,
+    dst_ip: A,
+}
+
+impl<A: IpAddr> UdpParseArgs<A> {
+    /// Construct a new `UdpParseArgs`.
+    pub fn new(src_ip: A, dst_ip: A) -> UdpParseArgs<A> {
+        UdpParseArgs { src_ip, dst_ip }
+    }
+}
+
+impl<B: ByteSlice, A: IpAddr> ParsablePacket<B, UdpParseArgs<A>> for UdpPacket<B> {
+    type Error = ParseError;
+
+    fn parse_metadata(&self) -> ParseMetadata {
+        ParseMetadata::from_packet(self.header.bytes().len(), self.body.len(), 0)
+    }
+
+    fn parse<BV: BufferView<B>>(mut buffer: BV, args: UdpParseArgs<A>) -> Result<Self, ParseError> {
         // See for details: https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure
 
-        let bytes_len = bytes.len();
-        let (header, body) =
-            LayoutVerified::<B, Header>::new_unaligned_from_prefix(bytes).ok_or_else(
-                debug_err_fn!(ParseError::Format, "too few bytes for header"),
-            )?;
-        let packet = UdpPacket { header, body };
+        let buf_len = buffer.len();
+        let header = buffer.take_obj_front::<Header>().ok_or_else(debug_err_fn!(
+            ParseError::Format,
+            "too few bytes for header"
+        ))?;
+        let packet = UdpPacket {
+            header,
+            body: buffer.into_rest(),
+        };
         let len = if packet.header.length() == 0 && A::Version::VERSION.is_v6() {
             // IPv6 supports jumbograms, so a UDP packet may be greater than
             // 2^16 bytes in size. In this case, the size doesn't fit in the
             // 16-bit length field in the header, and so the length field is set
             // to zero to indicate this.
-            bytes_len
+            buf_len
         } else {
             packet.header.length() as usize
         };
-        if len != bytes_len {
+        if len != buf_len {
             return debug_err!(
                 Err(ParseError::Format),
                 "length in header does not match packet length"
@@ -143,7 +149,7 @@
                 NetworkEndian::read_u16(&packet.header.checksum)
             };
             if packet
-                .compute_checksum(src_ip, dst_ip)
+                .compute_checksum(args.src_ip, args.dst_ip)
                 .ok_or_else(debug_err_fn!(ParseError::Format, "segment too large"))?
                 != target
             {
@@ -152,10 +158,7 @@
         } else if A::Version::VERSION.is_v6() {
             return debug_err!(Err(ParseError::Format), "missing checksum");
         }
-
-        let hdr_len = packet.header.bytes().len();
-        let total_len = hdr_len + packet.body.len();
-        Ok((packet, hdr_len..total_len))
+        Ok(packet)
     }
 }
 
@@ -229,9 +232,9 @@
         self.header_len() + self.body.len()
     }
 
-    /// Construct a serializer with the same contents as this packet.
-    pub fn serializer<A: IpAddr>(&self, src_ip: A, dst_ip: A) -> UdpPacketSerializer<A> {
-        UdpPacketSerializer {
+    /// Construct a builder with the same contents as this packet.
+    pub fn builder<A: IpAddr>(&self, src_ip: A, dst_ip: A) -> UdpPacketBuilder<A> {
+        UdpPacketBuilder {
             src_ip,
             dst_ip,
             src_port: self.src_port(),
@@ -242,24 +245,24 @@
 
 // NOTE(joshlf): In order to ensure that the checksum is always valid, we don't
 // expose any setters for the fields of the UDP packet; the only way to set them
-// is via UdpPacketSerializer::serialize. This, combined with checksum validation
+// is via UdpPacketBuilder::serialize. This, combined with checksum validation
 // performed in UdpPacket::parse, provides the invariant that a UdpPacket always
 // has a valid checksum.
 
-/// A serializer for UDP packets.
-pub struct UdpPacketSerializer<A: IpAddr> {
+/// A builder for UDP packets.
+pub struct UdpPacketBuilder<A: IpAddr> {
     src_ip: A,
     dst_ip: A,
     src_port: Option<NonZeroU16>,
     dst_port: NonZeroU16,
 }
 
-impl<A: IpAddr> UdpPacketSerializer<A> {
-    /// Construct a new `UdpPacketSerializer`.
+impl<A: IpAddr> UdpPacketBuilder<A> {
+    /// Construct a new `UdpPacketBuilder`.
     pub fn new(
         src_ip: A, dst_ip: A, src_port: Option<NonZeroU16>, dst_port: NonZeroU16,
-    ) -> UdpPacketSerializer<A> {
-        UdpPacketSerializer {
+    ) -> UdpPacketBuilder<A> {
+        UdpPacketBuilder {
             src_ip,
             dst_ip,
             src_port,
@@ -270,71 +273,71 @@
 
 const HEADER_BYTES: usize = 8;
 
-impl<A: IpAddr> PacketSerializer for UdpPacketSerializer<A> {
-    fn max_header_bytes(&self) -> usize {
+impl<A: IpAddr> PacketBuilder for UdpPacketBuilder<A> {
+    fn header_len(&self) -> usize {
         HEADER_BYTES
     }
 
-    fn min_header_bytes(&self) -> usize {
-        HEADER_BYTES
+    fn min_body_len(&self) -> usize {
+        0
     }
 
-    fn serialize<B: AsRef<[u8]> + AsMut<[u8]>>(self, buffer: &mut BufferAndRange<B>) {
+    fn footer_len(&self) -> usize {
+        0
+    }
+
+    fn serialize<'a>(self, mut buffer: SerializeBuffer<'a>) {
         // See for details: https://en.wikipedia.org/wiki/User_Datagram_Protocol#Packet_structure
 
-        let extend_backwards = {
-            let (header, body, _) = buffer.parts_mut();
-            // SECURITY: Use _zeroed constructor to ensure we zero memory to prevent
-            // leaking information from packets previously stored in this buffer.
-            let (_, header) = LayoutVerified::<_, Header>::new_unaligned_from_suffix_zeroed(header)
-                .expect("too few bytes for UDP header");
-            let mut packet = UdpPacket { header, body };
+        let (mut header, body, _) = buffer.parts();
+        // implements BufferViewMut, giving us take_obj_xxx_zero methods
+        let mut header = &mut header;
 
-            packet
-                .header
-                .set_src_port(self.src_port.map(|port| port.get()).unwrap_or(0));
-            packet.header.set_dst_port(self.dst_port.get());
-            let total_len = packet.total_packet_len();
-            let len_field = if fits_in_u16(total_len) {
-                total_len as u16
-            } else if A::Version::VERSION.is_v6() {
-                // IPv6 supports jumbograms, so a UDP packet may be greater than
-                // 2^16 bytes in size. In this case, the size doesn't fit in the
-                // 16-bit length field in the header, and so the length field is set
-                // to zero to indicate this.
-                0u16
-            } else {
-                panic!(
-                    "total UDP packet length of {} bytes overflows 16-bit length field of UDP \
-                     header",
-                    total_len
-                );
-            };
-            NetworkEndian::write_u16(&mut packet.header.length, len_field);
+        // SECURITY: Use _zero constructor to ensure we zero memory to prevent
+        // leaking information from packets previously stored in this buffer.
+        let header = header
+            .take_obj_front_zero::<Header>()
+            .expect("too few bytes for UDP header");
+        let mut packet = UdpPacket { header, body };
 
-            // This ignores the checksum field in the header, so it's fine that we
-            // haven't set it yet, and so it could be filled with arbitrary bytes.
-            let c = packet
-                .compute_checksum(self.src_ip, self.dst_ip)
-                .expect(&format!(
-                    "total UDP packet length of {} bytes overflow 32-bit length field of \
-                     pseudo-header",
-                    total_len
-                ));
-            NetworkEndian::write_u16(
-                &mut packet.header.checksum,
-                if c == 0 {
-                    // When computing the checksum, a checksum of 0 is sent as 0xFFFF.
-                    0xFFFF
-                } else {
-                    c
-                },
+        packet
+            .header
+            .set_src_port(self.src_port.map(|port| port.get()).unwrap_or(0));
+        packet.header.set_dst_port(self.dst_port.get());
+        let total_len = packet.total_packet_len();
+        let len_field = if fits_in_u16(total_len) {
+            total_len as u16
+        } else if A::Version::VERSION.is_v6() {
+            // IPv6 supports jumbograms, so a UDP packet may be greater than
+            // 2^16 bytes in size. In this case, the size doesn't fit in the
+            // 16-bit length field in the header, and so the length field is set
+            // to zero to indicate this.
+            0u16
+        } else {
+            panic!(
+                "total UDP packet length of {} bytes overflows 16-bit length field of UDP header",
+                total_len
             );
-
-            packet.header_len()
         };
+        NetworkEndian::write_u16(&mut packet.header.length, len_field);
 
-        buffer.extend_backwards(extend_backwards);
+        // This ignores the checksum field in the header, so it's fine that we
+        // haven't set it yet, and so it could be filled with arbitrary bytes.
+        let c = packet
+            .compute_checksum(self.src_ip, self.dst_ip)
+            .expect(&format!(
+                "total UDP packet length of {} bytes overflow 32-bit length field of pseudo-header",
+                total_len
+            ));
+        NetworkEndian::write_u16(
+            &mut packet.header.checksum,
+            if c == 0 {
+                // When computing the checksum, a checksum of 0 is sent as 0xFFFF.
+                0xFFFF
+            } else {
+                c
+            },
+        );
     }
 }
 
@@ -348,13 +351,13 @@
 
 #[cfg(test)]
 mod tests {
+    use packet::{Buf, BufferSerializer, ParseBuffer, Serializer};
+
+    use super::*;
     use crate::device::ethernet::EtherType;
     use crate::ip::{Ipv4Addr, Ipv6Addr};
     use crate::wire::ethernet::EthernetFrame;
     use crate::wire::ipv4::Ipv4Packet;
-    use crate::wire::util::SerializationRequest;
-
-    use super::*;
 
     const TEST_SRC_IPV4: Ipv4Addr = Ipv4Addr::new([1, 2, 3, 4]);
     const TEST_DST_IPV4: Ipv4Addr = Ipv4Addr::new([5, 6, 7, 8]);
@@ -368,14 +371,14 @@
     fn test_parse_serialize_full() {
         use crate::wire::testdata::dns_request::*;
 
-        let (frame, body_range) = EthernetFrame::parse(ETHERNET_FRAME_BYTES).unwrap();
-        assert_eq!(body_range, ETHERNET_BODY_RANGE);
+        let mut buf = &ETHERNET_FRAME_BYTES[..];
+        let frame = buf.parse::<EthernetFrame<_>>().unwrap();
         assert_eq!(frame.src_mac(), ETHERNET_SRC_MAC);
         assert_eq!(frame.dst_mac(), ETHERNET_DST_MAC);
         assert_eq!(frame.ethertype(), Some(Ok(EtherType::Ipv4)));
 
-        let (ip_packet, body_range) = Ipv4Packet::parse(frame.body()).unwrap();
-        assert_eq!(body_range, IP_BODY_RANGE);
+        let mut body = frame.body();
+        let ip_packet = body.parse::<Ipv4Packet<_>>().unwrap();
         assert_eq!(ip_packet.proto(), Ok(IpProto::Udp));
         assert_eq!(ip_packet.dscp(), IP_DSCP);
         assert_eq!(ip_packet.ecn(), IP_ECN);
@@ -387,9 +390,13 @@
         assert_eq!(ip_packet.src_ip(), IP_SRC_IP);
         assert_eq!(ip_packet.dst_ip(), IP_DST_IP);
 
-        let (udp_packet, body_range) =
-            UdpPacket::parse(ip_packet.body(), ip_packet.src_ip(), ip_packet.dst_ip()).unwrap();
-        assert_eq!(body_range, UDP_BODY_RANGE);
+        let mut body = ip_packet.body();
+        let udp_packet = body
+            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(
+                ip_packet.src_ip(),
+                ip_packet.dst_ip(),
+            ))
+            .unwrap();
         assert_eq!(
             udp_packet.src_port().map(|p| p.get()).unwrap_or(0),
             UDP_SRC_PORT
@@ -399,9 +406,9 @@
 
         let buffer = udp_packet
             .body()
-            .encapsulate(udp_packet.serializer(ip_packet.src_ip(), ip_packet.dst_ip()))
-            .encapsulate(ip_packet.serializer())
-            .encapsulate(frame.serializer())
+            .encapsulate(udp_packet.builder(ip_packet.src_ip(), ip_packet.dst_ip()))
+            .encapsulate(ip_packet.builder())
+            .encapsulate(frame.builder())
             .serialize_outer();
         assert_eq!(buffer.as_ref(), ETHERNET_FRAME_BYTES);
     }
@@ -409,20 +416,20 @@
     #[test]
     fn test_parse() {
         // source port of 0 (meaning none) is allowed, as is a missing checksum
-        let buf = [0, 0, 1, 2, 0, 8, 0, 0];
-        let (packet, body_range) =
-            UdpPacket::parse(&buf[..], TEST_SRC_IPV4, TEST_DST_IPV4).unwrap();
-        assert_eq!(body_range, 8..8);
+        let mut buf = &[0, 0, 1, 2, 0, 8, 0, 0][..];
+        let packet = buf
+            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(TEST_SRC_IPV4, TEST_DST_IPV4))
+            .unwrap();
         assert!(packet.src_port().is_none());
         assert_eq!(packet.dst_port().get(), NetworkEndian::read_u16(&[1, 2]));
         assert!(!packet.checksummed());
         assert!(packet.body().is_empty());
 
         // length of 0 is allowed in IPv6
-        let buf = [0, 0, 1, 2, 0, 0, 0xFD, 0xD3];
-        let (packet, body_range) =
-            UdpPacket::parse(&buf[..], TEST_SRC_IPV6, TEST_DST_IPV6).unwrap();
-        assert_eq!(body_range, 8..8);
+        let mut buf = &[0, 0, 1, 2, 0, 0, 0xFD, 0xD3][..];
+        let packet = buf
+            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(TEST_SRC_IPV6, TEST_DST_IPV6))
+            .unwrap();
         assert!(packet.src_port().is_none());
         assert_eq!(packet.dst_port().get(), NetworkEndian::read_u16(&[1, 2]));
         assert!(packet.checksummed());
@@ -431,23 +438,20 @@
 
     #[test]
     fn test_serialize() {
-        let mut buf = [0; 8];
-        {
-            let mut buffer = BufferAndRange::new_from(&mut buf, 8..);
-            UdpPacketSerializer::new(
+        let mut buf = (&[])
+            .encapsulate(UdpPacketBuilder::new(
                 TEST_SRC_IPV4,
                 TEST_DST_IPV4,
                 NonZeroU16::new(1),
                 NonZeroU16::new(2).unwrap(),
-            )
-            .serialize(&mut buffer);
-            assert_eq!(buffer.range(), 0..8);
-        }
-        // assert that we get the literal bytes we expected
-        assert_eq!(buf, [0, 1, 0, 2, 0, 8, 239, 199]);
-        let (packet, _) = UdpPacket::parse(&buf[..], TEST_SRC_IPV4, TEST_DST_IPV4).unwrap();
+            ))
+            .serialize_outer();
+        assert_eq!(buf.as_ref(), [0, 1, 0, 2, 0, 8, 239, 199]);
+        let packet = buf
+            .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(TEST_SRC_IPV4, TEST_DST_IPV4))
+            .unwrap();
         // assert that when we parse those bytes, we get the values we set in
-        // the serializer
+        // the builder
         assert_eq!(packet.src_port().unwrap().get(), 1);
         assert_eq!(packet.dst_port().get(), 2);
         assert!(packet.checksummed());
@@ -458,27 +462,29 @@
         // Test that UdpPacket::serialize properly zeroes memory before serializing
         // the header.
         let mut buf_0 = [0; 8];
-        UdpPacketSerializer::new(
-            TEST_SRC_IPV4,
-            TEST_DST_IPV4,
-            NonZeroU16::new(1),
-            NonZeroU16::new(2).unwrap(),
-        )
-        .serialize(&mut BufferAndRange::new_from(&mut buf_0[..], 8..));
+        BufferSerializer::new_vec(Buf::new(&mut buf_0[..], 8..))
+            .encapsulate(UdpPacketBuilder::new(
+                TEST_SRC_IPV4,
+                TEST_DST_IPV4,
+                NonZeroU16::new(1),
+                NonZeroU16::new(2).unwrap(),
+            ))
+            .serialize_outer();
         let mut buf_1 = [0xFF; 8];
-        UdpPacketSerializer::new(
-            TEST_SRC_IPV4,
-            TEST_DST_IPV4,
-            NonZeroU16::new(1),
-            NonZeroU16::new(2).unwrap(),
-        )
-        .serialize(&mut BufferAndRange::new_from(&mut buf_1[..], 8..));
+        BufferSerializer::new_vec(Buf::new(&mut buf_1[..], 8..))
+            .encapsulate(UdpPacketBuilder::new(
+                TEST_SRC_IPV4,
+                TEST_DST_IPV4,
+                NonZeroU16::new(1),
+                NonZeroU16::new(2).unwrap(),
+            ))
+            .serialize_outer();
         assert_eq!(buf_0, buf_1);
     }
 
     #[test]
     fn test_parse_fail() {
-        // Test that while a given byte pattern optionally succeeds, zeroing out
+        // Test thact while a given byte pattern optionally succeeds, zeroing out
         // certain bytes causes failure. `zero` is a list of byte indices to
         // zero out that should cause failure.
         fn test_zero<I: IpAddr>(src: I, dst: I, succeeds: bool, zero: &[usize], err: ParseError) {
@@ -486,12 +492,20 @@
             // IPv6, this /is/ the test.
             let mut buf = [1, 2, 3, 4, 0, 8, 0, 0];
             if succeeds {
-                assert!(UdpPacket::parse(&buf[..], src, dst).is_ok());
+                let mut buf = &buf[..];
+                assert!(buf
+                    .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(src, dst))
+                    .is_ok());
             }
             for idx in zero {
                 buf[*idx] = 0;
             }
-            assert_eq!(UdpPacket::parse(&buf[..], src, dst).unwrap_err(), err);
+            let mut buf = &buf[..];
+            assert_eq!(
+                buf.parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(src, dst))
+                    .unwrap_err(),
+                err
+            );
         }
 
         // destination port of 0 is disallowed
@@ -521,7 +535,9 @@
             let mut buf = vec![0u8; 1 << 32];
             (&mut buf[..8]).copy_from_slice(&[0, 0, 1, 2, 0, 0, 0xFF, 0xE4]);
             assert_eq!(
-                UdpPacket::parse(&buf[..], TEST_SRC_IPV6, TEST_DST_IPV6).unwrap_err(),
+                (&buf[..])
+                    .parse_with::<_, UdpPacket<_>>(UdpParseArgs::new(TEST_SRC_IPV6, TEST_DST_IPV6))
+                    .unwrap_err(),
                 ParseError::Format
             );
         }
@@ -530,43 +546,46 @@
     #[test]
     #[should_panic]
     fn test_serialize_fail_header_too_short() {
-        let mut buf = [0; 7];
-        UdpPacketSerializer::new(
+        UdpPacketBuilder::new(
             TEST_SRC_IPV4,
             TEST_DST_IPV4,
             None,
             NonZeroU16::new(1).unwrap(),
         )
-        .serialize(&mut BufferAndRange::new_from(&mut buf[..], 7..));
+        .serialize(SerializeBuffer::new(&mut [0; 7][..], ..));
     }
 
     #[test]
     #[should_panic]
     fn test_serialize_fail_packet_too_long_ipv4() {
-        let mut buf = [0; 1 << 16];
-        UdpPacketSerializer::new(
-            TEST_SRC_IPV4,
-            TEST_DST_IPV4,
-            None,
-            NonZeroU16::new(1).unwrap(),
-        )
-        .serialize(&mut BufferAndRange::new_from(&mut buf[..], 8..));
+        (&[0; (1 << 16) - 8][..])
+            .encapsulate(UdpPacketBuilder::new(
+                TEST_SRC_IPV4,
+                TEST_DST_IPV4,
+                None,
+                NonZeroU16::new(1).unwrap(),
+            ))
+            .serialize_outer();
     }
 
-    // This test tries to allocate 4GB of memory. Run at your own risk.
-    #[test]
-    #[should_panic]
-    #[ignore]
-    #[cfg(target_pointer_width = "64")] // 2^32 overflows on 32-bit platforms
-    fn test_serialize_fail_packet_too_long_ipv6() {
-        // total length of 2^32 or greater is disallowed in IPv6
-        let mut buf = vec![0u8; 1 << 32];
-        UdpPacketSerializer::new(
-            TEST_SRC_IPV4,
-            TEST_DST_IPV4,
-            None,
-            NonZeroU16::new(1).unwrap(),
-        )
-        .serialize(&mut BufferAndRange::new_from(&mut buf[..], 8..));
-    }
+    // TODO(joshlf): Figure out why compiling this test (yes, just compiling!)
+    // hangs the compiler.
+
+    // // This test tries to allocate 4GB of memory. Run at your own risk.
+    // #[test]
+    // #[should_panic]
+    // #[ignore]
+    // #[cfg(target_pointer_width = "64")] // 2^32 overflows on 32-bit platforms
+    // fn test_serialize_fail_packet_too_long_ipv6() {
+    //     // total length of 2^32 or greater is disallowed in IPv6
+    //     let mut buf = vec![0u8; 1 << 32];
+    //     (&[0u8; (1 << 32) - 8])
+    //         .encapsulate(UdpPacketBuilder::new(
+    //             TEST_SRC_IPV4,
+    //             TEST_DST_IPV4,
+    //             None,
+    //             NonZeroU16::new(1).unwrap(),
+    //         ))
+    //         .serialize_outer();
+    // }
 }
diff --git a/bin/recovery_netstack/core/src/wire/util.rs b/bin/recovery_netstack/core/src/wire/util.rs
index 960409b..8360161 100644
--- a/bin/recovery_netstack/core/src/wire/util.rs
+++ b/bin/recovery_netstack/core/src/wire/util.rs
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-pub use self::buffer::*;
 pub use self::checksum::*;
 pub use self::options::*;
 
@@ -642,888 +641,3 @@
         }
     }
 }
-
-mod buffer {
-    use std::cmp;
-    use std::convert::TryFrom;
-    use std::ops::{Bound, Range, RangeBounds};
-
-    // return the inclusive equivalent of the bound
-    fn canonicalize_lower_bound(bound: Bound<&usize>) -> usize {
-        match bound {
-            Bound::Included(x) => *x,
-            Bound::Excluded(x) => *x + 1,
-            Bound::Unbounded => 0,
-        }
-    }
-
-    // return the exclusive equivalent of the bound, verifying that it is in
-    // range of len
-    fn canonicalize_upper_bound(len: usize, bound: Bound<&usize>) -> Option<usize> {
-        let bound = match bound {
-            Bound::Included(x) => *x + 1,
-            Bound::Excluded(x) => *x,
-            Bound::Unbounded => len,
-        };
-        if bound > len {
-            return None;
-        }
-        Some(bound)
-    }
-
-    // return the inclusive-exclusive equivalent of the bound, verifying that it
-    // is in range of len, and panicking if it is not or if the range is
-    // nonsensical
-    fn canonicalize_range_infallible<R: RangeBounds<usize>>(len: usize, range: &R) -> Range<usize> {
-        let lower = canonicalize_lower_bound(range.start_bound());
-        let upper = canonicalize_upper_bound(len, range.end_bound()).expect("range out of bounds");
-        assert!(lower <= upper, "invalid range");
-        lower..upper
-    }
-
-    /// A serializer for non-encapsulating packets.
-    ///
-    /// `InnerPacketSerializer` is a serializer for packets which do not
-    /// themselves encapsulate other packets.
-    pub trait InnerPacketSerializer {
-        /// The number of bytes required to serialize this packet.
-        fn size(&self) -> usize;
-
-        /// Serialize a packet in an existing buffer.
-        ///
-        /// `serialize` serializes a packet into `buffer` from the present
-        /// configuration. It serializes starting at the beginning of
-        /// `buffer.range()`, and expects the range to be empty.
-        ///
-        /// `serialize` updates the buffer's range to be equal to the bytes of
-        /// the newly-serialized packet. This range can be used to indicate the
-        /// range for encapsulation in another packet.
-        ///
-        /// # Panics
-        ///
-        /// `serialize` panics if `buffer.range()` is non-empty, or if the
-        /// number of bytes following the range is less than `self.size()`.
-        fn serialize<B: AsRef<[u8]> + AsMut<[u8]>>(self, buffer: &mut BufferAndRange<B>);
-    }
-
-    // TODO(joshlf): Since {max,min}_{header,footer}_bytes are methods on a
-    // PacketSerializer, perhaps we can just require that the PacketSerializer
-    // know exactly how many bytes will be required, and collapse these from
-    // four to two methods? Currently, the justification is that the difference
-    // in bytes is small, and so performing the dynamic calculation may be more
-    // expensive than allocating a few too many bytes, but that assumption may
-    // be worth revisiting.
-
-    /// A serializer for encapsulating packets.
-    ///
-    /// `PacketSerializer` is a serializer for packets which encapsulate other
-    /// packets.
-    pub trait PacketSerializer {
-        /// The maximum number of pre-body bytes consumed by all headers.
-        ///
-        /// By providing at least `max_header_bytes` bytes preceding the body,
-        /// the caller can ensure that a call to `serialize` will not panic.
-        /// Note that the actual number of bytes consumed may be less than this.
-        fn max_header_bytes(&self) -> usize {
-            0
-        }
-
-        /// The minimum number of pre-body bytes consumed by all headers.
-        ///
-        /// `min_header_bytes` returns the minimum number of bytes which are
-        /// guaranteed to be consumed by all pre-body headers. Note that the
-        /// actual number of bytes consumed may be more than this.
-        fn min_header_bytes(&self) -> usize {
-            0
-        }
-
-        /// The minimum size of the body and padding bytes combined.
-        ///
-        /// Some packet formats have minimum length requirements. In order to
-        /// satisfy these requirements, any bodies smaller than a certain
-        /// minimum must be followed by padding bytes.
-        /// `min_body_and_padding_bytes` returns the minimum number of bytes
-        /// which must be consumed by the body and post-body padding.
-        ///
-        /// If a body of fewer bytes than this minimum is to be serialized using
-        /// `serialize`, the caller must provide space for enough padding bytes
-        /// to make up the difference.
-        fn min_body_and_padding_bytes(&self) -> usize {
-            0
-        }
-
-        /// The maximum number of post-body, post-padding bytes consumed by all
-        /// footers.
-        ///
-        /// `max_footer_bytes` returns the number of bytes which must be present
-        /// after all body bytes and all post-body padding bytes in order to
-        /// guarantee enough room to serialize footers. Note that the actual
-        /// number of bytes consumed may be less than this.
-        fn max_footer_bytes(&self) -> usize {
-            0
-        }
-
-        /// The minimum number of post-body, post-padding bytes consumed by all
-        /// footers.
-        ///
-        /// `min_footer_bytes` returns the minimum number of bytes which are
-        /// guaranteed to be consumed by all post-body, post-padding footers.
-        /// Note that the actual number of bytes consumed may be more than this.
-        fn min_footer_bytes(&self) -> usize {
-            0
-        }
-
-        /// Serialize a packet in an existing buffer.
-        ///
-        /// `serialize` serializes a packet into `buffer`, initializing any
-        /// headers and footers from the present configuration. It treats
-        /// `buffer.range()` as the packet body. It uses the last bytes before
-        /// the body to store any headers. It leaves padding as necessary
-        /// following the body, and serializes any footers immediately following
-        /// the padding (if any).
-        ///
-        /// `serialize` updates the buffer's range to be equal to the bytes of
-        /// the newly-serialized packet (including any headers, padding, and
-        /// footers). This range can be used to indicate the range for
-        /// encapsulation in another packet.
-        ///
-        /// # Panics
-        ///
-        /// `serialize` may panics
-        /// - there are fewer than `max_header_bytes` bytes preceding the body
-        /// - there are fewer than `max_footer_bytes` bytes following the body
-        /// - the sum of the body bytes and post-body bytes is less than the sum
-        ///   of `min_body_and_padding_bytes` and `max_footer_bytes` (in other
-        ///   words, the minimum body and padding byte requirement is not met)
-        fn serialize<B: AsRef<[u8]> + AsMut<[u8]>>(self, buffer: &mut BufferAndRange<B>);
-    }
-
-    // TODO(joshlf): Document common patterns with SerializationRequests,
-    // especially using an existing BufferAndRange to forward a just-parsed
-    // packet (this pattern is particularly subtle).
-
-    /// A request to serialize a payload.
-    ///
-    /// A `SerializationRequest` is a request to serialize a packet.
-    /// `SerializationRequest`s can be fulfilled either by serializing the
-    /// packet, or by creating a new `SerializationRequest` which represents
-    /// encapsulating the original packet in another packet, and then satisfying
-    /// the resulting request. `SerializationRequest`s handle all of the logic
-    /// of determining and satisfying header, footer, and padding requirements.
-    ///
-    /// `SerializationRequest` is implemented by the following types:
-    /// - A `BufferAndRange` represents a request to serialize the buffer's
-    ///   range. If a `BufferAndRange` is encapsulated, its range will be used
-    ///   as the payload, and the rest of the buffer will be used for the
-    ///   headers, footers, and padding of the encapsulating packets.
-    /// - An `InnerSerializationRequest` represents a request to serialize an
-    ///   innermost packet - one which doesn't encapsulate any other packets.
-    /// - An `EncapsulatingSerializationRequest` represents a request to
-    ///   serialize a packet which itself encapsulates the packet requested by
-    ///   another, nested `SerializationRequest`.
-    pub trait SerializationRequest: Sized {
-        type Buffer: AsRef<[u8]> + AsMut<[u8]>;
-
-        /// Serialize a packet, fulfilling this request.
-        ///
-        /// `serialize` serializes this request into a buffer, and returns the
-        /// buffer so that encapsulating packets may serialize their headers,
-        /// footers, and padding. The returned buffer's range represents the
-        /// bytes that have been serialized and should be encapsulated by any
-        /// lower layers. The returned buffer is guaranteed to satisfy the
-        /// following requirements:
-        /// - There are at least `header_bytes` bytes preceding the range
-        /// - There are at least `footer_bytes` bytes following the range
-        /// - The range and the bytes following it are at least
-        ///   `min_body_and_padding_bytes + footer_bytes` in length
-        fn serialize(
-            self, header_bytes: usize, min_body_and_padding_bytes: usize, footer_bytes: usize,
-        ) -> BufferAndRange<Self::Buffer>;
-
-        /// Serialize an outermost packet, fulfilling this request.
-        ///
-        /// `serialize_outer` is like `serialize`, except that the returned
-        /// buffer doesn't make any guarantees about how many bytes precede or
-        /// follow the range. It is intended to be called only when the returned
-        /// packet is not going to be further encapsulated.
-        fn serialize_outer(self) -> BufferAndRange<Self::Buffer> {
-            self.serialize(0, 0, 0)
-        }
-
-        /// Construct a new request to encapsulate this packet in another one.
-        ///
-        /// `encapsulate` consumes this request, and returns a new request
-        /// representing the encapsulation of this packet in another one.
-        /// `serializer` is a `PacketSerializer` which will be used to serialize
-        /// the encapsulating packet.
-        fn encapsulate<S: PacketSerializer>(
-            self, serializer: S,
-        ) -> EncapsulatingSerializationRequest<S, Self> {
-            EncapsulatingSerializationRequest {
-                serializer,
-                inner: self,
-            }
-        }
-    }
-
-    impl<I: InnerPacketSerializer> SerializationRequest for I {
-        type Buffer = Vec<u8>;
-
-        fn serialize(
-            self, header_bytes: usize, min_body_and_padding_bytes: usize, footer_bytes: usize,
-        ) -> BufferAndRange<Vec<u8>> {
-            InnerSerializationRequest::new(self).serialize(
-                header_bytes,
-                min_body_and_padding_bytes,
-                footer_bytes,
-            )
-        }
-    }
-
-    impl<'a> SerializationRequest for &'a [u8] {
-        type Buffer = Vec<u8>;
-
-        fn serialize(
-            self, header_bytes: usize, min_body_and_padding_bytes: usize, footer_bytes: usize,
-        ) -> BufferAndRange<Vec<u8>> {
-            // First use BufferAndRange::ensure_prefix_suffix_padding to either
-            // tell us that the current slice satisfies the constraints, or
-            // allocate a new, satisfying Vec for us.
-            let mut buffer = BufferAndRange::new_from(self, ..);
-            buffer.ensure_prefix_suffix_padding(
-                header_bytes,
-                footer_bytes,
-                min_body_and_padding_bytes,
-            );
-
-            // Next, either duplicate the slice (so we have something mutable)
-            // or use the existing Vec.
-            let range = buffer.range();
-            let mut v = match buffer.buffer {
-                RefOrOwned::Owned(v) => v,
-                RefOrOwned::Ref(r) => r.to_vec(),
-            };
-
-            BufferAndRange::new_from(v, range).serialize(
-                header_bytes,
-                min_body_and_padding_bytes,
-                footer_bytes,
-            )
-        }
-    }
-
-    /// A `SerializationRequest` for to serialize an inner packet.
-    ///
-    /// `InnerSerializationRequest` contains an `InnerPacketSerializer` and a
-    /// `BufferAndRange` and implements `SerializationRequest` by serializing
-    /// the serializer into the buffer.
-    pub struct InnerSerializationRequest<S: InnerPacketSerializer, B> {
-        serializer: S,
-        buffer: BufferAndRange<B>,
-    }
-
-    impl<S: InnerPacketSerializer, B> InnerSerializationRequest<S, B>
-    where
-        B: AsRef<[u8]>,
-    {
-        /// Construct a new `InnerSerializationRequest` from a serializer and a
-        /// buffer.
-        pub fn new_with_buffer(serializer: S, buffer: B) -> InnerSerializationRequest<S, B> {
-            InnerSerializationRequest {
-                serializer,
-                buffer: BufferAndRange::new_from(buffer, 0..0),
-            }
-        }
-    }
-
-    impl<S: InnerPacketSerializer> InnerSerializationRequest<S, Vec<u8>> {
-        /// Construct a new `InnerSerializationRequest` from a serializer,
-        /// allocating a new buffer.
-        pub fn new(serializer: S) -> InnerSerializationRequest<S, Vec<u8>> {
-            InnerSerializationRequest {
-                serializer,
-                buffer: BufferAndRange::new_from(vec![], ..),
-            }
-        }
-    }
-
-    impl<S: InnerPacketSerializer, B> SerializationRequest for InnerSerializationRequest<S, B>
-    where
-        B: AsRef<[u8]> + AsMut<[u8]>,
-    {
-        type Buffer = B;
-
-        fn serialize(
-            self, header_bytes: usize, min_body_and_padding_bytes: usize, footer_bytes: usize,
-        ) -> BufferAndRange<B> {
-            let InnerSerializationRequest {
-                serializer,
-                mut buffer,
-            } = self;
-            // Reset the buffer as required by InnerPacketSerializer::serialize.
-            buffer.range = 0..0;
-            // Ensure there's enough room for the packet itself.
-            let min_body_and_padding_bytes =
-                cmp::max(min_body_and_padding_bytes, serializer.size());
-            buffer.ensure_prefix_suffix_padding(
-                header_bytes,
-                footer_bytes,
-                min_body_and_padding_bytes,
-            );
-            serializer.serialize(&mut buffer);
-            buffer
-        }
-    }
-
-    /// A `SerializationRequest` to encapsulate a packet in another packet.
-    ///
-    /// `EncapsulatingSerializationRequest`s can be constructed from existing
-    /// `SerializationRequest`s using the `encapsulate` method.
-    ///
-    /// # Padding
-    ///
-    /// If the `PacketSerializer` used to construct this request specifies a
-    /// minimum body length requirement, and the encapsulated packet is not
-    /// large enough to satisfy that requirement, then padding will
-    /// automatically be added (and zeroed for security).
-    pub struct EncapsulatingSerializationRequest<S: PacketSerializer, R: SerializationRequest> {
-        serializer: S,
-        inner: R,
-    }
-
-    impl<S: PacketSerializer, R: SerializationRequest> SerializationRequest
-        for EncapsulatingSerializationRequest<S, R>
-    {
-        type Buffer = R::Buffer;
-
-        fn serialize(
-            self, mut header_bytes: usize, min_body_and_padding_bytes: usize,
-            mut footer_bytes: usize,
-        ) -> BufferAndRange<R::Buffer> {
-            header_bytes += self.serializer.max_header_bytes();
-            footer_bytes += self.serializer.max_footer_bytes();
-
-            // The number required by this layer.
-            let this_min_body = self.serializer.min_body_and_padding_bytes();
-            // The number required by the next outer layer, taking into account
-            // that at least min_header_bytes + min_footer_bytes will be
-            // consumed by this layer.
-            let next_min_body = min_body_and_padding_bytes
-                .checked_sub(
-                    self.serializer.min_header_bytes() + self.serializer.min_footer_bytes(),
-                )
-                .unwrap_or(0);
-
-            let EncapsulatingSerializationRequest { serializer, inner } = self;
-            let mut buffer = inner.serialize(
-                header_bytes,
-                footer_bytes,
-                cmp::max(this_min_body, next_min_body),
-            );
-
-            let body_len = buffer.range().len();
-            if body_len < this_min_body {
-                // The body itself isn't large enough to satisfy the minimum
-                // body length requirement, so we add padding. This is only
-                // valid if the length requirement comes from this layer - if it
-                // comes from a lower layer, then there are other encapsulating
-                // packets which need to be serialized before the padding is
-                // added, and that layer's call to serialize will run this code
-                // block instead.
-
-                // This is guaranteed to succeed so long as inner.serialize
-                // satisfies its contract.
-                //
-                // SECURITY: Use _zero to ensure we zero padding bytes to
-                // prevent leaking information from packets previously stored in
-                // this buffer.
-                buffer.extend_forwards_zero(this_min_body - body_len);
-            }
-
-            serializer.serialize(&mut buffer);
-            buffer
-        }
-    }
-
-    /// A buffer and a range into that buffer.
-    ///
-    /// A `BufferAndRange` stores a pair of a buffer and a range which
-    /// represents a subset of the buffer. It implements `AsRef<[u8]>` and
-    /// `AsMut<[u8]>` for the range of the buffer.
-    ///
-    /// `BufferAndRange` is useful for passing nested payloads up the stack
-    /// while still maintaining access to the entire buffer in case it is needed
-    /// again in the future, such as to serialize new packets.
-    pub struct BufferAndRange<B> {
-        buffer: RefOrOwned<B>,
-        range: Range<usize>,
-    }
-
-    impl<B> BufferAndRange<B>
-    where
-        B: AsRef<[u8]>,
-    {
-        /// Construct a new `BufferAndRange` from an existing buffer.
-        ///
-        /// # Panics
-        ///
-        /// `new_from` panics if `range` is out of bounds of `buffer` or is
-        /// nonsensical (i.e., the upper bound precedes the lower bound).
-        pub fn new_from<R: RangeBounds<usize>>(buffer: B, range: R) -> BufferAndRange<B> {
-            let len = buffer.as_ref().len();
-            BufferAndRange {
-                buffer: RefOrOwned::Ref(buffer),
-                range: canonicalize_range_infallible(len, &range),
-            }
-        }
-
-        /// Extend the end of the range forwards towards the end of the buffer.
-        ///
-        /// `extend_forwards` adds `bytes` to the end index of the buffer's
-        /// range, resulting in the range being `bytes` bytes closer to the end
-        /// of the buffer than it was before.
-        ///
-        /// # Panics
-        ///
-        /// `extend_forwards` panics if there are fewer than `bytes` bytes
-        /// following the existing range.
-        pub fn extend_forwards(&mut self, bytes: usize) {
-            assert!(
-                bytes <= self.buffer.as_ref().len() - self.range.end,
-                "cannot extend range with {} following bytes forwards by {} bytes",
-                self.buffer.as_ref().len() - self.range.end,
-                bytes
-            );
-            self.range.end += bytes;
-        }
-
-        /// Ensure that this `BufferAndRange` satisfies certain prefix, suffix,
-        /// and padding size requirements.
-        ///
-        /// `ensure_prefix_suffix_padding` ensures that this `BufferAndRange`
-        /// has at least `prefix` bytes preceding the range, at least `suffix`
-        /// bytes following the range, and at least `range_plus_padding +
-        /// suffix` bytes in the range plus any bytes following the range. If it
-        /// already satisfies these constraints, then it is left unchanged.
-        /// Otherwise, a new buffer is allocated, the original range bytes are
-        /// copied into the new buffer, and the range is adjusted so that it
-        /// matches the location of the bytes in the new buffer.
-        ///
-        /// The "range plus padding" construction is useful when a packet format
-        /// requires a minimum body length, and the body which is being
-        /// encapsulated does not meet that minimum. In that case, it is
-        /// necessary to add extra padding bytes after the body in order to meet
-        /// the minimum.
-        fn ensure_prefix_suffix_padding(
-            &mut self, prefix: usize, suffix: usize, range_plus_padding: usize,
-        ) {
-            let range_len = self.range.end - self.range.start;
-            let post_range_len = self.buffer.as_ref().len() - self.range.end;
-            // normalize to guarantee that range_plus_padding >= range_len
-            let range_plus_padding = cmp::max(range_plus_padding, range_len);
-            if prefix > self.range.start
-                || suffix < post_range_len
-                || range_len + post_range_len < range_plus_padding + suffix
-            {
-                // TODO(joshlf): Right now, we split the world into two cases -
-                // either the constraints aren't satisfied and so we need to
-                // reallocate, or they are, so we don't need to do anything. In
-                // fact, there's a third case, in which the constraints aren't
-                // satisfied, but the buffer is large enough to satisfy the
-                // constraints. In that case, we can avoid reallocating by
-                // simply moving the range within the existing buffer.
-
-                // The constraints aren't satisfied, and the buffer isn't large
-                // enough to satisfy the constraints, so we have to reallocate.
-
-                let padding = range_plus_padding - range_len;
-                let total_len = prefix + range_len + padding + suffix;
-                let mut vec = vec![0; total_len];
-                vec[prefix..prefix + range_len]
-                    .copy_from_slice(slice(self.buffer.as_ref(), &self.range));
-                *self = BufferAndRange {
-                    buffer: RefOrOwned::Owned(vec),
-                    range: prefix..prefix + range_len,
-                }
-            }
-        }
-    }
-
-    impl<B> BufferAndRange<B> {
-        /// Shrink the buffer range.
-        ///
-        /// `slice` shrinks the buffer's range to be equal to the provided
-        /// range. It interprets `range` as relative to the current range. For
-        /// example, if, in a 10-byte buffer, the current range is `[2, 8)`, and
-        /// the `range` argument is `[2, 6)`, then after `slice` returns, the
-        /// beginning of the buffer's range will be `2 + 2 = 4`. Since `range`
-        /// has a length of 4, the end of the buffer's range will be `4 + 4 =
-        /// 8`.
-        ///
-        /// # Examples
-        ///
-        /// ```rust,ignore
-        /// # // TODO(joshlf): Make this compile and remove the ignore
-        /// let buf = [0; 10];
-        /// let mut buf = BufferAndRange::new_from(&buf, 2..8);
-        /// assert_eq!(buf.as_ref().len(), 6);
-        /// buf.slice(2..6);
-        /// assert_eq!(buf.as_ref().len(), 4);
-        /// ```
-        ///
-        /// # Panics
-        ///
-        /// `slice` panics if `range` is out of bounds for the existing buffer
-        /// range, or if it nonsensical (i.e., the upper bound precedes the
-        /// lower bound).
-        pub fn slice<R: RangeBounds<usize>>(&mut self, range: R) {
-            let cur_range_len = self.range.end - self.range.start;
-            let range = canonicalize_range_infallible(cur_range_len, &range);
-            self.range = translate_range(&range, isize::try_from(self.range.start).unwrap());
-        }
-
-        /// Extend the beginning of the range backwards towards the beginning of
-        /// the buffer.
-        ///
-        /// `extend_backwards` subtracts `bytes` from the beginning index of the
-        /// buffer's range, resulting in the range being `bytes` bytes closer to
-        /// the beginning of the buffer than it was before.
-        ///
-        /// # Panics
-        ///
-        /// `extend_backwards` panics if there are fewer than `bytes` bytes
-        /// preceding the existing range.
-        pub fn extend_backwards(&mut self, bytes: usize) {
-            assert!(
-                bytes <= self.range.start,
-                "cannot extend range starting at {} backwards by {} bytes",
-                self.range.start,
-                bytes
-            );
-            self.range.start -= bytes;
-        }
-
-        /// Get the range.
-        pub fn range(&self) -> Range<usize> {
-            self.range.clone()
-        }
-    }
-
-    impl<B> BufferAndRange<B>
-    where
-        B: AsMut<[u8]>,
-    {
-        /// Extract the prefix, range, and suffix from the buffer.
-        ///
-        /// `parts_mut` returns the region of the buffer preceding the range,
-        /// the range itself, and the region of the buffer following the range.
-        pub fn parts_mut(&mut self) -> (&mut [u8], &mut [u8], &mut [u8]) {
-            let (prefix, rest) = (&mut self.buffer.as_mut()[..]).split_at_mut(self.range.start);
-            let (mid, suffix) = rest.split_at_mut(self.range.end - self.range.start);
-            (prefix, mid, suffix)
-        }
-    }
-
-    impl<B> BufferAndRange<B>
-    where
-        B: AsRef<[u8]> + AsMut<[u8]>,
-    {
-        /// Extend the end of the range forwards towards the end of the buffer,
-        /// zeroing the newly-included bytes.
-        ///
-        /// `extend_forwards_zero` adds `bytes` to the end index of the buffer's
-        /// range, resulting in the range being `bytes` bytes closer to the end
-        /// of the buffer than it was before. These new bytes are set to zero,
-        /// which can be useful when extending a body to include padding which
-        /// has not yet been zeroed.
-        ///
-        /// # Panics
-        ///
-        /// `extend_forwards_zero` panics if there are fewer than `bytes` bytes
-        /// following the existing range.
-        fn extend_forwards_zero(&mut self, bytes: usize) {
-            self.extend_forwards(bytes);
-            let slice = self.as_mut();
-            let len = slice.len();
-            zero(&mut slice[len - bytes..]);
-        }
-    }
-
-    impl<B: AsRef<[u8]> + AsMut<[u8]>> SerializationRequest for BufferAndRange<B> {
-        type Buffer = B;
-
-        /// Serialize a packet, fulfilling this request.
-        ///
-        /// `serialize` ensures that this buffer satisfies the header, padding,
-        /// and footer requirements using `ensure_prefix_suffix_padding`, and
-        /// then returns it. The buffer's range is left in tact, and thus will
-        /// be treated as the payload to be encapsulated by any encapsulating
-        /// packets.
-        fn serialize(
-            mut self, header_bytes: usize, min_body_and_padding_bytes: usize, footer_bytes: usize,
-        ) -> BufferAndRange<B> {
-            self.ensure_prefix_suffix_padding(
-                header_bytes,
-                footer_bytes,
-                min_body_and_padding_bytes,
-            );
-            self
-        }
-    }
-
-    impl<B> AsRef<[u8]> for BufferAndRange<B>
-    where
-        B: AsRef<[u8]>,
-    {
-        fn as_ref(&self) -> &[u8] {
-            &self.buffer.as_ref()[self.range.clone()]
-        }
-    }
-
-    impl<B> AsMut<[u8]> for BufferAndRange<B>
-    where
-        B: AsMut<[u8]>,
-    {
-        fn as_mut(&mut self) -> &mut [u8] {
-            &mut self.buffer.as_mut()[self.range.clone()]
-        }
-    }
-
-    /// Either a reference or an owned allocated buffer.
-    enum RefOrOwned<B> {
-        Ref(B),
-        Owned(Vec<u8>),
-    }
-
-    impl<B: AsRef<[u8]>> AsRef<[u8]> for RefOrOwned<B> {
-        fn as_ref(&self) -> &[u8] {
-            match self {
-                RefOrOwned::Ref(ref r) => r.as_ref(),
-                RefOrOwned::Owned(ref v) => v.as_slice(),
-            }
-        }
-    }
-
-    impl<B: AsMut<[u8]>> AsMut<[u8]> for RefOrOwned<B> {
-        fn as_mut(&mut self) -> &mut [u8] {
-            match self {
-                RefOrOwned::Ref(ref mut r) => r.as_mut(),
-                RefOrOwned::Owned(ref mut v) => v.as_mut_slice(),
-            }
-        }
-    }
-
-    /// Zero a slice.
-    ///
-    /// Set every element of `slice` to 0.
-    fn zero(slice: &mut [u8]) {
-        for s in slice.iter_mut() {
-            *s = 0;
-        }
-    }
-
-    /// Translate a `Range<usize>` left or right.
-    ///
-    /// Translate a `Range<usize>` by a fixed offset. This function is
-    /// equivalent to the following code, except with overflow explicitly
-    /// checked:
-    ///
-    /// ```rust,ignore
-    /// # // TODO(joshlf): Make this compile and remove the ignore
-    /// Range {
-    ///     start: ((range.start as isize) + offset) as usize,
-    ///     end: ((range.end as isize) + offset) as usize,
-    /// }
-    /// ```
-    ///
-    /// # Panics
-    ///
-    /// `translate_range` panics if any addition overflows or any conversion
-    /// between signed and unsigned types fails.
-    fn translate_range(range: &Range<usize>, offset: isize) -> Range<usize> {
-        let start = isize::try_from(range.start).unwrap();
-        let end = isize::try_from(range.end).unwrap();
-        Range {
-            start: usize::try_from(start.checked_add(offset).unwrap()).unwrap(),
-            end: usize::try_from(end.checked_add(offset).unwrap()).unwrap(),
-        }
-    }
-
-    /// Get an immutable slice from a range.
-    ///
-    /// This is a temporary replacement for the syntax `&slc[range]` until this
-    /// [issue] is fixed.
-    ///
-    /// [issue]: https://github.com/rust-lang/rust/issues/35729#issuecomment-394200339
-    fn slice<'a, T, R: RangeBounds<usize>>(slc: &'a [T], range: &R) -> &'a [T] {
-        let len = slc.len();
-        &slc[canonicalize_range_infallible(len, range)]
-    }
-
-    #[cfg(test)]
-    mod tests {
-        use super::*;
-
-        #[test]
-        fn test_buffer_and_range_slice() {
-            let mut buf = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
-            let mut buf = BufferAndRange::new_from(&mut buf, ..);
-            assert_eq!(buf.range(), 0..10);
-            assert_eq!(buf.as_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
-            assert_eq!(
-                buf.parts_mut(),
-                (
-                    &mut [][..],
-                    &mut [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][..],
-                    &mut [][..]
-                )
-            );
-
-            buf.slice(..);
-            assert_eq!(buf.range(), 0..10);
-            assert_eq!(buf.as_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
-            assert_eq!(
-                buf.parts_mut(),
-                (
-                    &mut [][..],
-                    &mut [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][..],
-                    &mut [][..]
-                )
-            );
-
-            buf.slice(2..);
-            assert_eq!(buf.range(), 2..10);
-            assert_eq!(buf.as_ref(), [2, 3, 4, 5, 6, 7, 8, 9]);
-            assert_eq!(
-                buf.parts_mut(),
-                (
-                    &mut [0, 1][..],
-                    &mut [2, 3, 4, 5, 6, 7, 8, 9][..],
-                    &mut [][..]
-                )
-            );
-
-            buf.slice(..8);
-            assert_eq!(buf.range(), 2..10);
-            assert_eq!(buf.as_ref(), [2, 3, 4, 5, 6, 7, 8, 9]);
-            assert_eq!(
-                buf.parts_mut(),
-                (
-                    &mut [0, 1][..],
-                    &mut [2, 3, 4, 5, 6, 7, 8, 9][..],
-                    &mut [][..]
-                )
-            );
-
-            buf.slice(..6);
-            assert_eq!(buf.range(), 2..8);
-            assert_eq!(buf.as_ref(), [2, 3, 4, 5, 6, 7]);
-            assert_eq!(
-                buf.parts_mut(),
-                (
-                    &mut [0, 1][..],
-                    &mut [2, 3, 4, 5, 6, 7][..],
-                    &mut [8, 9][..]
-                )
-            );
-
-            buf.slice(2..4);
-            assert_eq!(buf.range(), 4..6);
-            assert_eq!(buf.as_ref(), [4, 5]);
-            assert_eq!(
-                buf.parts_mut(),
-                (
-                    &mut [0, 1, 2, 3][..],
-                    &mut [4, 5][..],
-                    &mut [6, 7, 8, 9][..]
-                )
-            );
-        }
-
-        #[test]
-        fn test_buffer_and_range_extend_backwards() {
-            let buf = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
-            let mut buf = BufferAndRange::new_from(&buf, 2..8);
-            assert_eq!(buf.range(), 2..8);
-            assert_eq!(buf.as_ref(), [2, 3, 4, 5, 6, 7]);
-            buf.extend_backwards(1);
-            assert_eq!(buf.range(), 1..8);
-            assert_eq!(buf.as_ref(), [1, 2, 3, 4, 5, 6, 7]);
-            buf.extend_backwards(1);
-            assert_eq!(buf.range(), 0..8);
-            assert_eq!(buf.as_ref(), [0, 1, 2, 3, 4, 5, 6, 7]);
-        }
-
-        #[test]
-        #[should_panic]
-        fn test_buffer_and_range_extend_backwards_panics() {
-            let buf = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
-            let mut buf = BufferAndRange::new_from(&buf, 2..8);
-            assert_eq!(buf.as_ref(), [2, 3, 4, 5, 6, 7]);
-            buf.extend_backwards(1);
-            assert_eq!(buf.as_ref(), [1, 2, 3, 4, 5, 6, 7]);
-            buf.extend_backwards(2);
-        }
-
-        #[test]
-        fn test_ensure_prefix_suffix_padding() {
-            fn verify<B: AsRef<[u8]> + AsMut<[u8]>>(
-                mut buffer: BufferAndRange<B>, prefix: usize, suffix: usize,
-                range_plus_padding: usize,
-            ) {
-                let range_len_old = {
-                    let range = buffer.range();
-                    range.end - range.start
-                };
-                let mut range_old = Vec::with_capacity(range_len_old);
-                range_old.extend_from_slice(buffer.as_ref());
-
-                buffer.ensure_prefix_suffix_padding(prefix, suffix, range_plus_padding);
-                let range_len_new = {
-                    let range = buffer.range();
-                    range.end - range.start
-                };
-                assert_eq!(range_len_old, range_len_new);
-                let (pfx, range, sfx) = buffer.parts_mut();
-                assert!(pfx.len() >= prefix);
-                assert_eq!(range.len(), range_len_new);
-                assert!(sfx.len() >= suffix);
-                assert_eq!(range_old.as_slice(), range);
-                assert!(range.len() + sfx.len() >= (range_plus_padding + suffix));
-            }
-
-            // Test for every valid combination of buf_len, range_start,
-            // range_end, prefix, suffix, and range_plus_padding within [0, 8).
-            for buf_len in 0..8 {
-                for range_start in 0..buf_len {
-                    for range_end in range_start..buf_len {
-                        for prefix in 0..8 {
-                            for suffix in 0..8 {
-                                for range_plus_padding in 0..8 {
-                                    let mut vec = Vec::with_capacity(buf_len);
-                                    vec.resize(buf_len, 0);
-                                    // Initialize the vector with values 0, 1, 2,
-                                    // ... so that we can check to make sure that
-                                    // the range bytes have been properly copied if
-                                    // the buffer is reallocated.
-                                    for i in 0..vec.len() {
-                                        vec[i] = i as u8;
-                                    }
-                                    verify(
-                                        BufferAndRange::new_from(
-                                            vec.as_mut_slice(),
-                                            range_start..range_end,
-                                        ),
-                                        prefix,
-                                        suffix,
-                                        range_plus_padding,
-                                    );
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}