diff --git a/src/buffer.rs b/src/buffer.rs
index a822d66b3c8b8f678c4078f3f30f26c6ce6ccef4..13db9eedd91d8c6ff8974cf0d2cf96f5d257c5b4 100644
--- a/src/buffer.rs
+++ b/src/buffer.rs
@@ -1,24 +1,33 @@
-use core::{borrow::Borrow, ops::Deref};
+use core::ops::{Deref, DerefMut};
 
-pub(crate) struct BufferOverflow;
+#[derive(Debug)]
+pub struct BufferOverflow;
 
-pub(crate) struct Buffer<'a, const N: usize> {
+#[derive(Debug)]
+pub struct Buffer<'a, const N: usize> {
     data: &'a mut [u8; N],
     i: usize,
 }
 
 impl<'a, const N: usize> Buffer<'a, N> {
+    /// Constructs a new `Buffer`.
+    /// `data` is the backing array.
+    /// `i` is the number of elements in `data` that contain data (and should thus be exposed by `Buffer`)
     pub fn new(data: &'a mut [u8; N], i: usize) -> Self {
+        assert!(i <= data.len());
+
         Self { data, i }
     }
 
-    /*pub fn as_slice(&self) -> &'a [u8] {
-            &self.data[..self.i]
-        }
+    pub fn new_full(data: &'a mut [u8; N]) -> Self {
+        let i = data.len();
+
+        Self::new(data, i)
+    }
 
-        pub fn as_slice_mut(&mut self) -> &'a [u8] {
-            &mut self.data[..self.i]
-    }*/
+    pub fn new_empty(data: &'a mut [u8; N]) -> Self {
+        Self::new(data, 0)
+    }
 
     pub const fn remaining_capacity(&self) -> usize {
         N - self.i
@@ -35,6 +44,10 @@ impl<'a, const N: usize> Buffer<'a, N> {
         Ok(())
     }
 
+    pub fn push(&mut self, v: u8) {
+        self.try_push(v).unwrap();
+    }
+
     pub fn try_extend_from_slice(&mut self, other: &[u8]) -> Result<(), BufferOverflow> {
         if self.remaining_capacity() < other.len() {
             return Err(BufferOverflow);
@@ -46,6 +59,10 @@ impl<'a, const N: usize> Buffer<'a, N> {
         Ok(())
     }
 
+    pub fn extend(&mut self, other: &[u8]) {
+        self.try_extend_from_slice(other).unwrap();
+    }
+
     pub fn pop(&mut self) -> Option<u8> {
         if self.i == 0 {
             return None;
@@ -55,6 +72,33 @@ impl<'a, const N: usize> Buffer<'a, N> {
 
         Some(self.data[self.i])
     }
+
+    pub fn truncate(&mut self, new_len: usize) {
+        assert!(self.i >= new_len);
+
+        self.i = new_len;
+    }
+
+    pub fn drain(&mut self, start: usize, end: usize) {
+        assert!(end >= start);
+        assert!(end <= self.i);
+        let delta = end - start;
+        let surplus = self.len() - end;
+
+        for i in start..(start + surplus) {
+            self[i] = self[i + delta];
+        }
+
+        self.i -= delta;
+    }
+
+    pub fn clone_backing<'b>(&self, buf: &'b mut [u8; N]) -> Buffer<'b, N> {
+        let mut out = Buffer::new_empty(buf);
+
+        out.extend(self);
+
+        out
+    }
 }
 
 impl<'a, const N: usize> From<&'a mut [u8; N]> for Buffer<'a, N> {
@@ -70,3 +114,9 @@ impl<'a, const N: usize> Deref for Buffer<'a, N> {
         &self.data[..self.i]
     }
 }
+
+impl<'a, const N: usize> DerefMut for Buffer<'a, N> {
+    fn deref_mut(&mut self) -> &mut [u8] {
+        &mut self.data[..self.i]
+    }
+}
diff --git a/src/interleaver.rs b/src/interleaver.rs
index 5661bb43b2b90d526157167c4ad8f5718984f806..5efeaaf7a3fdded2468749e22dd88dfe01383ff0 100644
--- a/src/interleaver.rs
+++ b/src/interleaver.rs
@@ -1,11 +1,13 @@
-use arrayvec::{ArrayVec, CapacityError};
 use bitvec::prelude::*;
 
-use crate::error::EncodeError;
+use crate::{
+    buffer::{Buffer, BufferOverflow},
+    error::EncodeError,
+};
 
 pub(crate) fn interleave<const N: usize>(
     data: &[u8],
-    out: &mut ArrayVec<u8, N>,
+    out: &mut Buffer<N>,
 ) -> Result<(), EncodeError> {
     let bv = data.view_bits::<Msb0>();
 
@@ -33,10 +35,10 @@ pub(crate) fn interleave<const N: usize>(
 
 pub(crate) fn uninterleave<const N: usize>(
     data: &[u8],
-) -> Result<ArrayVec<u8, N>, CapacityError<u8>> {
+    out: &mut Buffer<N>,
+) -> Result<(), BufferOverflow> {
     let bv = data.view_bits::<Msb0>();
 
-    let mut out: ArrayVec<u8, N> = ArrayVec::new();
     for _ in 0..data.len() {
         out.try_push(0)?;
     }
@@ -55,7 +57,7 @@ pub(crate) fn uninterleave<const N: usize>(
         }
     }
 
-    Ok(out)
+    Ok(())
 }
 
 #[cfg(test)]
@@ -64,17 +66,22 @@ mod tests {
 
     #[test]
     fn interleaver_works() {
-        let orig =
-            ArrayVec::try_from([0x84, 0x73, 0x12, 0xA3, 0xFF, 0x00, 0xC2, 0x1B, 0x77]).unwrap();
-        let mut interleaved: ArrayVec<u8, 10> = ArrayVec::new();
+        let mut data = [0x84, 0x73, 0x12, 0xA3, 0xFF, 0x00, 0xC2, 0x1B, 0x77];
+        let orig = Buffer::new_full(&mut data);
+
+        let mut interleaved = [0; 10];
+        let mut interleaved = Buffer::new(&mut interleaved, 0);
         interleaved.push(b'H');
+
         interleave(&orig, &mut interleaved).unwrap();
 
         let expected = [b'H', 0xCD, 0xB5, 0xDB, 0x2A, 0x0A, 0x52, 0x0C, 0x89, 0x4F];
         assert_eq!(expected, interleaved[..]);
 
-        let uninterleaved = uninterleave(&interleaved[1..]).unwrap();
+        let mut uninterleaved = [0; 10];
+        let mut uninterleaved = Buffer::new(&mut uninterleaved, 0);
+        uninterleave(&interleaved[1..], &mut uninterleaved).unwrap();
 
-        assert_eq!(orig, uninterleaved);
+        assert_eq!(*orig, *uninterleaved);
     }
 }
diff --git a/src/ldpc.rs b/src/ldpc.rs
index 0c5c07e0e8b9fa62666ca30e644616747678d542..a3cda75036ccfb1cadf6a4159707913c59198dc5 100644
--- a/src/ldpc.rs
+++ b/src/ldpc.rs
@@ -1,4 +1,3 @@
-use arrayvec::ArrayVec;
 use labrador_ldpc::LDPCCode;
 
 use crate::{buffer::Buffer, error::EncodeError};
@@ -84,7 +83,7 @@ pub(crate) fn encode<const N: usize>(data: &mut Buffer<N>) -> Result<(), EncodeE
     Ok(())
 }
 
-pub(crate) fn decode<const N: usize>(data_av: &mut ArrayVec<u8, N>) -> Option<()> {
+pub(crate) fn decode<const N: usize>(data_av: &mut Buffer<N>) -> Option<()> {
     if data_av.len() < 2 {
         return None;
     }
@@ -157,25 +156,18 @@ pub(crate) fn decode<const N: usize>(data_av: &mut ArrayVec<u8, N>) -> Option<()
 #[cfg(test)]
 mod tests {
     use super::*;
-    use arrayvec::ArrayVec;
 
     #[test]
     fn len_test() {
         // from the example in the docs
-        let mut data: ArrayVec<u8, 8191> = ArrayVec::new();
+        let mut buf = [0; 8191];
+        let mut data = Buffer::new_empty(&mut buf);
 
         for _ in 0..41 {
-            data.extend(
-                b"Example packet data  wueirpqwerwrywqoeiruy29346129384761"
-                    .iter()
-                    .cloned(),
-            );
+            data.extend(b"Example packet data  wueirpqwerwrywqoeiruy29346129384761");
         }
-        data.extend(
-            b"Example packet data  wueirpqwerwrywqoeiru346129384761"
-                .iter()
-                .cloned(),
-        );
+        data.extend(b"Example packet data  wueirpqwerwrywqoeiru346129384761");
+
         assert_eq!(2349, data.len());
 
         encode(&mut data).unwrap();
@@ -185,51 +177,50 @@ mod tests {
 
     #[test]
     fn basic_encode_decode_short() {
-        let mut data: ArrayVec<u8, 32> = ArrayVec::new();
+        let mut buf = [0; 32];
+        let mut buf2 = [0; 32];
+        let mut data = Buffer::new_empty(&mut buf);
         data.try_extend_from_slice(b"Hello world!").unwrap();
-        let orig = data.clone();
+        let orig = data.clone_backing(&mut buf2);
 
         encode(&mut data).unwrap();
 
         decode(&mut data).unwrap();
 
-        assert_eq!(orig, data);
+        assert_eq!(*orig, *data);
     }
 
     #[test]
     fn basic_encode_decode() {
-        let mut data: ArrayVec<u8, 8191> = ArrayVec::new();
+        let mut buf = [0; 8191];
+        let mut buf2 = [0; 8191];
+        let mut data = Buffer::new_empty(&mut buf);
         for _ in 0..50 {
-            data.extend(
-                b"This is a test packet. jsalksjd093809324JASLD:LKD*#$)(*#@)"
-                    .iter()
-                    .cloned(),
-            );
+            data.extend(b"This is a test packet. jsalksjd093809324JASLD:LKD*#$)(*#@)");
         }
-        let orig = data.clone();
+        let orig = data.clone_backing(&mut buf2);
 
         encode(&mut data).unwrap();
-        assert_ne!(orig, data);
+        assert_ne!(*orig, *data);
 
         decode(&mut data).unwrap();
 
-        assert_eq!(orig, data);
+        assert_eq!(*orig, *data);
     }
 
     #[test]
     fn encode_decode_with_bit_flips() {
-        let mut data: ArrayVec<u8, 8191> = ArrayVec::new();
+        let mut buf = [0; 8191];
+        let mut buf2 = [0; 8191];
+        let mut data = Buffer::new_empty(&mut buf);
+
         for _ in 0..50 {
-            data.extend(
-                b"jsalksjd093809324JASLD:LKD*#$)(*#@) Another test packet"
-                    .iter()
-                    .cloned(),
-            );
+            data.extend(b"jsalksjd093809324JASLD:LKD*#$)(*#@) Another test packet");
         }
-        let orig = data.clone();
+        let orig = data.clone_backing(&mut buf2);
 
         encode(&mut data).unwrap();
-        assert_ne!(orig, data);
+        assert_ne!(*orig, *data);
 
         data[234] ^= 0x55;
         data[0] ^= 0xAA;
@@ -237,6 +228,6 @@ mod tests {
 
         decode(&mut data).unwrap();
 
-        assert_eq!(orig, data);
+        assert_eq!(*orig, *data);
     }
 }
diff --git a/src/packet.rs b/src/packet.rs
index f669b1b86ddae43ef1cede5498d1e667a6951881..7565205cd75b1a36589b5e38fac9a36869816b5e 100644
--- a/src/packet.rs
+++ b/src/packet.rs
@@ -1,6 +1,5 @@
 use core::fmt::Debug;
 
-use arrayvec::{ArrayVec, CapacityError};
 use crc::{Crc, CRC_16_IBM_SDLC};
 
 use crate::{
@@ -31,7 +30,7 @@ macro_rules! uniq_whisker {
                     return Err(EncodeError::DuplicateData);
                 }
 
-                try_lock(&mut self.data, |data| {
+                try_lock(&mut self.buf, |data| {
 					let mut buf = [0; 256];
                     // safe to unwrap since we know we have enough space
                     let out = w.encode(&mut buf).unwrap();
@@ -67,7 +66,7 @@ macro_rules! poly_whisker {
             }
 
 			pub fn [<add_ $t:lower>](&mut self, w: $t) -> Result<(), EncodeError> {
-				try_lock(self.buf.as_slice_mut(), |data| {
+				try_lock(&mut self.buf, |data| {
 					let mut buf = [0; 256];
                     // safe to unwrap since we know we have enough space
                     let out = w.encode(&mut buf).unwrap();
@@ -148,7 +147,7 @@ impl<'a, const N: usize> Packet<'a, N> {
     /// Directly after the CRC block in The Pipeline
     /// Expects bytes in the `buf`
     /// `buf` is used as the backing buffer for the Packet
-    pub fn semi_decode(buf: Buffer<'a, N>) -> Result<Self, DecodeError> {
+    pub fn semi_decode(mut buf: Buffer<'a, N>) -> Result<Self, DecodeError> {
         let crc1 = buf.pop().ok_or(DecodeError::UnexpectedEndOfInput)?;
         let crc0 = buf.pop().ok_or(DecodeError::UnexpectedEndOfInput)?;
         let crc_expected = u16::from_le_bytes([crc0, crc1]);
@@ -163,34 +162,34 @@ impl<'a, const N: usize> Packet<'a, N> {
 
     /// Encodes packet for transmission on the air.
     /// Includes the data length L, but does not include the preamble or sync word.
-    // TODO buffer-ify this
-    pub fn fully_encode(self) -> Result<ArrayVec<u8, N>, EncodeError> {
+    pub fn fully_encode(self, out: &mut Buffer<N>) -> Result<(), EncodeError> {
         let mut data = self.semi_encode().map_err(|(err, _)| err)?;
         whitener::whiten(&mut data);
         ldpc::encode(&mut data)?;
-        let mut out: ArrayVec<u8, N> = ArrayVec::new();
+
         // safe to unwrap - length must be below 8191
         out.try_extend_from_slice(&u16::try_from(data.len()).unwrap().to_le_bytes())
             .map_err(|_| EncodeError::CatsOverflow)?;
-        interleaver::interleave(&data, &mut out)?;
+        interleaver::interleave(&data, out)?;
 
-        Ok(out)
+        Ok(())
     }
 
     /// Decodes packet that was received over the air.
     /// Packet shouldn't have preamble, sync word, or data langth L.
     /// Expects bytes in the `buf`
     /// `buf` is used as the backing buffer for the Packet
-    pub fn fully_decode(buf: Buffer<'a, N>) -> Result<Self, DecodeError> {
-        let mut data = interleaver::uninterleave(buf).map_err(|_| DecodeError::Overflow)?;
-        ldpc::decode(&mut data).ok_or(DecodeError::LdpcError)?;
-        whitener::whiten(&mut data);
+    pub fn fully_decode(data: &[u8], buf: &'a mut [u8; N]) -> Result<Self, DecodeError> {
+        let mut buf = Buffer::new_empty(buf);
+        interleaver::uninterleave(data, &mut buf).map_err(|_| DecodeError::Overflow)?;
+        ldpc::decode(&mut buf).ok_or(DecodeError::LdpcError)?;
+        whitener::whiten(&mut buf);
 
-        Self::semi_decode(data)
+        Self::semi_decode(buf)
     }
 
     pub fn iter(&self) -> ValidatedWhiskerIter {
-        ValidatedWhiskerIter::new(&self.data)
+        ValidatedWhiskerIter::new(&self.buf)
     }
 
     uniq_whisker!(Identification);
@@ -200,7 +199,7 @@ impl<'a, const N: usize> Packet<'a, N> {
     poly_whisker!(Destination);
     poly_whisker!(Arbitrary);
 
-    pub fn comment<'b>(&self, buf: &'b mut [u8]) -> Result<&'a str, CommentError> {
+    pub fn comment<'b>(&self, buf: &'b mut [u8]) -> Result<&'b str, CommentError> {
         let iter = self.iter().filter_map(|w| match w {
             Whisker::Comment(c) => Some(c),
             _ => None,
@@ -232,7 +231,7 @@ impl<'a, const N: usize> Packet<'a, N> {
             return Err(EncodeError::DuplicateData);
         }
 
-        try_lock(&mut self.data, |data| {
+        try_lock(&mut self.buf, |data| {
             let mut comment = comment.as_bytes();
 
             while comment.len() > 255 {
@@ -269,12 +268,12 @@ impl<'a, const N: usize> Packet<'a, N> {
 
     fn clear_by_type(&mut self, whisker_type: u8, all: bool) {
         let mut i = 0;
-        while i < self.data.len() {
-            let t = self.data[i];
-            let step = usize::from(self.data[i + 1]) + 2;
+        while i < self.buf.len() {
+            let t = self.buf[i];
+            let step = usize::from(self.buf[i + 1]) + 2;
 
             if t == whisker_type {
-                self.data.drain(i..(i + step));
+                self.buf.drain(i, i + step);
 
                 if !all {
                     return;
@@ -289,7 +288,7 @@ impl<'a, const N: usize> Packet<'a, N> {
 impl<'a, const N: usize> Debug for Packet<'a, N> {
     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
         f.debug_list()
-            .entries(ValidatedWhiskerIter::new(self.buf.as_slice()))
+            .entries(ValidatedWhiskerIter::new(&self.buf))
             .finish()
     }
 }
@@ -314,7 +313,7 @@ fn try_lock<'a, const N: usize, T, E, F: Fn(&mut Buffer<'a, N>) -> Result<T, E>>
 
 #[cfg(test)]
 mod tests {
-    use arrayvec::ArrayString;
+    use arrayvec::{ArrayString, ArrayVec};
 
     use super::*;
 
@@ -323,7 +322,8 @@ mod tests {
         let d1 = Destination::new(false, 7, "CALL1", 23).unwrap();
         let d2 = Destination::new(true, 23, "CALL2", 2).unwrap();
 
-        let mut packet: Packet<1024> = Packet::default();
+        let mut buf = [0; 1024];
+        let mut packet: Packet<1024> = Packet::new(&mut buf);
         packet.add_destination(d1.clone()).unwrap();
         packet.add_destination(d2.clone()).unwrap();
 
@@ -335,7 +335,8 @@ mod tests {
 
     #[test]
     fn route_clear() {
-        let mut p: Packet<1024> = Packet::default();
+        let mut buf = [0; 1024];
+        let mut p: Packet<1024> = Packet::new(&mut buf);
         p.add_identification(Identification::new(123, "call", 43).unwrap())
             .unwrap();
         let mut r = Route::new(8);
@@ -358,7 +359,8 @@ mod tests {
     fn semi_e2e() {
         let comment = "Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.";
 
-        let mut packet = Packet::<2048>::default();
+        let mut buf = [0; 2048];
+        let mut packet = Packet::new(&mut buf);
         packet
             .add_identification(Identification {
                 icon: 123,
@@ -396,7 +398,8 @@ mod tests {
     fn full_e2e() {
         let comment = "Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.Hello world! This is a comment. It's long so that it needs to be split across more than one whisker.";
 
-        let mut packet = Packet::<4096>::default();
+        let mut buf = [0; 4096];
+        let mut packet = Packet::new(&mut buf);
         packet
             .add_identification(Identification {
                 icon: 123,
@@ -414,13 +417,16 @@ mod tests {
         });
         assert!(matches!(res, Err(EncodeError::DuplicateData)));
 
-        let mut fully = packet.fully_encode().unwrap();
+        let mut buf2 = [0; 4096];
+        let mut fully = Buffer::new_empty(&mut buf2);
+        packet.fully_encode(&mut fully).unwrap();
 
         fully[40] ^= 0x55;
         fully[844] ^= 0x7B;
 
         // exclude length
-        let packet2: Packet<8191> = Packet::fully_decode(&fully[2..]).unwrap();
+        let mut buf3 = [0; 8191];
+        let packet2: Packet<8191> = Packet::fully_decode(&fully[2..], &mut buf3).unwrap();
         assert_eq!(
             Identification {
                 icon: 123,
@@ -487,7 +493,8 @@ mod tests {
             118, 118, 118, 118, 118, 118, 118, 0, 0, 0, 0, 96, 96,
         ];
 
-        let _ = Packet::<1024>::fully_decode(&data);
+        let mut buf = [0; 1024];
+        let _ = Packet::<1024>::fully_decode(&data, &mut buf);
     }
 
     #[test]
@@ -535,13 +542,15 @@ mod tests {
         )
         .unwrap();
 
-        let _ = Packet::<1024>::semi_decode(data);
+        let mut buf = [0; 1024];
+        let _ = Packet::<1024>::fully_decode(&data, &mut buf);
     }
 
     #[test]
     fn decode_fuzz_tests() {
-        let data = ArrayVec::try_from(&[4, 0, 0, 0][..]).unwrap();
+        let data = [4, 0, 0, 0];
 
-        let _ = Packet::<1024>::decode(data);
+        let mut buf = [0; 1024];
+        let _ = Packet::<1024>::fully_decode(&data, &mut buf);
     }
 }
diff --git a/src/whitener.rs b/src/whitener.rs
index 53d846dc35b6c19ec254ee79cec82c8ea6f6beaf..79d21d97d4a8fa58b6bc6c616e3041fdbd560b77 100644
--- a/src/whitener.rs
+++ b/src/whitener.rs
@@ -14,9 +14,9 @@ mod tests {
 
     #[test]
     fn basic() {
-        let mut data: ArrayVec<u8, 512> =
-            ArrayVec::try_from(&b"Hello world! The quick brown fox jumped over the lazy dog"[..])
-                .unwrap();
+        let mut data = [0; 64];
+        data[0..57]
+            .clone_from_slice(&b"Hello world! The quick brown fox jumped over the lazy dog"[..]);
         let orig = data.clone();
 
         whiten(&mut data);