Project import generated by Copybara.

GitOrigin-RevId: 5028dd6db990e351e6ce4b988267b60f3ac785d7
Change-Id: I981c26f33d52d775acde707d87df1c10a602e26f
diff --git a/nearby/Cargo.lock b/nearby/Cargo.lock
index 7441c1b..657ac34 100644
--- a/nearby/Cargo.lock
+++ b/nearby/Cargo.lock
@@ -279,6 +279,7 @@
  "globset",
  "log",
  "owo-colors",
+ "regex",
  "semver",
  "serde_json",
  "shell-escape",
@@ -1022,7 +1023,6 @@
 version = "0.1.0"
 dependencies = [
  "criterion",
- "crypto_provider",
  "lazy_static",
  "lock_adapter",
 ]
@@ -1338,9 +1338,9 @@
 
 [[package]]
 name = "memchr"
-version = "2.5.0"
+version = "2.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
 
 [[package]]
 name = "memoffset"
@@ -1861,9 +1861,9 @@
 
 [[package]]
 name = "regex"
-version = "1.9.1"
+version = "1.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -1873,9 +1873,9 @@
 
 [[package]]
 name = "regex-automata"
-version = "0.3.4"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -1884,9 +1884,9 @@
 
 [[package]]
 name = "regex-syntax"
-version = "0.7.4"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
 [[package]]
 name = "reword"
diff --git a/nearby/Cargo.toml b/nearby/Cargo.toml
index 1416e24..df92f64 100644
--- a/nearby/Cargo.toml
+++ b/nearby/Cargo.toml
@@ -171,6 +171,7 @@
 which = "4.4.0"
 file-header = "0.1.2"
 serde_json.workspace = true
+regex = "1.10.2"
 
 [dev-dependencies]
 tempfile.workspace = true
diff --git a/nearby/presence/np_adv/Cargo.toml b/nearby/presence/np_adv/Cargo.toml
index 5123737..97e12f5 100644
--- a/nearby/presence/np_adv/Cargo.toml
+++ b/nearby/presence/np_adv/Cargo.toml
@@ -14,7 +14,7 @@
 crypto_provider.workspace = true
 strum.workspace = true
 strum_macros.workspace = true
-nom = { version = "7.1.3", default-features = false, features = ["alloc"] }
+nom = { version = "7.1.3", default-features = false }
 lazy_static.workspace = true
 sink.workspace = true
 tinyvec.workspace = true
diff --git a/nearby/presence/np_adv/src/array_vec.rs b/nearby/presence/np_adv/src/array_vec.rs
new file mode 100644
index 0000000..a92ff5b
--- /dev/null
+++ b/nearby/presence/np_adv/src/array_vec.rs
@@ -0,0 +1,203 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Provides an [`ArrayVecOption`] wrapper implementation over [`ArrayVec`],
+//! which stores all the elements in an `Option` in order to satisfy
+//! `ArrayVec`'s requirement that the elements must implement `Default`.
+
+use tinyvec::{ArrayVec, ArrayVecIterator};
+#[cfg(any(test, feature = "alloc"))]
+extern crate alloc;
+#[cfg(any(test, feature = "alloc"))]
+use alloc::vec::Vec;
+
+/// A wrapper of [`ArrayVec`] that stores it values as [`Option`], in order to
+/// satisfy `ArrayVec`'s requirement that the elements must implement `Default`.
+/// The implementation guarantees that any items in the wrapped `ArrayVec`
+/// within `0..len` is `Some`, and therefore will not panic when unwrapped.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ArrayVecOption<A, const N: usize>(ArrayVec<[Option<A>; N]>);
+
+// Cannot derive due to https://github.com/rust-lang/rust/issues/74462
+impl<A, const N: usize> Default for ArrayVecOption<A, N> {
+    fn default() -> Self {
+        Self(Default::default())
+    }
+}
+
+/// Iterator type returned by `ArrayVecOption.iter()`, which can be used as
+/// `impl Iterator<Item = &A>`
+pub type ArrayVecOptionRefIter<'a, A> =
+    core::iter::Map<core::slice::Iter<'a, Option<A>>, fn(&'a Option<A>) -> &'a A>;
+
+/// Iterator type returned by `ArrayVecOption.into_iter()`, which can be used as
+/// `impl Iterator<Item = A>`
+pub type ArrayVecOptionIntoIter<A, const N: usize> =
+    core::iter::Map<ArrayVecIterator<[Option<A>; N]>, fn(Option<A>) -> A>;
+
+impl<A, const N: usize> ArrayVecOption<A, N> {
+    /// Returns an iterator over this vec.
+    pub fn iter(&self) -> ArrayVecOptionRefIter<A> {
+        self.0
+            .iter()
+            .map(|v| v.as_ref().expect("ArrayVecOption value before .len() should not be None"))
+    }
+
+    /// The length of the vec (in elements).
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    /// Checks if the length is 0.
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    /// Returns the first element of the vec, or `None` if it is empty.
+    pub fn first(&self) -> Option<&A> {
+        self.iter().next()
+    }
+
+    /// Place an element onto the end of the vec.
+    ///
+    /// # Panics
+    /// * If the length of the vec would overflow the capacity.
+    pub fn push(&mut self, value: A) {
+        self.0.push(Some(value))
+    }
+
+    /// Returns a reference to an element at the given index.
+    pub fn get(&self, index: usize) -> Option<&A> {
+        self.0.get(index).and_then(|opt| opt.as_ref())
+    }
+
+    /// Sorts the slice with a key extraction function, but might not preserve
+    /// the order of equal elements.
+    ///
+    /// This sort is unstable (i.e., may reorder equal elements), in-place
+    /// (i.e., does not allocate), and *O*(*m* \* *n* \* log(*n*)) worst-case,
+    /// where the key function is *O*(*m*).
+    pub fn sort_unstable_by_key<K: Ord>(&mut self, f: impl Fn(&A) -> K) {
+        self.0.sort_unstable_by_key(|a| f(a.as_ref().unwrap()))
+    }
+
+    /// Remove an element, swapping the end of the vec into its place.
+    pub fn swap_remove(&mut self, index: usize) -> A {
+        self.0.swap_remove(index).unwrap()
+    }
+
+    /// Converts this vector into a regular `Vec`, unwrapping all of the
+    /// `Option` in the process.
+    #[cfg(any(test, feature = "alloc"))]
+    pub fn into_vec(self) -> Vec<A> {
+        self.into_iter().collect()
+    }
+}
+
+impl<A, const N: usize> IntoIterator for ArrayVecOption<A, N> {
+    type Item = A;
+    type IntoIter = ArrayVecOptionIntoIter<A, N>;
+    fn into_iter(self) -> Self::IntoIter {
+        self.0
+            .into_iter()
+            .map(|v| v.expect("ArrayVecOption value before .len() should not be None"))
+    }
+}
+
+// Implement `FromIterator` to enable `iter.collect::<ArrayVecOption<_>>()`
+impl<A, const N: usize> FromIterator<A> for ArrayVecOption<A, N> {
+    fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
+        Self(iter.into_iter().map(Some).collect())
+    }
+}
+
+impl<A, const N: usize> core::ops::Index<usize> for ArrayVecOption<A, N> {
+    type Output = A;
+    fn index(&self, index: usize) -> &Self::Output {
+        self.0[index].as_ref().unwrap()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    extern crate std;
+    use super::ArrayVecOption;
+    use std::vec;
+
+    #[test]
+    fn test_default_array_vec() {
+        let vec = ArrayVecOption::<u32, 5>::default();
+        assert_eq!(0, vec.len());
+        assert_eq!(None, vec.iter().next());
+        assert!(vec.is_empty());
+        assert_eq!(None, vec.first());
+        assert_eq!(None, vec.get(0));
+        assert_eq!(vec![0_u32; 0], vec.into_vec());
+    }
+
+    #[test]
+    fn test_array_vec_with_contents() {
+        let mut vec = ArrayVecOption::<u32, 5>::default();
+        vec.push(1);
+        vec.push(2);
+        vec.push(3);
+        vec.push(4);
+        vec.push(5);
+        assert_eq!(5, vec.len());
+        let mut iter = vec.iter();
+        assert_eq!(Some(&1_u32), iter.next());
+        assert_eq!(Some(&2_u32), iter.next());
+        assert_eq!(Some(&3_u32), iter.next());
+        assert_eq!(Some(&4_u32), iter.next());
+        assert_eq!(Some(&5_u32), iter.next());
+        assert_eq!(None, iter.next());
+        assert!(!vec.is_empty());
+        assert_eq!(Some(&1_u32), vec.first());
+        assert_eq!(Some(&5_u32), vec.get(4));
+        assert_eq!(vec![1_u32, 2, 3, 4, 5], vec.clone().into_vec());
+
+        vec.swap_remove(2);
+        assert_eq!(vec![1_u32, 2, 5, 4], vec.clone().into_vec());
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_array_vec_push_overflow() {
+        let mut vec = ArrayVecOption::<u32, 5>::default();
+        vec.push(1);
+        vec.push(2);
+        vec.push(3);
+        vec.push(4);
+        vec.push(5);
+        vec.push(6);
+    }
+
+    #[test]
+    fn test_sort() {
+        let mut vec = ArrayVecOption::<u32, 5>::default();
+        vec.push(3);
+        vec.push(1);
+        vec.push(4);
+        vec.push(1);
+        vec.push(2);
+        vec.sort_unstable_by_key(|k| *k);
+        assert_eq!(vec![1_u32, 1, 2, 3, 4], vec.clone().into_vec());
+    }
+
+    #[test]
+    fn test_collect() {
+        let vec: ArrayVecOption<u32, 5> = [5_u32, 4, 3, 2, 1].into_iter().collect();
+        assert_eq!(vec![5_u32, 4, 3, 2, 1], vec.clone().into_vec());
+    }
+}
diff --git a/nearby/presence/np_adv/src/credential/book.rs b/nearby/presence/np_adv/src/credential/book.rs
index 9e6361d..902d55a 100644
--- a/nearby/presence/np_adv/src/credential/book.rs
+++ b/nearby/presence/np_adv/src/credential/book.rs
@@ -35,6 +35,8 @@
 use crypto_provider::CryptoProvider;
 
 #[cfg(feature = "alloc")]
+extern crate alloc;
+#[cfg(feature = "alloc")]
 use alloc::vec::Vec;
 
 /// A collection of credentials to try when attempting to deserialize
diff --git a/nearby/presence/np_adv/src/credential/tests.rs b/nearby/presence/np_adv/src/credential/tests.rs
index 297fdb5..8b12bfc 100644
--- a/nearby/presence/np_adv/src/credential/tests.rs
+++ b/nearby/presence/np_adv/src/credential/tests.rs
@@ -13,6 +13,8 @@
 
 //! Tests of functionality related to credentials, credential-views, and credential suppliers.
 
+extern crate alloc;
+
 use crate::credential::{
     book::{
         init_cache_from_source, CachedCredentialSource, PossiblyCachedDiscoveryCryptoMaterialKind,
diff --git a/nearby/presence/np_adv/src/deser_v0_tests.rs b/nearby/presence/np_adv/src/deser_v0_tests.rs
index 2fb6f38..93c3b97 100644
--- a/nearby/presence/np_adv/src/deser_v0_tests.rs
+++ b/nearby/presence/np_adv/src/deser_v0_tests.rs
@@ -38,7 +38,7 @@
     HasIdentityMatch, PlaintextIdentityMode, PublicIdentity, V0AdvertisementContents,
 };
 use array_view::ArrayView;
-use core::{borrow::BorrowMut, marker::PhantomData};
+use core::marker::PhantomData;
 use crypto_provider::CryptoProvider;
 use crypto_provider_default::CryptoProviderImpl;
 use ldt_np_adv::LegacySalt;
@@ -61,10 +61,10 @@
             })
             .collect::<Vec<_>>();
 
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
         let cred_book =
             CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&creds, &[]);
-        let contents = deser_v0::<_, CryptoProviderImpl>(&mut arena, adv.as_slice(), &cred_book);
+        let contents = deser_v0::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book);
 
         assert_adv_equals(&adv_config, &contents);
     }
@@ -90,12 +90,12 @@
             })
             .collect::<Vec<_>>();
 
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
         let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(
             &credentials,
             &[],
         );
-        let contents = deser_v0::<_, CryptoProviderImpl>(&mut arena, adv.as_slice(), &cred_book);
+        let contents = deser_v0::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book);
 
         match adv_config.identity {
             // we ended up generating plaintext, so it's fine
@@ -118,10 +118,10 @@
 
         let creds = Vec::<MatchableCredential<V0, EmptyMatchedCredential>>::new();
 
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
         let cred_book =
             CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&creds, &[]);
-        let contents = deser_v0::<_, CryptoProviderImpl>(&mut arena, adv.as_slice(), &cred_book);
+        let contents = deser_v0::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book);
 
         match adv_config.identity {
             // we ended up generating plaintext, so it's fine
@@ -150,8 +150,8 @@
 
                 assert_eq!(adv_config.plaintext_mode.unwrap(), p.identity());
                 assert_eq!(
-                    vec![&PlainDataElement::Actions(de)],
-                    p.data_elements().collect::<Vec<_>>()
+                    vec![PlainDataElement::Actions(de)],
+                    p.data_elements().collect::<Result<Vec<_>, _>>().unwrap()
                 )
             }
             _ => panic!("should be a plaintext adv"),
@@ -166,8 +166,8 @@
                 let de = ActionsDataElement::from(action_bits);
 
                 assert_eq!(
-                    vec![&PlainDataElement::Actions(de)],
-                    wmc.contents().data_elements().collect::<Vec<_>>()
+                    vec![PlainDataElement::Actions(de)],
+                    wmc.contents().data_elements().collect::<Result<Vec<_>, _>>().unwrap()
                 );
                 assert_eq!(
                     adv_config.identity.unwrap().identity_type,
@@ -184,10 +184,10 @@
 }
 
 fn deser_v0<'adv, B, P>(
-    arena: impl BorrowMut<DeserializationArena<'adv>>,
+    arena: DeserializationArena<'adv>,
     adv: &'adv [u8],
     cred_book: &'adv B,
-) -> V0AdvertisementContents<B::Matched>
+) -> V0AdvertisementContents<'adv, B::Matched>
 where
     B: CredentialBook<'adv>,
     P: CryptoProvider,
diff --git a/nearby/presence/np_adv/src/deser_v1_tests.rs b/nearby/presence/np_adv/src/deser_v1_tests.rs
index 80f646e..903ed3d 100644
--- a/nearby/presence/np_adv/src/deser_v1_tests.rs
+++ b/nearby/presence/np_adv/src/deser_v1_tests.rs
@@ -68,12 +68,12 @@
             })
             .collect::<Vec<_>>();
 
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
         // check if the section is empty or there is more than one public section
         let cred_book =
             CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds);
         if section_configs.is_empty() {
-            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
             assert_eq!(
                 v1_error,
                 AdvDeserializationError::ParseError {
@@ -82,7 +82,7 @@
                 }
             ); //assert a adv deserialization error
         } else {
-            let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
             assert_eq!(0, v1_contents.invalid_sections_count());
             assert_eq!(section_configs.len(), v1_contents.sections.len());
             for (section_config, section) in section_configs.iter().zip(v1_contents.sections.iter())
@@ -111,12 +111,12 @@
                 match_data: EmptyMatchedCredential,
             })
             .collect::<Vec<_>>();
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
         // check if the section header would be 0 or if the section is empty
         let cred_book =
             CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds);
         if section_configs.is_empty() {
-            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
             assert_eq!(
                 v1_error,
                 AdvDeserializationError::ParseError {
@@ -125,7 +125,7 @@
                 }
             ); //assert a adv deserialization error
         } else {
-            let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
             assert_eq!(0, v1_contents.invalid_sections_count());
             assert_eq!(section_configs.len(), v1_contents.sections.len());
             for (section_config, section) in section_configs.iter().zip(v1_contents.sections.iter())
@@ -161,12 +161,12 @@
             })
             .collect::<Vec<_>>();
 
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
         // check if the section header would be 0
         let cred_book =
             CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds);
         if section_configs.is_empty() {
-            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
             assert_eq!(
                 v1_error,
                 AdvDeserializationError::ParseError {
@@ -175,7 +175,7 @@
                 }
             ); //assert a adv deserialization error
         } else {
-            let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
             // all encrypted identity sections are invalid
             let encrypted_section_count =
                 section_configs.iter().filter(|sc| sc.identity.is_some()).count();
@@ -206,7 +206,7 @@
             &mut adv_builder,
         );
         let adv = adv_builder.into_advertisement();
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
         // check if the section header would be 0
         let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
             0,
@@ -214,7 +214,7 @@
             CryptoProviderImpl,
         >(&[], &[]);
         if section_configs.is_empty() {
-            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
             assert_eq!(
                 v1_error,
                 AdvDeserializationError::ParseError {
@@ -223,7 +223,7 @@
                 }
             ); //assert a adv deserialization error
         } else {
-            let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
             // all encrypted identity sections are invalid
             let encrypted_section_count =
                 section_configs.iter().filter(|sc| sc.identity.is_some()).count();
@@ -276,14 +276,14 @@
             })
             .collect::<Vec<_>>();
 
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
 
         let cred_book =
             CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds);
 
         // check if the section header would be 0
         if section_configs.is_empty() {
-            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
             assert_eq!(
                 v1_error,
                 AdvDeserializationError::ParseError {
@@ -292,7 +292,7 @@
                 }
             ); //assert a adv deserialization error
         } else {
-            let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+            let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, &adv, &cred_book);
 
             let affected_sections: Vec<_> = section_configs
                 .iter()
@@ -367,7 +367,7 @@
 }
 
 fn deser_v1_error<'a, B, P>(
-    arena: &'a mut DeserializationArena<'a>,
+    arena: DeserializationArena<'a>,
     adv: &'a EncodedAdvertisement,
     cred_book: &'a B,
 ) -> AdvDeserializationError
@@ -383,7 +383,7 @@
 }
 
 fn deser_v1<'adv, B, P>(
-    arena: &'adv mut DeserializationArena<'adv>,
+    arena: DeserializationArena<'adv>,
     adv: &'adv EncodedAdvertisement,
     cred_book: &'adv B,
 ) -> V1AdvertisementContents<'adv, B::Matched>
diff --git a/nearby/presence/np_adv/src/deserialization_arena.rs b/nearby/presence/np_adv/src/deserialization_arena.rs
index 6b5f651..9b58cbf 100644
--- a/nearby/presence/np_adv/src/deserialization_arena.rs
+++ b/nearby/presence/np_adv/src/deserialization_arena.rs
@@ -27,24 +27,19 @@
     // Reference: https://doc.rust-lang.org/reference/destructors.html#temporary-lifetime-extension
     () => {
         $crate::deserialization_arena::DeserializationArena {
-            slice: &mut $crate::deserialization_arena::DeserializationArena::buffer(),
+            buffer: &mut $crate::deserialization_arena::DeserializationArena::new_buffer(),
         }
     };
 }
 
 /// A simple allocator that simply keeps splitting the given slice on allocation. Use the
 /// [`deserialization_arena!`][crate::deserialization_arena!] macro to create an instance.
-pub struct DeserializationArena<'a> {
-    #[doc(hidden)] // Exposed for use by `deserialization_arena!` only.
-    pub slice: &'a mut [u8],
+pub(crate) struct DeserializationArenaAllocator<'a> {
+    #[doc(hidden)]
+    slice: &'a mut [u8],
 }
 
-impl<'a> DeserializationArena<'a> {
-    #[doc(hidden)] // Exposed for use by `deserialization_arena!` only.
-    pub fn buffer() -> [u8; BLE_ADV_SVC_CONTENT_LEN] {
-        [0; BLE_ADV_SVC_CONTENT_LEN]
-    }
-
+impl<'a> DeserializationArenaAllocator<'a> {
     /// Allocates `len` bytes from the slice given upon construction. In the expected use case, the
     /// allocated slice should be written to with actual data, overwriting what's contained in
     /// there. While reading from the allocated slice without first writing to it is safe in the
@@ -59,17 +54,36 @@
         }
         let (allocated, remaining) = core::mem::take(&mut self.slice).split_at_mut(len.into());
         self.slice = remaining;
-        // Note: the returned data is logically garbage, but in practice it's all zeroes assuming
-        // `deserialization_arena!` was used to create this allocator.
+        // Note: the returned data is logically garbage. While it's deterministic (not UB),
+        // semantically this should be treated as a write only slice.
         Ok(allocated)
     }
 }
 
+/// A simple allocator that simply keeps splitting the given slice on allocation. Use the
+/// [`deserialization_arena!`][crate::deserialization_arena!] macro to create an instance.
+pub struct DeserializationArena<'a> {
+    #[doc(hidden)] // Exposed for use by `deserialization_arena!` only.
+    pub buffer: &'a mut [u8; BLE_ADV_SVC_CONTENT_LEN],
+}
+
+impl<'a> DeserializationArena<'a> {
+    #[doc(hidden)] // Exposed for use by `deserialization_arena!` only.
+    pub fn new_buffer() -> [u8; BLE_ADV_SVC_CONTENT_LEN] {
+        [0; BLE_ADV_SVC_CONTENT_LEN]
+    }
+
+    /// Convert this arena into an allocator that can start allocating.
+    pub(crate) fn into_allocator(self) -> DeserializationArenaAllocator<'a> {
+        DeserializationArenaAllocator { slice: self.buffer }
+    }
+}
+
 /// Error indicating that the arena has ran out of space, and deserialization cannot proceed. This
 /// should never happen if the arena is created with [`crate::deserialization_arena!`], since the
 /// total size of decrypted sections should be less than the size of the incoming BLE advertisement.
 #[derive(Debug, PartialEq, Eq)]
-pub struct ArenaOutOfSpace;
+pub(crate) struct ArenaOutOfSpace;
 
 #[cfg(test)]
 mod test {
@@ -77,26 +91,29 @@
 
     #[test]
     fn test_creation() {
-        assert_eq!(BLE_ADV_SVC_CONTENT_LEN, deserialization_arena!().slice.len());
+        assert_eq!(BLE_ADV_SVC_CONTENT_LEN, deserialization_arena!().buffer.len());
     }
 
     #[test]
     fn test_allocation() {
-        let mut arena = deserialization_arena!();
-        assert_eq!(Ok(&mut [0_u8; 100][..]), arena.allocate(100));
-        assert_eq!(BLE_ADV_SVC_CONTENT_LEN - 100, arena.slice.len());
+        let arena = deserialization_arena!();
+        let mut allocator = arena.into_allocator();
+        assert_eq!(Ok(&mut [0_u8; 100][..]), allocator.allocate(100));
+        assert_eq!(BLE_ADV_SVC_CONTENT_LEN - 100, allocator.slice.len());
     }
 
     #[test]
     fn test_allocation_overflow() {
-        let mut arena = deserialization_arena!();
-        assert_eq!(Err(ArenaOutOfSpace), arena.allocate(u8::MAX));
+        let arena = deserialization_arena!();
+        let mut allocator = arena.into_allocator();
+        assert_eq!(Err(ArenaOutOfSpace), allocator.allocate(u8::MAX));
     }
 
     #[test]
     fn test_allocation_twice_overflow() {
-        let mut arena = deserialization_arena!();
-        assert_eq!(Ok(&mut [0_u8; 128][..]), arena.allocate(128));
-        assert_eq!(Err(ArenaOutOfSpace), arena.allocate(128));
+        let arena = deserialization_arena!();
+        let mut allocator = arena.into_allocator();
+        assert_eq!(Ok(&mut [0_u8; 128][..]), allocator.allocate(128));
+        assert_eq!(Err(ArenaOutOfSpace), allocator.allocate(128));
     }
 }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
index dfab7ce..9bd54b8 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
@@ -99,10 +99,11 @@
     let verification_material =
         discovery_credential.unsigned_verification_material::<CryptoProviderImpl>();
 
-    let mut arena = deserialization_arena!();
+    let arena = deserialization_arena!();
+    let mut allocator = arena.into_allocator();
     let section = contents
         .try_resolve_identity_and_deserialize::<CryptoProviderImpl>(
-            &mut arena,
+            &mut allocator,
             &identity_resolution_material,
             &verification_material,
         )
@@ -179,10 +180,11 @@
                 &identity_resolution_material.into_raw_resolution_material(),
             )
             .unwrap();
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
+        let mut allocator = arena.into_allocator();
         let decrypted = contents
             .contents
-            .decrypt_ciphertext::<CryptoProviderImpl>(&mut arena, identity_match)
+            .decrypt_ciphertext::<CryptoProviderImpl>(&mut allocator, identity_match)
             .unwrap();
 
         let mut expected = Vec::new();
@@ -366,7 +368,7 @@
         error,
         contents
             .try_resolve_identity_and_deserialize::<C>(
-                &mut deserialization_arena!(),
+                &mut deserialization_arena!().into_allocator(),
                 &identity_resolution_material,
                 &verification_material,
             )
@@ -447,7 +449,7 @@
         discovery_credential.unsigned_verification_material::<CryptoProviderImpl>();
 
     match contents.try_resolve_identity_and_deserialize::<CryptoProviderImpl>(
-        &mut deserialization_arena!(),
+        &mut deserialization_arena!().into_allocator(),
         &identity_resolution_material,
         &verification_material,
     ) {
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
index 0afeb08..2236630 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
@@ -14,7 +14,7 @@
 
 use crate::{
     credential::v1::*,
-    deserialization_arena::DeserializationArena,
+    deserialization_arena::DeserializationArenaAllocator,
     extended::{
         deserialize::{
             DecryptedSection, EncryptedIdentityMetadata, EncryptionInfo, SectionContents,
@@ -27,6 +27,8 @@
 };
 
 #[cfg(any(feature = "devtools", test))]
+extern crate alloc;
+#[cfg(any(feature = "devtools", test))]
 use alloc::vec::Vec;
 #[cfg(feature = "devtools")]
 use array_view::ArrayView;
@@ -189,7 +191,7 @@
     /// and returns the raw bytes of the decrypted plaintext.
     pub(crate) fn decrypt_ciphertext<C: CryptoProvider>(
         &self,
-        arena: &mut DeserializationArena<'a>,
+        arena: &mut DeserializationArenaAllocator<'a>,
         mut identity_match: IdentityMatch<C>,
     ) -> Result<RawDecryptedSection<'a>, ArenaOutOfSpace> {
         // Fill decrypt_buf with the ciphertext after the metadata key
@@ -213,12 +215,12 @@
     #[cfg(feature = "devtools")]
     pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>(
         &self,
-        arena: &mut DeserializationArena<'a>,
+        allocator: &mut DeserializationArenaAllocator<'a>,
         identity_resolution_material: &SectionIdentityResolutionMaterial,
     ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
         let identity_resolution_contents = self.compute_identity_resolution_contents::<P>();
         identity_resolution_contents.try_match(identity_resolution_material).map(|identity_match| {
-            let decrypted_section = self.decrypt_ciphertext::<P>(arena, identity_match)?;
+            let decrypted_section = self.decrypt_ciphertext::<P>(allocator, identity_match)?;
             Ok(decrypted_section.to_raw_bytes())
         })
     }
@@ -235,7 +237,7 @@
     /// with some paired verification material for the matched identity.
     pub(crate) fn try_deserialize<P>(
         &self,
-        arena: &mut DeserializationArena<'a>,
+        arena: &mut DeserializationArenaAllocator<'a>,
         identity_match: IdentityMatch<P>,
         verification_material: &SignedSectionVerificationMaterial,
     ) -> Result<DecryptedSection<'a>, DeserializationError<SignatureVerificationError>>
@@ -294,11 +296,11 @@
     #[cfg(feature = "devtools")]
     pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>(
         &self,
-        arena: &mut DeserializationArena<'a>,
+        allocator: &mut DeserializationArenaAllocator<'a>,
         identity_resolution_material: &SignedSectionIdentityResolutionMaterial,
     ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
         self.contents.try_resolve_identity_and_decrypt::<P>(
-            arena,
+            allocator,
             identity_resolution_material.as_raw_resolution_material(),
         )
     }
@@ -307,7 +309,7 @@
     #[cfg(test)]
     pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>(
         &self,
-        arena: &mut DeserializationArena<'a>,
+        allocator: &mut DeserializationArenaAllocator<'a>,
         identity_resolution_material: &SignedSectionIdentityResolutionMaterial,
         verification_material: &SignedSectionVerificationMaterial,
     ) -> Result<
@@ -320,7 +322,7 @@
             .try_match::<P>(identity_resolution_material.as_raw_resolution_material())
         {
             Some(identity_match) => self
-                .try_deserialize(arena, identity_match, verification_material)
+                .try_deserialize(allocator, identity_match, verification_material)
                 .map_err(|e| e.into()),
             None => Err(IdentityResolutionOrDeserializationError::IdentityMatchingError),
         }
@@ -413,14 +415,14 @@
     /// Returns an error if the credential is incorrect or if the section data is malformed.
     pub(crate) fn try_deserialize<P>(
         &self,
-        arena: &mut DeserializationArena<'a>,
+        allocator: &mut DeserializationArenaAllocator<'a>,
         identity_match: IdentityMatch<P>,
         verification_material: &UnsignedSectionVerificationMaterial,
     ) -> Result<DecryptedSection<'a>, DeserializationError<MicVerificationError>>
     where
         P: CryptoProvider,
     {
-        let raw_decrypted = self.contents.decrypt_ciphertext(arena, identity_match)?;
+        let raw_decrypted = self.contents.decrypt_ciphertext(allocator, identity_match)?;
         let metadata_key = raw_decrypted.metadata_key_plaintext;
         let nonce = raw_decrypted.nonce;
         let remaining_des = raw_decrypted.plaintext_contents;
@@ -455,11 +457,11 @@
     #[cfg(feature = "devtools")]
     pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>(
         &self,
-        arena: &mut DeserializationArena<'a>,
+        allocator: &mut DeserializationArenaAllocator<'a>,
         identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial,
     ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
         self.contents.try_resolve_identity_and_decrypt::<P>(
-            arena,
+            allocator,
             identity_resolution_material.as_raw_resolution_material(),
         )
     }
@@ -468,7 +470,7 @@
     #[cfg(test)]
     pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>(
         &self,
-        arena: &mut DeserializationArena<'a>,
+        allocator: &mut DeserializationArenaAllocator<'a>,
         identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial,
         verification_material: &UnsignedSectionVerificationMaterial,
     ) -> Result<DecryptedSection, IdentityResolutionOrDeserializationError<MicVerificationError>>
@@ -479,7 +481,7 @@
             .try_match::<P>(identity_resolution_material.as_raw_resolution_material())
         {
             Some(identity_match) => self
-                .try_deserialize(arena, identity_match, verification_material)
+                .try_deserialize(allocator, identity_match, verification_material)
                 .map_err(|e| e.into()),
             None => Err(IdentityResolutionOrDeserializationError::IdentityMatchingError),
         }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
index 10fc46b..2370aeb 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
@@ -143,8 +143,10 @@
             )
             .unwrap();
 
-        let mut arena = deserialization_arena!();
-        let decrypted = contents.contents.decrypt_ciphertext(&mut arena, identity_match).unwrap();
+        let arena = deserialization_arena!();
+        let mut allocator = arena.into_allocator();
+        let decrypted =
+            contents.contents.decrypt_ciphertext(&mut allocator, identity_match).unwrap();
 
         let mut expected = Vec::new();
         expected.extend_from_slice(txpower_de.de_header().serialize().as_slice());
@@ -186,10 +188,11 @@
         let signed_verification_material =
             crypto_material.signed_verification_material::<CryptoProviderImpl>();
 
-        let mut arena = deserialization_arena!();
+        let arena = deserialization_arena!();
+        let mut allocator = arena.into_allocator();
         let section = contents
             .try_resolve_identity_and_deserialize::<CryptoProviderImpl>(
-                &mut arena,
+                &mut allocator,
                 &signed_identity_resolution_material,
                 &signed_verification_material,
             )
@@ -424,7 +427,7 @@
         error,
         contents
             .try_resolve_identity_and_deserialize::<C>(
-                &mut deserialization_arena!(),
+                &mut deserialization_arena!().into_allocator(),
                 &signed_identity_resolution_material,
                 &signed_verification_material,
             )
@@ -533,7 +536,7 @@
         crypto_material.signed_verification_material::<CryptoProviderImpl>();
 
     match contents.try_resolve_identity_and_deserialize::<CryptoProviderImpl>(
-        &mut deserialization_arena!(),
+        &mut deserialization_arena!().into_allocator(),
         &identity_resolution_material,
         &verification_material,
     ) {
diff --git a/nearby/presence/np_adv/src/extended/deserialize/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/mod.rs
index d431325..4b78a32 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/mod.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/mod.rs
@@ -14,8 +14,10 @@
 
 //! Deserialization for V1 advertisement contents
 
+#[cfg(any(test, feature = "alloc"))]
 extern crate alloc;
 
+#[cfg(any(test, feature = "alloc"))]
 use alloc::vec::Vec;
 use core::array::TryFromSliceError;
 
@@ -27,12 +29,13 @@
 use crypto_provider::CryptoProvider;
 use np_hkdf::v1_salt::{self, V1Salt};
 
+use crate::array_vec::ArrayVecOption;
 #[cfg(any(feature = "devtools", test))]
 use crate::credential::v1::V1DiscoveryCryptoMaterial;
 use crate::credential::v1::V1;
 use crate::deserialization_arena::ArenaOutOfSpace;
 #[cfg(any(feature = "devtools", test))]
-use crate::deserialization_arena::DeserializationArena;
+use crate::deserialization_arena::DeserializationArenaAllocator;
 #[cfg(test)]
 use crate::extended::deserialize::encrypted_section::IdentityResolutionOrDeserializationError;
 use crate::extended::{NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT};
@@ -66,19 +69,32 @@
 pub(crate) fn parse_sections(
     adv_header: V1Header,
     adv_body: &[u8],
-) -> Result<Vec<IntermediateSection>, nom::Err<error::Error<&[u8]>>> {
+) -> Result<
+    ArrayVecOption<IntermediateSection, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT>,
+    nom::Err<error::Error<&[u8]>>,
+> {
     combinator::all_consuming(branch::alt((
         // Public advertisement
-        multi::many_m_n(
+        multi::fold_many_m_n(
             1,
             NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT,
             IntermediateSection::parser_public_section(),
+            ArrayVecOption::default,
+            |mut acc, item| {
+                acc.push(item);
+                acc
+            },
         ),
         // Encrypted advertisement
-        multi::many_m_n(
+        multi::fold_many_m_n(
             1,
             NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT,
             IntermediateSection::parser_encrypted_with_header(adv_header),
+            ArrayVecOption::default,
+            |mut acc, item| {
+                acc.push(item);
+                acc
+            },
         ),
     )))(adv_body)
     .map(|(_rem, sections)| sections)
@@ -395,7 +411,7 @@
     #[cfg(test)]
     pub(crate) fn try_resolve_identity_and_deserialize<C, P>(
         &'a self,
-        arena: &mut DeserializationArena<'a>,
+        allocator: &mut DeserializationArenaAllocator<'a>,
         crypto_material: &C,
     ) -> Result<DecryptedSection<'a>, SectionDeserializeError>
     where
@@ -410,7 +426,7 @@
 
                 contents
                     .try_resolve_identity_and_deserialize::<P>(
-                        arena,
+                        allocator,
                         &identity_resolution_material,
                         &verification_material,
                     )
@@ -423,7 +439,7 @@
 
                 contents
                     .try_resolve_identity_and_deserialize::<P>(
-                        arena,
+                        allocator,
                         &identity_resolution_material,
                         &verification_material,
                     )
@@ -439,19 +455,19 @@
         P: CryptoProvider,
     >(
         &self,
-        arena: &mut DeserializationArena<'a>,
+        allocator: &mut DeserializationArenaAllocator<'a>,
         crypto_material: &C,
     ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
         match self {
             CiphertextSection::SignatureEncryptedIdentity(x) => {
                 let identity_resolution_material =
                     crypto_material.signed_identity_resolution_material::<P>();
-                x.try_resolve_identity_and_decrypt::<P>(arena, &identity_resolution_material)
+                x.try_resolve_identity_and_decrypt::<P>(allocator, &identity_resolution_material)
             }
             CiphertextSection::MicEncryptedIdentity(x) => {
                 let identity_resolution_material =
                     crypto_material.unsigned_identity_resolution_material::<P>();
-                x.try_resolve_identity_and_decrypt::<P>(arena, &identity_resolution_material)
+                x.try_resolve_identity_and_decrypt::<P>(allocator, &identity_resolution_material)
             }
         }
     }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
index 506a127..4afa3ec 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
@@ -36,7 +36,8 @@
     // de 2 byte header, type 6, len 1
     adv_body.extend_from_slice(&[0x81, 0x06, 0x01]);
 
-    let parsed_sections = parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap();
+    let parsed_sections =
+        parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap().into_vec();
     assert_eq!(
         vec![IntermediateSection::from(PlaintextSection::new(
             PlaintextIdentityMode::Public,
@@ -168,7 +169,7 @@
         },
     ];
     let parsed_sections = parse_sections(adv_header, &adv_body).unwrap();
-    assert_eq!(parsed_sections, &expected_sections.map(IntermediateSection::from));
+    assert_eq!(parsed_sections.into_vec(), expected_sections.map(IntermediateSection::from));
 }
 
 #[test]
@@ -296,7 +297,7 @@
         },
     ];
     let parsed_sections = parse_sections(adv_header, &adv_body).unwrap();
-    assert_eq!(parsed_sections, &expected_sections.map(IntermediateSection::from));
+    assert_eq!(parsed_sections.into_vec(), &expected_sections.map(IntermediateSection::from));
 }
 
 #[test]
diff --git a/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
index 39644ab..5b1290f 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
@@ -16,6 +16,7 @@
 
 use super::*;
 use crate::deserialization_arena;
+use crate::deserialization_arena::DeserializationArena;
 use crate::extended::serialize::AdvertisementType;
 use crate::extended::NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT;
 use crate::{
@@ -36,7 +37,7 @@
     },
     parse_adv_header, AdvHeader, WithMatchedCredential,
 };
-use core::borrow::{Borrow, BorrowMut};
+use core::borrow::Borrow;
 use core::convert::Into;
 use crypto_provider::{CryptoProvider, CryptoRng};
 use crypto_provider_default::CryptoProviderImpl;
@@ -569,7 +570,7 @@
 /// - `Ok(None)` if no matching credential was found, or if `cred_source` provides no credentials
 /// - `Err` if an error occurred.
 fn try_deserialize_all_creds<'a, S, P>(
-    mut arena: impl BorrowMut<DeserializationArena<'a>>,
+    arena: DeserializationArena<'a>,
     section: &'a CiphertextSection,
     cred_source: &'a S,
 ) -> Result<TryDeserOutput<'a, S::Matched>, BatchSectionDeserializeError>
@@ -577,11 +578,11 @@
     S: DiscoveryCredentialSource<'a, V1>,
     P: CryptoProvider,
 {
+    let mut allocator = arena.into_allocator();
     for (crypto_material, match_data) in cred_source.iter() {
-        match section.try_resolve_identity_and_deserialize::<_, P>(
-            arena.borrow_mut(),
-            crypto_material.borrow(),
-        ) {
+        match section
+            .try_resolve_identity_and_deserialize::<_, P>(&mut allocator, crypto_material.borrow())
+        {
             Ok(s) => {
                 let metadata_nonce = crypto_material.metadata_nonce::<P>();
                 return Ok(Some(WithMatchedCredential::new(match_data, metadata_nonce, s)));
diff --git a/nearby/presence/np_adv/src/filter/mod.rs b/nearby/presence/np_adv/src/filter/mod.rs
index cb020cf..a2b2d9c 100644
--- a/nearby/presence/np_adv/src/filter/mod.rs
+++ b/nearby/presence/np_adv/src/filter/mod.rs
@@ -17,6 +17,7 @@
 //! it matched with. Used as a first pass option to quickly check if a buffer should
 //! further processed.
 use crate::credential::MatchedCredential;
+use crate::legacy::data_elements::DataElementDeserializeError;
 use crate::legacy::deserialize::DecryptedAdvContents;
 use crate::{
     credential::{book::CredentialBook, v0::V0DiscoveryCryptoMaterial},
@@ -157,7 +158,7 @@
             IntermediateAdvContents::Plaintext(p) => match self.identity {
                 IdentityFilterType::Public | IdentityFilterType::Any => self
                     .data_elements
-                    .match_v0_legible_adv(p.data_elements())
+                    .match_v0_legible_adv(|| p.data_elements())
                     .map(|()| FilterResult::Public),
                 _ => Err(NoMatch),
             },
@@ -165,7 +166,7 @@
                 IdentityFilterType::Private | IdentityFilterType::Any => {
                     let (legible_adv, m) = try_decrypt_and_match::<B, P>(cred_book.v0_iter(), &c)?;
                     self.data_elements
-                        .match_v0_legible_adv(legible_adv.data_elements())
+                        .match_v0_legible_adv(|| legible_adv.data_elements())
                         .map(|()| FilterResult::Private(m))
                 }
                 _ => Err(NoMatch),
@@ -174,12 +175,12 @@
     }
 }
 
-fn try_decrypt_and_match<'a, B, P>(
+fn try_decrypt_and_match<'cred, B, P>(
     v0_creds: B::V0Iterator,
     adv: &EncryptedAdvContents,
 ) -> Result<(DecryptedAdvContents, B::Matched), NoMatch>
 where
-    B: CredentialBook<'a>,
+    B: CredentialBook<'cred>,
     P: CryptoProvider,
 {
     for (crypto_material, m) in v0_creds {
@@ -199,14 +200,15 @@
 
 impl V0DataElementsFilter {
     /// A legible adv is either plaintext to begin with, or decrypted contents from an encrypted adv
-    fn match_v0_legible_adv<'a, F: PacketFlavor + 'a>(
-        &self,
-        mut data_elements: impl ExactSizeIterator<Item = &'a PlainDataElement<F>>,
-    ) -> Result<(), NoMatch> {
+    fn match_v0_legible_adv<F, I>(&self, data_elements: impl Fn() -> I) -> Result<(), NoMatch>
+    where
+        F: PacketFlavor,
+        I: Iterator<Item = Result<PlainDataElement<F>, DataElementDeserializeError>>,
+    {
         match &self.contains_tx_power {
             None => Ok(()),
             Some(c) => {
-                if c == &data_elements.any(|de| matches!(de, PlainDataElement::TxPower(_))) {
+                if c == &data_elements().any(|de| matches!(de, Ok(PlainDataElement::TxPower(_)))) {
                     Ok(())
                 } else {
                     Err(NoMatch)
@@ -217,13 +219,16 @@
         match &self.actions_filter {
             None => Ok(()),
             Some(filter) => {
+                if let Some(_err) = data_elements().find_map(|result| result.err()) {
+                    return Err(NoMatch);
+                }
                 // find if an actions DE exists, if so match on the provided action filter
-                let actions = data_elements.find_map(|de| match de {
-                    PlainDataElement::Actions(actions) => Some(actions),
+                let actions = data_elements().find_map(|de| match de {
+                    Ok(PlainDataElement::Actions(actions)) => Some(actions),
                     _ => None,
                 });
                 if let Some(actions) = actions {
-                    filter.match_v0_actions(actions)
+                    filter.match_v0_actions(&actions)
                 } else {
                     return Err(NoMatch);
                 }
diff --git a/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs b/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs
index cb7c067..4a893a2 100644
--- a/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs
+++ b/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs
@@ -23,16 +23,17 @@
     let filter = V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: None };
 
     let tx_power = TxPower::try_from(5).expect("within range");
-    let tx_power_de = TxPowerDataElement::from(tx_power);
-    let result =
-        filter.match_v0_legible_adv([PlainDataElement::<Ciphertext>::TxPower(tx_power_de)].iter());
+    let result = filter.match_v0_legible_adv(|| {
+        [Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone())))]
+            .into_iter()
+    });
     assert_eq!(result, Ok(()))
 }
 
 #[test]
 fn match_not_contains_tx_power() {
     let filter = V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: None };
-    let result = filter.match_v0_legible_adv::<Plaintext>([].iter());
+    let result = filter.match_v0_legible_adv::<Plaintext, _>(|| [].into_iter());
     assert_eq!(result, Err(NoMatch))
 }
 
@@ -42,9 +43,10 @@
         .expect("1 is a valid length");
     let filter = V0DataElementsFilter { contains_tx_power: None, actions_filter: Some(filter) };
     let tx_power = TxPower::try_from(5).expect("within range");
-    let tx_power_de = TxPowerDataElement::from(tx_power);
-    let result =
-        filter.match_v0_legible_adv([PlainDataElement::<Ciphertext>::TxPower(tx_power_de)].iter());
+    let result = filter.match_v0_legible_adv(|| {
+        [Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone())))]
+            .into_iter()
+    });
     assert_eq!(result, Err(NoMatch))
 }
 
@@ -54,18 +56,17 @@
         .expect("1 is a valid length");
     let filter = V0DataElementsFilter { contains_tx_power: None, actions_filter: Some(filter) };
     let tx_power = TxPower::try_from(5).expect("within range");
-    let tx_power_de = TxPowerDataElement::from(tx_power);
 
     let mut action_bits = ActionBits::<Ciphertext>::default();
     action_bits.set_action(ActiveUnlock::from(true));
 
-    let result = filter.match_v0_legible_adv(
+    let result = filter.match_v0_legible_adv(|| {
         [
-            PlainDataElement::<Ciphertext>::TxPower(tx_power_de),
-            PlainDataElement::Actions(action_bits.into()),
+            Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone()))),
+            Ok(PlainDataElement::Actions(action_bits.into())),
         ]
-        .iter(),
-    );
+        .into_iter()
+    });
     assert_eq!(result, Ok(()))
 }
 
@@ -76,18 +77,17 @@
     let filter =
         V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: Some(filter) };
     let tx_power = TxPower::try_from(5).expect("within range");
-    let tx_power_de = TxPowerDataElement::from(tx_power);
 
     let mut action_bits = ActionBits::<Ciphertext>::default();
     action_bits.set_action(ActiveUnlock::from(true));
 
-    let result = filter.match_v0_legible_adv(
+    let result = filter.match_v0_legible_adv(|| {
         [
-            PlainDataElement::<Ciphertext>::TxPower(tx_power_de),
-            PlainDataElement::Actions(action_bits.into()),
+            Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone()))),
+            Ok(PlainDataElement::Actions(action_bits.into())),
         ]
-        .iter(),
-    );
+        .into_iter()
+    });
     assert_eq!(result, Ok(()))
 }
 
@@ -98,9 +98,10 @@
     let filter =
         V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: Some(filter) };
     let tx_power = TxPower::try_from(5).expect("within range");
-    let tx_power_de = TxPowerDataElement::from(tx_power);
 
-    let result =
-        filter.match_v0_legible_adv([PlainDataElement::<Ciphertext>::TxPower(tx_power_de)].iter());
+    let result = filter.match_v0_legible_adv(|| {
+        [Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone())))]
+            .into_iter()
+    });
     assert_eq!(result, Err(NoMatch))
 }
diff --git a/nearby/presence/np_adv/src/legacy/data_elements.rs b/nearby/presence/np_adv/src/legacy/data_elements.rs
index 505fdd9..c97cb25 100644
--- a/nearby/presence/np_adv/src/legacy/data_elements.rs
+++ b/nearby/presence/np_adv/src/legacy/data_elements.rs
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 //! V0 data elements and core trait impls.
+use nom::error::{ErrorKind, FromExternalError};
+
 use crate::legacy::{
     de_type::{DataElementType, PlainDataElementType},
     serialize::{DataElementBundle, ToDataElementBundle},
@@ -53,6 +55,37 @@
         /// The DE type attempting to be deserialized
         de_type: DataElementType,
     },
+    /// Only one identity data element is allowed in an advertisement, but a duplicate is found
+    /// while parsing.
+    DuplicateIdentityDataElement,
+    /// There is unexpected data remaining in the incoming payload.
+    UnexpectedDataRemaining,
+    /// Parsing error returned from Nom.
+    NomError(nom::error::ErrorKind),
+}
+
+impl FromExternalError<&[u8], DataElementDeserializeError> for DataElementDeserializeError {
+    fn from_external_error(
+        _input: &[u8],
+        _kind: ErrorKind,
+        e: DataElementDeserializeError,
+    ) -> Self {
+        e
+    }
+}
+
+impl nom::error::ParseError<&[u8]> for DataElementDeserializeError {
+    /// Creates an error from the input position and an [ErrorKind]
+    fn from_error_kind(_input: &[u8], kind: ErrorKind) -> Self {
+        Self::NomError(kind)
+    }
+
+    /// Combines an existing error with a new one created from the input
+    /// position and an [ErrorKind]. This is useful when backtracking
+    /// through a parse tree, accumulating error context on the way
+    fn append(_input: &[u8], kind: ErrorKind, _other: Self) -> Self {
+        Self::NomError(kind)
+    }
 }
 
 /// Data element holding a [TxPower].
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
index 48555be..938045d 100644
--- a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
+++ b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
@@ -16,7 +16,7 @@
 //!
 //! This module only deals with the _contents_ of an advertisement, not the advertisement header.
 
-extern crate alloc;
+use core::marker::PhantomData;
 
 use crate::{
     credential::v0::V0,
@@ -29,10 +29,12 @@
     },
     HasIdentityMatch, PlaintextIdentityMode,
 };
-use alloc::vec::Vec;
+use array_view::ArrayView;
 use crypto_provider::CryptoProvider;
 use ldt_np_adv::{LegacySalt, NP_LEGACY_METADATA_KEY_LEN};
-use nom::{bytes, combinator, multi, number, sequence};
+use nom::{bytes, combinator, number, sequence};
+
+use super::BLE_ADV_SVC_CONTENT_LEN;
 
 #[cfg(test)]
 mod tests;
@@ -41,16 +43,14 @@
 /// ciphertext.
 pub(crate) fn deserialize_adv_contents<C: CryptoProvider>(
     input: &[u8],
-) -> Result<IntermediateAdvContents, AdvDeserializeError> {
+) -> Result<IntermediateAdvContents<'_>, AdvDeserializeError> {
     parse_raw_adv_contents::<C>(input).and_then(|raw_adv| match raw_adv {
-        RawAdvertisement::Plaintext(parc) => {
-            if parc.data_elements.is_empty() {
+        RawAdvertisement::Plaintext(adv_contents) => {
+            if adv_contents.data_elements().next().is_none() {
                 return Err(AdvDeserializeError::NoPublicDataElements);
             }
 
-            parc.try_deserialize()
-                .map(IntermediateAdvContents::Plaintext)
-                .map_err(AdvDeserializeError::DataElementDeserializeError)
+            Ok(IntermediateAdvContents::Plaintext(adv_contents))
         }
         RawAdvertisement::Ciphertext(eac) => Ok(IntermediateAdvContents::Ciphertext(eac)),
     })
@@ -63,50 +63,56 @@
 fn parse_raw_adv_contents<C: CryptoProvider>(
     input: &[u8],
 ) -> Result<RawAdvertisement, AdvDeserializeError> {
-    let (_, data_elements) = parse_data_elements(input)
-        .map_err(|_e| AdvDeserializeError::AdvertisementDeserializeError)?;
-
-    if let Some(identity_de_type) =
-        data_elements.first().and_then(|de| de.de_type.try_as_identity_de_type())
-    {
-        match identity_de_type.as_encrypted_identity_de_type() {
-            Some(encrypted_de_type) => {
-                if data_elements.len() == 1 {
-                    match encrypted_de_type {
-                        // TODO handle length=0 provisioned identity DEs
-                        EncryptedIdentityDataElementType::Private
-                        | EncryptedIdentityDataElementType::Trusted
-                        | EncryptedIdentityDataElementType::Provisioned => {
-                            combinator::map(
-                                parse_encrypted_identity_de_contents,
-                                |(salt, payload)| {
-                                    RawAdvertisement::Ciphertext(EncryptedAdvContents {
-                                        identity_type: encrypted_de_type,
-                                        salt_padder: ldt_np_adv::salt_padder::<16, C>(salt),
-                                        salt,
-                                        ciphertext: payload,
-                                    })
-                                },
-                            )(data_elements[0].contents)
-                            .map(|(_rem, contents)| contents)
-                            .map_err(|_e| AdvDeserializeError::AdvertisementDeserializeError)
+    if input.is_empty() {
+        return Err(AdvDeserializeError::MissingIdentity);
+    }
+    match parse_de(input) {
+        Ok((rem, identity_de)) => {
+            if let Some(identity_de_type) = identity_de.de_type.try_as_identity_de_type() {
+                match identity_de_type.as_encrypted_identity_de_type() {
+                    Some(encrypted_de_type) => {
+                        if matches!(parse_de(rem), Err(nom::Err::Error(..))) {
+                            match encrypted_de_type {
+                                // TODO handle length=0 provisioned identity DEs
+                                EncryptedIdentityDataElementType::Private
+                                | EncryptedIdentityDataElementType::Trusted
+                                | EncryptedIdentityDataElementType::Provisioned => combinator::map(
+                                    parse_encrypted_identity_de_contents,
+                                    |(salt, payload)| {
+                                        RawAdvertisement::Ciphertext(EncryptedAdvContents {
+                                            identity_type: encrypted_de_type,
+                                            salt_padder: ldt_np_adv::salt_padder::<16, C>(salt),
+                                            salt,
+                                            ciphertext: payload,
+                                        })
+                                    },
+                                )(
+                                    identity_de.contents,
+                                )
+                                .map(|(_rem, contents)| contents)
+                                .map_err(|_e| AdvDeserializeError::AdvertisementDeserializeError),
+                            }
+                        } else {
+                            Err(AdvDeserializeError::TooManyTopLevelDataElements)
                         }
                     }
-                } else {
-                    Err(AdvDeserializeError::TooManyTopLevelDataElements)
+                    // It's an identity de, but not encrypted, so it must be public, and the rest
+                    // must be plain
+                    None => Ok(RawAdvertisement::Plaintext(PlaintextAdvContents {
+                        identity_type: PlaintextIdentityMode::Public,
+                        data: rem,
+                    })),
                 }
+            } else {
+                Err(AdvDeserializeError::MissingIdentity)
             }
-            // It's an identity de, but not encrypted, so it must be public, and the rest must be
-            // plain
-            None => plain_data_elements(&data_elements[1..]).map(|pdes| {
-                RawAdvertisement::Plaintext(PlaintextAdvRawContents {
-                    identity_type: PlaintextIdentityMode::Public,
-                    data_elements: pdes,
-                })
-            }),
         }
-    } else {
-        Err(AdvDeserializeError::MissingIdentity)
+        Err(nom::Err::Error(_)) | Err(nom::Err::Failure(_)) => {
+            Err(AdvDeserializeError::AdvertisementDeserializeError)
+        }
+        Err(nom::Err::Incomplete(_)) => {
+            panic!("Should not hit Incomplete when using nom::complete parsers")
+        }
     }
 }
 
@@ -115,27 +121,16 @@
 pub(crate) enum AdvDeserializeError {
     /// Parsing the overall advertisement or DE structure failed
     AdvertisementDeserializeError,
-    /// Deserializing an individual DE from its DE contents failed
-    DataElementDeserializeError(DataElementDeserializeError),
     /// Must not have any other top level data elements if there is an encrypted identity DE
     TooManyTopLevelDataElements,
-    /// Must not have an identity DE inside an identity DE
-    InvalidDataElementHierarchy,
     /// Missing identity DE
     MissingIdentity,
     /// Non-identity DE contents must not be empty
     NoPublicDataElements,
 }
 
-/// Parse an advertisement's contents into raw DEs.
-///
-/// Consumes the entire input.
-fn parse_data_elements(adv_contents: &[u8]) -> nom::IResult<&[u8], Vec<RawDataElement>> {
-    combinator::all_consuming(multi::many0(parse_de))(adv_contents)
-}
-
 /// Parse an individual DE into its header and contents.
-fn parse_de(input: &[u8]) -> nom::IResult<&[u8], RawDataElement> {
+fn parse_de(input: &[u8]) -> nom::IResult<&[u8], RawDataElement, DataElementDeserializeError> {
     let (remaining, (de_type, actual_len)) =
         combinator::map_opt(number::complete::u8, |de_header| {
             // header: LLLLTTTT
@@ -159,22 +154,6 @@
     })(remaining)
 }
 
-/// Returns `Err`` if any DEs are not of a plain DE type.
-fn plain_data_elements<'d, D: AsRef<[RawDataElement<'d>]>>(
-    data_elements: D,
-) -> Result<Vec<RawPlainDataElement<'d>>, AdvDeserializeError> {
-    data_elements
-        .as_ref()
-        .iter()
-        .map(|de| {
-            de.de_type
-                .try_as_plain_de_type()
-                .map(|de_type| RawPlainDataElement { de_type, contents: de.contents })
-        })
-        .collect::<Option<Vec<_>>>()
-        .ok_or(AdvDeserializeError::InvalidDataElementHierarchy)
-}
-
 /// Parse legacy encrypted identity DEs (private, trusted, provisioned) into salt and ciphertext
 /// (encrypted metadata key and at least 2 bytes of DEs).
 ///
@@ -199,6 +178,7 @@
 #[derive(Debug, PartialEq, Eq)]
 struct RawDataElement<'d> {
     de_type: DataElementType,
+    /// Byte array payload of the data element, without the DE header.
     contents: &'d [u8],
 }
 
@@ -206,30 +186,76 @@
 /// level DE representations.
 #[derive(Debug, PartialEq, Eq)]
 enum RawAdvertisement<'d> {
-    Plaintext(PlaintextAdvRawContents<'d>),
+    Plaintext(PlaintextAdvContents<'d>),
     Ciphertext(EncryptedAdvContents<'d>),
 }
 
-/// A plaintext advertisement's content in raw DEs but without further deserialization.
-#[derive(Debug, PartialEq, Eq)]
-struct PlaintextAdvRawContents<'d> {
-    identity_type: PlaintextIdentityMode,
-    data_elements: Vec<RawPlainDataElement<'d>>,
+/// An iterator that parses the given data elements iteratively. In environments
+/// where memory is not severely constrained, it is usually safer to collect
+/// this into `Result<Vec<PlainDataElement>>` so the validity of the whole
+/// advertisement can be checked before proceeding with further processing.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct PlainDeIterator<'d, F>
+where
+    F: PacketFlavor,
+    actions::ActionsDataElement<F>: DataElement,
+{
+    /// Data to be parsed, containing a sequence of data elements in serialized
+    /// form. This should not contain the identity data elements.
+    data: &'d [u8],
+    _marker: PhantomData<F>,
 }
 
-impl<'d> PlaintextAdvRawContents<'d> {
-    /// Deserialize the DE contents into per-DE-type structures.
-    ///
-    /// Returns `Some` if each DE's contents can be successfully deserialized, otherwise `None`.
-    fn try_deserialize(&self) -> Result<PlaintextAdvContents, DataElementDeserializeError> {
-        self.data_elements
-            .iter()
-            .map(|de| de.try_deserialize::<Plaintext>())
-            .collect::<Result<Vec<_>, _>>()
-            .map(|des| PlaintextAdvContents {
-                identity_type: self.identity_type,
-                data_elements: des,
-            })
+impl<'d, F> PlainDeIterator<'d, F>
+where
+    F: PacketFlavor,
+    actions::ActionsDataElement<F>: DataElement,
+{
+    fn raw_de_to_plain_de(
+        raw_de: RawDataElement<'d>,
+    ) -> Result<PlainDataElement<F>, DataElementDeserializeError> {
+        let de_type = raw_de
+            .de_type
+            .try_as_plain_de_type()
+            .ok_or(DataElementDeserializeError::DuplicateIdentityDataElement)?;
+        (RawPlainDataElement { de_type, contents: raw_de.contents }).try_deserialize()
+    }
+}
+
+impl<'d, F> Iterator for PlainDeIterator<'d, F>
+where
+    F: PacketFlavor,
+    actions::ActionsDataElement<F>: DataElement,
+{
+    type Item = Result<PlainDataElement<F>, DataElementDeserializeError>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let parse_result = nom::combinator::cut(nom::combinator::map_res(
+            parse_de,
+            Self::raw_de_to_plain_de,
+        ))(self.data);
+        match parse_result {
+            Ok((rem, de)) => {
+                self.data = rem;
+                Some(Ok(de))
+            }
+            Err(nom::Err::Error(_)) => {
+                panic!("All Errors are turned into Failures with `cut` above");
+            }
+            Err(nom::Err::Failure(DataElementDeserializeError::NomError(
+                nom::error::ErrorKind::Eof,
+            ))) => {
+                if self.data.is_empty() {
+                    None
+                } else {
+                    Some(Err(DataElementDeserializeError::UnexpectedDataRemaining))
+                }
+            }
+            Err(nom::Err::Failure(e)) => Some(Err(e)),
+            Err(nom::Err::Incomplete(_)) => {
+                panic!("Incomplete unexpected when using nom::complete APIs")
+            }
+        }
     }
 }
 
@@ -237,6 +263,7 @@
 #[derive(Debug, PartialEq, Eq)]
 pub(crate) struct RawPlainDataElement<'d> {
     de_type: PlainDataElementType,
+    /// Byte array payload of the data element, without the DE header.
     contents: &'d [u8],
 }
 
@@ -297,26 +324,15 @@
             DecryptError::DeserializeError(AdvDeserializeError::AdvertisementDeserializeError)
         })?;
 
-        let (_remaining, raw_des) = combinator::all_consuming(parse_data_elements)(remaining)
-            .map_err(|_e| {
-                DecryptError::DeserializeError(AdvDeserializeError::AdvertisementDeserializeError)
-            })?;
+        let remaining_arr = ArrayView::try_from_slice(remaining)
+            .expect("Max remaining = 31 - 14 = 17 bytes < BLE_ADV_SVC_CONTENT_LEN");
 
-        plain_data_elements(&raw_des)?
-            .into_iter()
-            .map(|de| de.try_deserialize())
-            .collect::<Result<Vec<_>, _>>()
-            .map_err(|e| {
-                DecryptError::DeserializeError(AdvDeserializeError::DataElementDeserializeError(e))
-            })
-            .map(|data_elements| {
-                DecryptedAdvContents::new(
-                    self.identity_type,
-                    ShortMetadataKey(metadata_key),
-                    self.salt,
-                    data_elements,
-                )
-            })
+        Ok(DecryptedAdvContents::new(
+            self.identity_type,
+            ShortMetadataKey(metadata_key),
+            self.salt,
+            remaining_arr,
+        ))
     }
 }
 
@@ -345,26 +361,21 @@
 
 /// The contents of a plaintext advertisement after deserializing DE contents
 #[derive(Debug, PartialEq, Eq)]
-pub struct PlaintextAdvContents {
+pub struct PlaintextAdvContents<'d> {
     identity_type: PlaintextIdentityMode,
-    data_elements: Vec<PlainDataElement<Plaintext>>,
+    /// Contents of the advertisement excluding the identity DE
+    data: &'d [u8],
 }
 
-impl PlaintextAdvContents {
+impl<'d> PlaintextAdvContents<'d> {
     /// Returns the identity type used for the advertisement
     pub fn identity(&self) -> PlaintextIdentityMode {
         self.identity_type
     }
 
     /// Returns an iterator over the v0 data elements
-    pub fn data_elements(&self) -> impl ExactSizeIterator<Item = &PlainDataElement<Plaintext>> {
-        self.data_elements.iter()
-    }
-
-    /// Destructures this V0 plaintext advertisement
-    /// into just the contained data elements
-    pub fn to_data_elements(self) -> Vec<PlainDataElement<Plaintext>> {
-        self.data_elements
+    pub fn data_elements(&self) -> PlainDeIterator<'d, Plaintext> {
+        PlainDeIterator { data: self.data, _marker: PhantomData }
     }
 }
 
@@ -374,7 +385,9 @@
     identity_type: EncryptedIdentityDataElementType,
     metadata_key: ShortMetadataKey,
     salt: LegacySalt,
-    data_elements: Vec<PlainDataElement<Ciphertext>>,
+    /// The decrypted data in this advertisement. This should be a sequence of
+    /// serialized data elements, excluding the identity DE.
+    data: ArrayView<u8, { BLE_ADV_SVC_CONTENT_LEN }>,
 }
 
 impl DecryptedAdvContents {
@@ -383,9 +396,9 @@
         identity_type: EncryptedIdentityDataElementType,
         metadata_key: ShortMetadataKey,
         salt: LegacySalt,
-        data_elements: Vec<PlainDataElement<Ciphertext>>,
+        data: ArrayView<u8, { BLE_ADV_SVC_CONTENT_LEN }>,
     ) -> Self {
-        Self { identity_type, metadata_key, salt, data_elements }
+        Self { identity_type, metadata_key, salt, data }
     }
 
     /// The type of identity DE used in the advertisement.
@@ -396,8 +409,8 @@
     /// Iterator over the data elements in an advertisement, except for any DEs related to resolving
     /// the identity or otherwise validating the payload (e.g. any identity DEs like Private
     /// Identity).
-    pub fn data_elements(&self) -> impl ExactSizeIterator<Item = &PlainDataElement<Ciphertext>> {
-        self.data_elements.iter()
+    pub fn data_elements(&self) -> PlainDeIterator<Ciphertext> {
+        PlainDeIterator { data: self.data.as_slice(), _marker: PhantomData }
     }
 
     /// The salt used for decryption of this advertisement.
@@ -418,7 +431,7 @@
 #[derive(Debug, PartialEq, Eq)]
 pub(crate) enum IntermediateAdvContents<'d> {
     /// Plaintext advertisements
-    Plaintext(PlaintextAdvContents),
+    Plaintext(PlaintextAdvContents<'d>),
     /// Ciphertext advertisements
     Ciphertext(EncryptedAdvContents<'d>),
 }
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
index 4512bf7..5725e0b 100644
--- a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
+++ b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
@@ -12,10 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+extern crate alloc;
 extern crate std;
 
 use super::*;
-use crate::legacy::actions::{ActionBits, ActionsDataElement};
+use crate::legacy::actions::{ActionBits, ActionsDataElement, Finder, NearbyShare};
+use crate::legacy::serialize::id_de_type_as_generic_de_type;
 use crate::shared_data::TxPower;
 use crate::{
     credential::{v0::V0, SimpleBroadcastCryptoMaterial},
@@ -25,18 +27,19 @@
         de_type::DeActualLength,
         random_data_elements::{random_de_ciphertext, random_de_plaintext},
         serialize::{
-            encode_de_header_actual_len, id_de_type_as_generic_de_type, AdvBuilder,
-            DataElementBundle, Identity, LdtIdentity, ToDataElementBundle as _,
+            encode_de_header_actual_len, AdvBuilder, DataElementBundle, Identity, LdtIdentity,
+            ToDataElementBundle as _,
         },
         PacketFlavorEnum, BLE_ADV_SVC_CONTENT_LEN,
     },
     parse_adv_header, shared_data, AdvHeader, PublicIdentity,
 };
+use alloc::vec::Vec;
 use array_view::ArrayView;
 use crypto_provider_default::CryptoProviderImpl;
 use init_with::InitWith as _;
 use ldt_np_adv::LdtEncrypterXtsAes128;
-use nom::error;
+use nom::error::{self, ErrorKind};
 use rand_ext::rand::{prelude::SliceRandom, Rng as _};
 use std::vec;
 use strum::IntoEnumIterator as _;
@@ -76,22 +79,27 @@
     let adv = parse_raw_adv_contents::<CryptoProviderImpl>(&[
         0x03, // public identity
         0x15, 0x05, // tx power 5
-        0x36, 0x11, 0x12, 0x13, // actions
+        0x26, 0x00, 0x44, // actions
     ])
     .unwrap();
-    assert_eq!(
-        RawAdvertisement::Plaintext(PlaintextAdvRawContents {
-            identity_type: PlaintextIdentityMode::Public,
-            data_elements: vec![
-                RawPlainDataElement { de_type: PlainDataElementType::TxPower, contents: &[0x05] },
-                RawPlainDataElement {
-                    de_type: PlainDataElementType::Actions,
-                    contents: &[0x11, 0x12, 0x13]
-                }
-            ],
-        }),
-        adv
-    );
+    match adv {
+        RawAdvertisement::Plaintext(plaintext) => {
+            assert_eq!(PlaintextIdentityMode::Public, plaintext.identity_type);
+            let mut action_bits = ActionBits::default();
+            action_bits.set_action(NearbyShare::from(true));
+            action_bits.set_action(Finder::from(true));
+            assert_eq!(
+                vec![
+                    PlainDataElement::<Plaintext>::TxPower(TxPowerDataElement::from(
+                        TxPower::try_from(5).unwrap()
+                    )),
+                    PlainDataElement::Actions(ActionsDataElement::from(action_bits)),
+                ],
+                plaintext.data_elements().collect::<Result<Vec<_>, _>>().unwrap(),
+            );
+        }
+        RawAdvertisement::Ciphertext(_) => panic!("adv should be plaintext"),
+    }
 }
 
 #[test]
@@ -100,13 +108,16 @@
         0x03, // public identity
     ])
     .unwrap();
-    assert_eq!(
-        RawAdvertisement::Plaintext(PlaintextAdvRawContents {
-            identity_type: PlaintextIdentityMode::Public,
-            data_elements: vec![],
-        }),
-        adv
-    );
+    match adv {
+        RawAdvertisement::Plaintext(plaintext) => {
+            assert_eq!(PlaintextIdentityMode::Public, plaintext.identity_type);
+            assert_eq!(
+                Vec::<PlainDataElement<Plaintext>>::new(),
+                plaintext.data_elements().collect::<Result<Vec<_>, _>>().unwrap()
+            );
+        }
+        RawAdvertisement::Ciphertext(_) => panic!("adv should be plaintext"),
+    }
 }
 
 #[test]
@@ -114,8 +125,8 @@
     // battery uses the header length as is
     let input = &[0xFB, 0x01, 0x02, 0x03];
     assert_eq!(
-        nom::Err::Error(error::Error { input: input.as_slice(), code: error::ErrorKind::Eof }),
-        parse_data_elements(input).unwrap_err()
+        nom::Err::Error(DataElementDeserializeError::NomError(ErrorKind::MapOpt)),
+        parse_de(input).unwrap_err(),
     );
 }
 
@@ -126,10 +137,15 @@
         0x03, // another public identity
         0x15, 0x03, // tx power de
     ];
-    assert_eq!(
-        AdvDeserializeError::InvalidDataElementHierarchy,
-        parse_raw_adv_contents::<CryptoProviderImpl>(input).unwrap_err()
-    );
+    match parse_raw_adv_contents::<CryptoProviderImpl>(input).unwrap() {
+        RawAdvertisement::Plaintext(content) => {
+            assert_eq!(
+                DataElementDeserializeError::DuplicateIdentityDataElement,
+                content.data_elements().collect::<Result<Vec<_>, _>>().unwrap_err(),
+            );
+        }
+        RawAdvertisement::Ciphertext(_) => panic!("Adv should be plaintext"),
+    }
 }
 
 #[test]
@@ -297,35 +313,42 @@
     ];
 
     assert_eq!(
-        nom::Err::Error(error::Error { input: input.as_slice(), code: error::ErrorKind::MapOpt }),
+        nom::Err::Error(DataElementDeserializeError::NomError(ErrorKind::MapOpt)),
         parse_de(&input[..]).unwrap_err()
     );
 }
 
 #[test]
-fn plain_data_elements_matches_plain_des() {
+fn raw_de_to_plain_de_matches_plain_des() {
     assert_eq!(
-        vec![
-            RawPlainDataElement { de_type: PlainDataElementType::TxPower, contents: &[0x01] },
-            RawPlainDataElement { de_type: PlainDataElementType::Actions, contents: &[0x02] }
-        ],
-        plain_data_elements(&[
-            RawDataElement { de_type: DataElementType::TxPower, contents: &[0x01] },
-            RawDataElement { de_type: DataElementType::Actions, contents: &[0x02] }
-        ])
-        .unwrap()
+        PlainDataElement::TxPower(TxPowerDataElement::from(TxPower::try_from(1).unwrap())),
+        PlainDeIterator::<Plaintext>::raw_de_to_plain_de(RawDataElement {
+            de_type: DataElementType::TxPower,
+            contents: &[0x01]
+        })
+        .unwrap(),
+    );
+    assert_eq!(
+        PlainDataElement::Actions(ActionsDataElement::from(
+            ActionBits::try_from(0x00400000).unwrap()
+        )),
+        PlainDeIterator::<Plaintext>::raw_de_to_plain_de(RawDataElement {
+            de_type: DataElementType::Actions,
+            contents: &[0x00, 0x40]
+        })
+        .unwrap(),
     );
 }
 
 #[test]
-fn plain_data_elements_rejects_identity_de_error() {
+fn raw_de_to_plain_de_rejects_identity_de_error() {
     for idet in IdentityDataElementType::iter() {
         assert_eq!(
-            AdvDeserializeError::InvalidDataElementHierarchy,
-            plain_data_elements(&[
-                RawDataElement { de_type: DataElementType::TxPower, contents: &[0x01] },
-                RawDataElement { de_type: id_de_type_as_generic_de_type(idet), contents: &[0x02] }
-            ])
+            DataElementDeserializeError::DuplicateIdentityDataElement,
+            PlainDeIterator::<Plaintext>::raw_de_to_plain_de(RawDataElement {
+                de_type: id_de_type_as_generic_de_type(idet),
+                contents: &[0x02],
+            })
             .unwrap_err()
         );
     }
@@ -432,10 +455,11 @@
                 eac
             );
 
-            assert_eq!(
-                DecryptedAdvContents { identity_type, metadata_key, salt, data_elements: des },
-                eac.try_decrypt(&cipher).unwrap()
-            )
+            let contents = eac.try_decrypt(&cipher).unwrap();
+            assert_eq!(identity_type, contents.identity_type);
+            assert_eq!(metadata_key, contents.metadata_key);
+            assert_eq!(salt, contents.salt);
+            assert_eq!(des, contents.data_elements().collect::<Result<Vec<_>, _>>().unwrap());
         } else {
             panic!("Unexpected variant: {:?}", parsed_adv);
         }
@@ -496,17 +520,16 @@
             eac
         );
 
+        let decrypted = eac.try_decrypt(&cipher).unwrap();
+        assert_eq!(EncryptedIdentityDataElementType::Private, decrypted.identity_type);
+        assert_eq!(metadata_key, decrypted.metadata_key);
+        assert_eq!(salt, decrypted.salt);
         assert_eq!(
-            DecryptedAdvContents {
-                identity_type: EncryptedIdentityDataElementType::Private,
-                metadata_key,
-                salt,
-                data_elements: vec![PlainDataElement::TxPower(TxPowerDataElement::from(
-                    TxPower::try_from(3).unwrap()
-                ))],
-            },
-            eac.try_decrypt(&cipher).unwrap()
-        )
+            vec![PlainDataElement::TxPower(TxPowerDataElement::from(
+                TxPower::try_from(3).unwrap()
+            ))],
+            decrypted.data_elements().collect::<Result<Vec<_>, _>>().unwrap()
+        );
     } else {
         panic!("Unexpected variant: {:?}", parsed_adv);
     }
@@ -534,16 +557,14 @@
     assert_eq!(AdvHeader::V0, header);
 
     let parsed_adv = deserialize_adv_contents::<CryptoProviderImpl>(remaining).unwrap();
-    if let IntermediateAdvContents::Plaintext(parc) = parsed_adv {
+    if let IntermediateAdvContents::Plaintext(adv_contents) = parsed_adv {
+        assert_eq!(PlaintextIdentityMode::Public, adv_contents.identity());
         assert_eq!(
-            PlaintextAdvContents {
-                identity_type: PlaintextIdentityMode::Public,
-                data_elements: vec![PlainDataElement::Actions(ActionsDataElement::from(
-                    ActionBits::default()
-                ))],
-            },
-            parc
-        )
+            vec![PlainDataElement::<Plaintext>::Actions(ActionsDataElement::from(
+                ActionBits::default()
+            ))],
+            adv_contents.data_elements().collect::<Result<Vec<_>, _>>().unwrap()
+        );
     } else {
         panic!("Unexpected variant: {:?}", parsed_adv);
     }
@@ -670,8 +691,12 @@
     let parsed_adv = deserialize_adv_contents::<CryptoProviderImpl>(&adv[1..]).unwrap();
     if let IntermediateAdvContents::Ciphertext(eac) = parsed_adv {
         assert_eq!(
-            DecryptError::DeserializeError(AdvDeserializeError::InvalidDataElementHierarchy),
-            eac.try_decrypt(&cipher).unwrap_err()
+            DataElementDeserializeError::DuplicateIdentityDataElement,
+            eac.try_decrypt(&cipher)
+                .unwrap()
+                .data_elements()
+                .collect::<Result<Vec<_>, _>>()
+                .unwrap_err()
         )
     } else {
         panic!("Unexpected variant: {:?}", parsed_adv);
@@ -734,11 +759,11 @@
     );
     buf.extend_from_slice(contents);
 
-    let raw_de = combinator::all_consuming(parse_de)(&buf).map(|(_remaining, de)| de).unwrap();
-
-    let plain_des = plain_data_elements(&[raw_de]).unwrap();
+    let mut plain_des = PlainDeIterator { data: &buf, _marker: PhantomData }
+        .collect::<Result<Vec<_>, _>>()
+        .unwrap();
     assert_eq!(1, plain_des.len());
-    plain_des.first().unwrap().try_deserialize().unwrap()
+    plain_des.swap_remove(0)
 }
 
 fn plaintext_random_adv_contents_round_trip<I: Identity<Flavor = Plaintext>, F: Fn() -> I>(
@@ -774,31 +799,12 @@
             parse_raw_adv_contents::<CryptoProviderImpl>(&serialized.as_slice()[1..]).unwrap();
 
         assert_eq!(AdvHeader::V0, header);
-        if let RawAdvertisement::Plaintext(parc) = parsed_adv {
+        if let RawAdvertisement::Plaintext(adv_contents) = parsed_adv {
+            assert_eq!(identity_type, adv_contents.identity_type);
             assert_eq!(
-                PlaintextAdvRawContents {
-                    identity_type,
-                    data_elements: de_tuples
-                        .iter()
-                        .map(|(_de, de_type, bundle)| RawPlainDataElement {
-                            de_type: *de_type,
-                            contents: bundle.contents_as_slice(),
-                        })
-                        .collect()
-                },
-                parc
+                de_tuples.into_iter().map(|(de, _de_type, _bundle)| de).collect::<Vec<_>>(),
+                adv_contents.data_elements().collect::<Result<Vec<_>, _>>().unwrap(),
             );
-
-            assert_eq!(
-                PlaintextAdvContents {
-                    identity_type,
-                    data_elements: de_tuples
-                        .into_iter()
-                        .map(|(de, _de_type, _bundle)| de)
-                        .collect(),
-                },
-                parc.try_deserialize().unwrap()
-            )
         } else {
             panic!("Unexpected variant: {:?}", parsed_adv);
         }
diff --git a/nearby/presence/np_adv/src/lib.rs b/nearby/presence/np_adv/src/lib.rs
index 45207ff..1c42796 100644
--- a/nearby/presence/np_adv/src/lib.rs
+++ b/nearby/presence/np_adv/src/lib.rs
@@ -21,6 +21,7 @@
 #![forbid(unsafe_code)]
 #![deny(missing_docs)]
 
+#[cfg(any(test, feature = "alloc"))]
 extern crate alloc;
 extern crate core;
 use crate::{
@@ -39,20 +40,22 @@
     },
 };
 
+#[cfg(any(test, feature = "alloc"))]
 use alloc::vec::Vec;
+use array_vec::ArrayVecOption;
 #[cfg(feature = "devtools")]
 use array_view::ArrayView;
-use core::borrow::BorrowMut;
 use core::fmt::Debug;
 use crypto_provider::CryptoProvider;
-use deserialization_arena::DeserializationArena;
-
+use deserialization_arena::{DeserializationArena, DeserializationArenaAllocator};
 #[cfg(feature = "devtools")]
 use extended::NP_ADV_MAX_SECTION_LEN;
+use extended::NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT;
 use legacy::{data_elements::DataElementDeserializeError, deserialize::AdvDeserializeError};
 use nom::{combinator, number};
 pub use strum;
 
+mod array_vec;
 pub mod credential;
 pub mod de_type;
 #[cfg(test)]
@@ -76,21 +79,7 @@
 /// Parse, deserialize, decrypt, and validate a complete NP advertisement (the entire contents of
 /// the service data for the NP UUID).
 pub fn deserialize_advertisement<'adv, 'cred, B, P>(
-    mut arena: impl BorrowMut<DeserializationArena<'adv>>,
-    adv: &'adv [u8],
-    cred_book: &'cred B,
-) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError>
-where
-    B: CredentialBook<'cred>,
-    P: CryptoProvider,
-{
-    // Turn `impl BorrowMut` into the concrete `&mut` reference so that the compiler does not need
-    // to duplicate the function code for different concrete types.
-    deserialize_advertisement_impl::<B, P>(arena.borrow_mut(), adv, cred_book)
-}
-
-fn deserialize_advertisement_impl<'adv, 'cred, B, P>(
-    arena: &mut DeserializationArena<'adv>,
+    arena: DeserializationArena<'adv>,
     adv: &'adv [u8],
     cred_book: &'cred B,
 ) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError>
@@ -128,15 +117,6 @@
     ParseError,
     /// No suitable credential found to decrypt the given section.
     NoMatchingCredentials,
-    /// Given Arena is not large enough to hold the decrypted data.
-    ArenaOutOfSpace,
-}
-
-#[cfg(feature = "devtools")]
-impl From<ArenaOutOfSpace> for AdvDecryptionError {
-    fn from(_: ArenaOutOfSpace) -> Self {
-        Self::ArenaOutOfSpace
-    }
 }
 
 /// Decrypt, but do not further deserialize the v1 bytes, intended for developer tooling uses only.
@@ -144,7 +124,7 @@
 /// structured format and provides extra type safety.
 #[cfg(feature = "devtools")]
 pub fn deser_decrypt_v1_section_bytes_for_dev_tools<'adv, 'cred, B, P>(
-    arena: &mut DeserializationArena<'adv>,
+    arena: DeserializationArena<'adv>,
     cred_book: &'cred B,
     header_byte: u8,
     section_bytes: &'adv [u8],
@@ -161,21 +141,22 @@
         IntermediateSection::Ciphertext(section) => section,
     };
 
+    let mut allocator = arena.into_allocator();
     for (crypto_material, _) in cred_book.v1_iter() {
         if let Some(plaintext) = cipher_section
-            .try_resolve_identity_and_decrypt::<_, P>(arena.borrow_mut(), &crypto_material)
+            .try_resolve_identity_and_decrypt::<_, P>(&mut allocator, &crypto_material)
         {
-            return plaintext
-                .map(|pt| {
-                    let encryption_scheme = match cipher_section {
-                        CiphertextSection::SignatureEncryptedIdentity(_) => {
-                            V1EncryptionScheme::Signature
-                        }
-                        CiphertextSection::MicEncryptedIdentity(_) => V1EncryptionScheme::Mic,
-                    };
-                    (pt, encryption_scheme)
-                })
-                .map_err(|e| e.into());
+            let pt = plaintext.expect(concat!(
+                "Should not run out of space because DeserializationArenaAllocator is big ",
+                "enough to hold a single advertisement, and we exit immediately upon ",
+                "successful decryption",
+            ));
+
+            let encryption_scheme = match cipher_section {
+                CiphertextSection::SignatureEncryptedIdentity(_) => V1EncryptionScheme::Signature,
+                CiphertextSection::MicEncryptedIdentity(_) => V1EncryptionScheme::Mic,
+            };
+            return Ok((pt, encryption_scheme));
         }
     }
     Err(AdvDecryptionError::NoMatchingCredentials)
@@ -197,8 +178,14 @@
 /// section ordering as they appeared within the original advertisement to ensure
 /// that the fully-deserialized advertisement may be correctly reconstructed.
 struct SectionsInProcessing<'adv, M: MatchedCredential> {
-    deserialized_sections: Vec<(usize, V1DeserializedSection<'adv, M>)>,
-    encrypted_sections: Vec<(usize, ResolvableCiphertextSection<'adv>)>,
+    deserialized_sections: ArrayVecOption<
+        (usize, V1DeserializedSection<'adv, M>),
+        { NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT },
+    >,
+    encrypted_sections: ArrayVecOption<
+        (usize, ResolvableCiphertextSection<'adv>),
+        { NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT },
+    >,
     malformed_sections_count: usize,
 }
 
@@ -214,8 +201,8 @@
             parse_sections(header, remaining).map_err(|_| AdvDeserializationError::ParseError {
                 details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError,
             })?;
-        let mut deserialized_sections = Vec::new();
-        let mut encrypted_sections = Vec::new();
+        let mut deserialized_sections = ArrayVecOption::default();
+        let mut encrypted_sections = ArrayVecOption::default();
         // keep track of ordering for later sorting during `self.finished_with_decryption_attempts()`.
         for (idx, s) in int_sections.into_iter().enumerate() {
             match s {
@@ -247,7 +234,7 @@
     /// the cost of iterating over sections-in-memory.
     fn try_decrypt_with_credential<C: V1DiscoveryCryptoMaterial, P: CryptoProvider>(
         &mut self,
-        arena: &mut DeserializationArena<'adv>,
+        arena: &mut DeserializationArenaAllocator<'adv>,
         crypto_material: C,
         match_data: M,
     ) -> Result<(), ArenaOutOfSpace> {
@@ -278,14 +265,14 @@
                     let deserialization_result = match &section.ciphertext_section {
                         CiphertextSection::SignatureEncryptedIdentity(c) => c
                             .try_deserialize(
-                                arena.borrow_mut(),
+                                arena,
                                 identity_match,
                                 &crypto_material.signed_verification_material::<P>(),
                             )
                             .map_err(SectionDeserializeError::from),
                         CiphertextSection::MicEncryptedIdentity(c) => c
                             .try_deserialize(
-                                arena.borrow_mut(),
+                                arena,
                                 identity_match,
                                 &crypto_material.unsigned_verification_material::<P>(),
                             )
@@ -351,7 +338,7 @@
 
 /// Deserialize and decrypt the contents of a v1 adv after the version header
 fn deser_decrypt_v1<'adv, 'cred, B, P>(
-    arena: &mut DeserializationArena<'adv>,
+    arena: DeserializationArena<'adv>,
     cred_book: &'cred B,
     remaining: &'adv [u8],
     header: V1Header,
@@ -365,14 +352,18 @@
             header, remaining,
         )?;
 
+    let mut allocator = arena.into_allocator();
+
     // Hot loop
     // We assume that iterating credentials is more expensive than iterating sections
     for (crypto_material, match_data) in cred_book.v1_iter() {
-        sections_in_processing.try_decrypt_with_credential::<_, P>(
-            arena.borrow_mut(),
-            crypto_material,
-            match_data,
-        )?;
+        sections_in_processing
+            .try_decrypt_with_credential::<_, P>(&mut allocator, crypto_material, match_data)
+            .expect(concat!(
+                "Should not run out of space because DeserializationArenaAllocator is big ",
+                "enough to hold a single advertisement, and we exit immediately upon ",
+                "successful decryption",
+            ));
         if sections_in_processing.resolved_all_identities() {
             // No need to consider the other credentials
             break;
@@ -382,12 +373,12 @@
 }
 
 /// Deserialize and decrypt the contents of a v0 adv after the version header
-fn deser_decrypt_v0<'a, B, P>(
-    cred_book: &'a B,
-    remaining: &[u8],
-) -> Result<V0AdvertisementContents<B::Matched>, AdvDeserializationError>
+fn deser_decrypt_v0<'adv, 'cred, B, P>(
+    cred_book: &'cred B,
+    remaining: &'adv [u8],
+) -> Result<V0AdvertisementContents<'adv, B::Matched>, AdvDeserializationError>
 where
-    B: CredentialBook<'a>,
+    B: CredentialBook<'cred>,
     P: CryptoProvider,
 {
     let contents = legacy::deserialize::deserialize_adv_contents::<P>(remaining)?;
@@ -449,11 +440,12 @@
 }
 
 /// An NP advertisement with its header parsed.
+#[allow(clippy::large_enum_variant)]
 #[derive(Debug, PartialEq, Eq)]
 pub enum DeserializedAdvertisement<'adv, M: MatchedCredential> {
     /// V0 header has all reserved bits, so there is no data to represent other than the version
     /// itself.
-    V0(V0AdvertisementContents<M>),
+    V0(V0AdvertisementContents<'adv, M>),
     /// V1 advertisement
     V1(V1AdvertisementContents<'adv, M>),
 }
@@ -461,7 +453,7 @@
 impl<'adv, M: MatchedCredential> DeserializedAdvertisement<'adv, M> {
     /// Attempts to cast this deserialized advertisement into the `V0AdvertisementContents`
     /// variant. If the underlying advertisement is not V0, this will instead return `None`.
-    pub fn into_v0(self) -> Option<V0AdvertisementContents<M>> {
+    pub fn into_v0(self) -> Option<V0AdvertisementContents<'adv, M>> {
         match self {
             Self::V0(x) => Some(x),
             _ => None,
@@ -480,18 +472,26 @@
 /// The contents of a deserialized and decrypted V1 advertisement.
 #[derive(Debug, PartialEq, Eq)]
 pub struct V1AdvertisementContents<'adv, M: MatchedCredential> {
-    sections: Vec<V1DeserializedSection<'adv, M>>,
+    sections: ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT>,
     invalid_sections: usize,
 }
 
 impl<'adv, M: MatchedCredential> V1AdvertisementContents<'adv, M> {
-    fn new(sections: Vec<V1DeserializedSection<'adv, M>>, invalid_sections: usize) -> Self {
+    fn new(
+        sections: ArrayVecOption<
+            V1DeserializedSection<'adv, M>,
+            NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT,
+        >,
+        invalid_sections: usize,
+    ) -> Self {
         Self { sections, invalid_sections }
     }
 
     /// Destructures this V1 advertisement into just the sections
     /// which could be successfully deserialized and decrypted
-    pub fn into_sections(self) -> Vec<V1DeserializedSection<'adv, M>> {
+    pub fn into_sections(
+        self,
+    ) -> ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT> {
         self.sections
     }
 
@@ -508,9 +508,9 @@
 
 /// Advertisement content that was either already plaintext or has been decrypted.
 #[derive(Debug, PartialEq, Eq)]
-pub enum V0AdvertisementContents<M: MatchedCredential> {
+pub enum V0AdvertisementContents<'adv, M: MatchedCredential> {
     /// Contents of an originally plaintext advertisement
-    Plaintext(PlaintextAdvContents),
+    Plaintext(PlaintextAdvContents<'adv>),
     /// Contents that was ciphertext in the original advertisement, and has been decrypted
     /// with the credential in the [MatchedCredential]
     Decrypted(WithMatchedCredential<M, DecryptedAdvContents>),
@@ -650,14 +650,6 @@
         /// [AdvDeserializationErrorDetailsHazmat] before using this field.
         details_hazmat: AdvDeserializationErrorDetailsHazmat,
     },
-    /// The given arena is not large enough to hold the decrypted data.
-    ArenaOutOfSpace,
-}
-
-impl From<ArenaOutOfSpace> for AdvDeserializationError {
-    fn from(_: ArenaOutOfSpace) -> Self {
-        Self::ArenaOutOfSpace
-    }
 }
 
 impl Debug for AdvDeserializationError {
@@ -665,7 +657,6 @@
         match self {
             AdvDeserializationError::HeaderParseError => write!(f, "HeaderParseError"),
             AdvDeserializationError::ParseError { .. } => write!(f, "ParseError"),
-            AdvDeserializationError::ArenaOutOfSpace => write!(f, "ArenaOutOfSpace"),
         }
     }
 }
@@ -699,24 +690,12 @@
                         AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError,
                 }
             }
-            AdvDeserializeError::DataElementDeserializeError(e) => {
-                AdvDeserializationError::ParseError {
-                    details_hazmat:
-                        AdvDeserializationErrorDetailsHazmat::V0DataElementDeserializeError(e),
-                }
-            }
             AdvDeserializeError::TooManyTopLevelDataElements => {
                 AdvDeserializationError::ParseError {
                     details_hazmat:
                         AdvDeserializationErrorDetailsHazmat::TooManyTopLevelDataElements,
                 }
             }
-            AdvDeserializeError::InvalidDataElementHierarchy => {
-                AdvDeserializationError::ParseError {
-                    details_hazmat:
-                        AdvDeserializationErrorDetailsHazmat::InvalidDataElementHierarchy,
-                }
-            }
             AdvDeserializeError::MissingIdentity => AdvDeserializationError::ParseError {
                 details_hazmat: AdvDeserializationErrorDetailsHazmat::MissingIdentity,
             },
diff --git a/nearby/presence/np_adv/tests/examples_v0.rs b/nearby/presence/np_adv/tests/examples_v0.rs
index 27ea7c5..8310531 100644
--- a/nearby/presence/np_adv/tests/examples_v0.rs
+++ b/nearby/presence/np_adv/tests/examples_v0.rs
@@ -35,8 +35,9 @@
         0,
         CryptoProviderImpl,
     >(&[], &[]);
+    let arena = deserialization_arena!();
     let adv = deserialize_advertisement::<_, CryptoProviderImpl>(
-        deserialization_arena!(),
+        arena,
         &[
             0x00, // adv header
             0x03, // public identity
@@ -52,10 +53,10 @@
         V0AdvertisementContents::Plaintext(p) => {
             assert_eq!(PlaintextIdentityMode::Public, p.identity());
             assert_eq!(
-                vec![&PlainDataElement::TxPower(TxPowerDataElement::from(
+                vec![PlainDataElement::TxPower(TxPowerDataElement::from(
                     TxPower::try_from(3).unwrap()
-                )),],
-                p.data_elements().collect::<Vec<_>>()
+                ))],
+                p.data_elements().collect::<Result<Vec<_>, _>>().unwrap()
             );
         }
         _ => panic!("this example is plaintext"),
@@ -154,7 +155,7 @@
     assert_eq!(metadata_key, decrypted.metadata_key());
 
     assert_eq!(
-        vec![&PlainDataElement::TxPower(TxPowerDataElement::from(TxPower::try_from(3).unwrap())),],
-        decrypted.data_elements().collect::<Vec<_>>()
+        vec![PlainDataElement::TxPower(TxPowerDataElement::from(TxPower::try_from(3).unwrap())),],
+        decrypted.data_elements().collect::<Result<Vec<_>, _>>().unwrap(),
     );
 }
diff --git a/nearby/presence/np_c_ffi/Cargo.lock b/nearby/presence/np_c_ffi/Cargo.lock
index f55cf29..796292b 100644
--- a/nearby/presence/np_c_ffi/Cargo.lock
+++ b/nearby/presence/np_c_ffi/Cargo.lock
@@ -473,7 +473,6 @@
 name = "handle_map"
 version = "0.1.0"
 dependencies = [
- "crypto_provider",
  "lock_adapter",
 ]
 
diff --git a/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt b/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt
index bddda64..7ca9eb0 100644
--- a/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt
+++ b/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt
@@ -17,9 +17,10 @@
         deserialize_result_tests.cc
         deserialize_v0_tests.cc
         deserialize_v1_tests.cc
-        global_config_tests.cc
         credential_book_tests.cc
         byte_buffer_tests.cc
+        np_cpp_test.h
+        np_cpp_test.cc
 )
 
 target_link_libraries(
diff --git a/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc b/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
index 53218ee..3a32904 100644
--- a/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
@@ -14,13 +14,27 @@
 
 #include "nearby_protocol.h"
 #include "shared_test_util.h"
+#include "np_cpp_test.h"
 
 #include "gtest/gtest.h"
 
-TEST(NpFfiCredentialBookTests, TestMoveConstructor) {
+TEST_F(NpCppTest, TestSetMaxCredBooks) {
+  auto book1_result = nearby_protocol::CredentialBook::TryCreate();
+  ASSERT_TRUE(book1_result.ok());
+
+  auto book2_result = nearby_protocol::CredentialBook::TryCreate();
+  ASSERT_TRUE(book2_result.ok());
+
+  auto book3_result = nearby_protocol::CredentialBook::TryCreate();
+  ASSERT_FALSE(book3_result.ok());
+  ASSERT_TRUE(absl::IsResourceExhausted(book3_result.status()));
+}
+
+TEST_F(NpCppTest, TestMoveConstructor) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
   auto deserialize_result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
+                                                              book);
   ASSERT_EQ(deserialize_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
@@ -47,10 +61,11 @@
                "");
 }
 
-TEST(NpFfiCredentialBookTests, TestMoveAssignment) {
+TEST_F(NpCppTest, TestMoveAssignment) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
   auto deserialize_result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
+                                                              book);
   ASSERT_EQ(deserialize_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
index 54872c6..737fd18 100644
--- a/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
@@ -14,14 +14,15 @@
 
 #include "nearby_protocol.h"
 #include "shared_test_util.h"
+#include "np_cpp_test.h"
 
 #include "absl/strings/escaping.h"
 #include "gtest/gtest.h"
 
-TEST(NpFfiDeserializeResultTests, TestResultMoveConstructor) {
+TEST_F(NpCppTest, TestResultMoveConstructor) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  auto result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
@@ -49,7 +50,7 @@
   ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetKind(), "");
 }
 
-TEST(NpFfiDeserializeResultTests, DeserializeFromStringView) {
+TEST_F(NpCppTest, DeserializeFromStringView) {
   auto bytes = absl::HexStringToBytes("00031503");
   auto buffer = nearby_protocol::ByteBuffer<255>::CopyFrom(bytes);
   ASSERT_TRUE(buffer.ok());
@@ -86,16 +87,16 @@
   ASSERT_EQ(tx_power.tx_power, 3);
 }
 
-TEST(NpFfiDeserializeResultTests, TestResultMoveAssignment) {
+TEST_F(NpCppTest, TestResultMoveAssignment) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  auto result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
   // create a second result
-  auto another_result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  auto another_result = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
   ASSERT_EQ(another_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
@@ -119,10 +120,7 @@
   ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetKind(), "");
 }
 
-TEST(NpFfiDeserializeResultTests, TestInvalidPayloadHeader) {
-  ASSERT_TRUE(
-      nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
-
+TEST_F(NpCppTest, TestInvalidPayloadHeader) {
   // An invalid header result should result in error
   nearby_protocol::RawAdvertisementPayload InvalidHeaderPayload(
       nearby_protocol::ByteBuffer<255>({1, {0xFF}}));
@@ -141,10 +139,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeResultTests, TestInvalidV0Cast) {
-  ASSERT_TRUE(
-      nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
-
+TEST_F(NpCppTest, TestInvalidV0Cast) {
   auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
   auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -156,10 +151,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeResultTests, TestInvalidV1Cast) {
-  ASSERT_TRUE(
-      nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
-
+TEST_F(NpCppTest, TestInvalidV1Cast) {
   // Create an empty credential book and verify that is is successful
   auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(maybe_credential_book.ok());
@@ -173,7 +165,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeResultTests, V0UseResultTwice) {
+TEST_F(NpCppTest, V0UseResultTwice) {
   auto book_result = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(book_result.ok());
 
@@ -191,7 +183,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeResultTests, V1UseResultTwice) {
+TEST_F(NpCppTest, V1UseResultTwice) {
   auto book_result = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(book_result.ok());
 
@@ -209,7 +201,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeResultTests, IntoV0AfterOutOfScope) {
+TEST_F(NpCppTest, IntoV0AfterOutOfScope) {
   auto book_result = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(book_result.ok());
 
@@ -228,7 +220,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeResultTests, IntoV1AfterOutOfScope) {
+TEST_F(NpCppTest, IntoV1AfterOutOfScope) {
   auto book_result = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(book_result.ok());
 
@@ -247,7 +239,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeV0Tests, V0ResultKindAfterOutOfScope) {
+TEST_F(NpCppTest, V0ResultKindAfterOutOfScope) {
   auto book_result = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(book_result.ok());
 
@@ -266,7 +258,7 @@
       { [[maybe_unused]] auto failure = deserialize_result.GetKind(); }, "");
 }
 
-TEST(NpFfiDeserializeResultTests, V1ResultKindAfterOutOfScope) {
+TEST_F(NpCppTest, V1ResultKindAfterOutOfScope) {
   auto book_result = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(book_result.ok());
 
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
index cd95c48..e2a10a0 100644
--- a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
@@ -14,10 +14,24 @@
 
 #include "nearby_protocol.h"
 #include "shared_test_util.h"
+#include "np_cpp_test.h"
 
 #include "gtest/gtest.h"
 
-TEST(NpFfiDeserializeV0Tests, V0SingleDataElementTxPower) {
+TEST_F(NpCppTest, InvalidCast) {
+  auto book = nearby_protocol::CredentialBook::TryCreate().value();
+  auto deserialize_result =
+      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  ASSERT_EQ(deserialize_result.GetKind(),
+            np_ffi::internal::DeserializeAdvertisementResultKind::V0);
+
+  // Now try to cast the result into the wrong type and verify the process
+  // aborts
+  ASSERT_DEATH({ [[maybe_unused]] auto failure = deserialize_result.IntoV1(); },
+               "");
+}
+
+TEST_F(NpCppTest, V0SingleDataElementTxPower) {
   nearby_protocol::RawAdvertisementPayload adv(
       nearby_protocol::ByteBuffer<255>({
           4,
@@ -56,7 +70,7 @@
   ASSERT_EQ(tx_power.tx_power, 3);
 }
 
-TEST(NpFfiDeserializeV0Tests, V0LengthOneActionsDataElement) {
+TEST_F(NpCppTest, V0LengthOneActionsDataElement) {
   nearby_protocol::RawAdvertisementPayload adv(
       nearby_protocol::ByteBuffer<255>({
           4,
@@ -92,10 +106,10 @@
 
   ASSERT_EQ(de.GetKind(), nearby_protocol::V0DataElementKind::Actions);
   auto actions = de.AsActions();
-  ASSERT_EQ(actions.GetAsU32(), 0);
+  ASSERT_EQ(actions.GetAsU32(), (uint32_t)0);
 }
 
-TEST(NpFfiDeserializeV0Tests, V0LengthTwoActionsDataElement) {
+TEST_F(NpCppTest, V0LengthTwoActionsDataElement) {
   nearby_protocol::RawAdvertisementPayload adv(
       nearby_protocol::ByteBuffer<255>({
           5,
@@ -150,7 +164,7 @@
   ASSERT_EQ(actions.GetContextSyncSequenceNumber(), 0xD);
 }
 
-TEST(NpFfiDeserializeV0Tests, V0MultipleDataElements) {
+TEST_F(NpCppTest, V0MultipleDataElements) {
   nearby_protocol::RawAdvertisementPayload adv(nearby_protocol::ByteBuffer<255>(
       {7,
        {
@@ -195,11 +209,11 @@
 
   ASSERT_EQ(second_de.GetKind(), nearby_protocol::V0DataElementKind::Actions);
   auto actions = second_de.AsActions();
-  ASSERT_EQ(actions.GetAsU32(), 0x00460000);
-  ASSERT_EQ(actions.GetContextSyncSequenceNumber(), 0);
+  ASSERT_EQ(actions.GetAsU32(), (uint32_t)0x00460000);
+  ASSERT_EQ(actions.GetContextSyncSequenceNumber(), (uint8_t)0);
 }
 
-TEST(NpFfiDeserializeV0Tests, V0EmptyPayload) {
+TEST_F(NpCppTest, V0EmptyPayload) {
   auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
@@ -210,10 +224,10 @@
             nearby_protocol::DeserializeAdvertisementResultKind::Error);
 }
 
-TEST(NpFfiDeserializeV0Tests, TestV0AdvMoveConstructor) {
+TEST_F(NpCppTest, TestV0AdvMoveConstructor) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  auto result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
   auto adv = result.IntoV0();
@@ -237,17 +251,17 @@
   ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetKind(), "");
 }
 
-TEST(NpFfiDeserializeResultTests, TestV0AdvMoveAssignment) {
+TEST_F(NpCppTest, TestV0AdvMoveAssignment) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  auto result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
   auto adv = result.IntoV0();
 
   // create a second result
-  auto another_result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  auto another_result = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
   ASSERT_EQ(another_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
   auto adv2 = another_result.IntoV0();
@@ -270,39 +284,48 @@
   ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetKind(), "");
 }
 
-TEST(NpFfiDeserializeV0Tests, V0AdvDestructor) {
-  nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(1);
+static nearby_protocol::DeserializeAdvertisementResult
+CreateAdv(nearby_protocol::CredentialBook &book) {
+  auto adv = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
+  return adv;
+}
+
+TEST_F(NpCppTest, V0AdvDestructor) {
   auto book_result = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(book_result.ok());
   {
-    auto deserialize_result =
-        nearby_protocol::Deserializer::DeserializeAdvertisement(
-            V0AdvSimple, book_result.value());
+    auto deserialize_result = CreateAdv(book_result.value());
+    auto deserialize_result2 = CreateAdv(book_result.value());
+    // Deserialize 2 advertisements, which will take up 2 slots in the handle
+    // map
     ASSERT_EQ(deserialize_result.GetKind(),
               np_ffi::internal::DeserializeAdvertisementResultKind::V0);
+    ASSERT_EQ(deserialize_result2.GetKind(),
+              np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 
     // Going over max amount should result in error
-    auto deserialize_result2 =
+    auto deserialize_result3 =
         nearby_protocol::Deserializer::DeserializeAdvertisement(
             V0AdvSimple, book_result.value());
-    ASSERT_EQ(deserialize_result2.GetKind(),
+    ASSERT_EQ(deserialize_result3.GetKind(),
               np_ffi::internal::DeserializeAdvertisementResultKind::Error);
 
-    // Calling IntoV0() should move the underlying resources into the v0 object
-    // when both go out of scope only one should be freed
+    // Calling IntoV0() should move the underlying resources into the v0
+    // object when both go out of scope only one should be freed
     auto v0_adv = deserialize_result.IntoV0();
   }
 
   // Now that the first v0 adv is out of scope, it should be de-allocated which
   // will create room for one more to be created.
-  auto deserialize_result3 =
+  auto deserialize_result =
       nearby_protocol::Deserializer::DeserializeAdvertisement(
           V0AdvSimple, book_result.value());
-  ASSERT_EQ(deserialize_result3.GetKind(),
+  ASSERT_EQ(deserialize_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
 }
 
-TEST(NpFfiDeserializeV0Tests, V0AdvUseAfterMove) {
+TEST_F(NpCppTest, V0AdvUseAfterMove) {
   auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
@@ -322,10 +345,10 @@
   ASSERT_DEATH([[maybe_unused]] auto failure = v0_adv.IntoLegible(), "");
 }
 
-TEST(NpFfiDeserializeV0Tests, TestLegibleAdvMoveConstructor) {
+TEST_F(NpCppTest, TestLegibleAdvMoveConstructor) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  auto result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
   auto legible = result.IntoV0().IntoLegible();
@@ -356,17 +379,17 @@
   ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.IntoPayload(), "");
 }
 
-TEST(NpFfiDeserializeResultTests, TestLegibleAdvMoveAssignment) {
+TEST_F(NpCppTest, TestLegibleAdvMoveAssignment) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  auto result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
   ASSERT_EQ(result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
   auto legible = result.IntoV0().IntoLegible();
 
   // create a second result
-  auto another_result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
+  auto another_result = nearby_protocol::Deserializer::DeserializeAdvertisement(
+      V0AdvSimple, book);
   ASSERT_EQ(another_result.GetKind(),
             np_ffi::internal::DeserializeAdvertisementResultKind::V0);
   auto legible2 = another_result.IntoV0().IntoLegible();
@@ -402,7 +425,7 @@
   return v0_adv.IntoLegible();
 }
 
-TEST(NpFfiDeserializeV0Tests, V0LegibleAdvUseAfterMove) {
+TEST_F(NpCppTest, V0LegibleAdvUseAfterMove) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
   auto legible_adv = CreateLegibleAdv(book);
 
@@ -422,16 +445,19 @@
   ASSERT_DEATH([[maybe_unused]] auto failure = legible_adv.IntoPayload(), "");
 }
 
-TEST(NpFfiDeserializeV0Tests, LegibleAdvDestructor) {
+TEST_F(NpCppTest, LegibleAdvDestructor) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(1);
   {
     auto legible_adv = CreateLegibleAdv(book);
+    auto legible_adv2 = CreateLegibleAdv(book);
 
-    // check that legible adv is valid.
+    // check that legible advs are valid.
     ASSERT_EQ(legible_adv.GetIdentity().GetKind(),
               nearby_protocol::DeserializedV0IdentityKind::Plaintext);
     ASSERT_EQ(legible_adv.GetNumberOfDataElements(), 1);
+    ASSERT_EQ(legible_adv2.GetIdentity().GetKind(),
+              nearby_protocol::DeserializedV0IdentityKind::Plaintext);
+    ASSERT_EQ(legible_adv2.GetNumberOfDataElements(), 1);
 
     // allocation slots should be full
     ASSERT_EQ(nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -453,14 +479,15 @@
   return legible_adv.IntoPayload();
 }
 
-TEST(NpFfiDeserializeV0Tests, V0PayloadDestructor) {
+TEST_F(NpCppTest, V0PayloadDestructor) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(1);
   {
     auto payload = CreatePayload(book);
+    auto payload2 = CreatePayload(book);
 
     // check that payload adv is valid even though its parent is out of scope
     ASSERT_TRUE(payload.TryGetDataElement(0).ok());
+    ASSERT_TRUE(payload2.TryGetDataElement(0).ok());
 
     // allocation slots should be full
     ASSERT_EQ(nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -477,7 +504,7 @@
             nearby_protocol::DeserializeAdvertisementResultKind::V0);
 }
 
-TEST(NpFfiDeserializeV0Tests, TestV0PayloadMoveConstructor) {
+TEST_F(NpCppTest, TestV0PayloadMoveConstructor) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
@@ -503,7 +530,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeResultTests, TestV0PayloadMoveAssignment) {
+TEST_F(NpCppTest, TestV0PayloadMoveAssignment) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V0AdvSimple, book);
@@ -534,10 +561,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeV0Tests, InvalidDataElementCast) {
-  ASSERT_TRUE(
-      nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
-
+TEST_F(NpCppTest, InvalidDataElementCast) {
   auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
@@ -566,7 +590,7 @@
   ASSERT_DEATH([[maybe_unused]] auto failure = de.AsActions();, "");
 }
 
-TEST(NpFfiDeserializeV0Tests, InvalidDataElementIndex) {
+TEST_F(NpCppTest, InvalidDataElementIndex) {
   auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(maybe_credential_book.ok());
   auto deserialize_result =
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
index 115ad14..f46457e 100644
--- a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
@@ -14,10 +14,11 @@
 
 #include "nearby_protocol.h"
 #include "shared_test_util.h"
+#include "np_cpp_test.h"
 
 #include "gtest/gtest.h"
 
-TEST(NpFfiDeserializeV1Tests, V1SimpleTestCase) {
+TEST_F(NpCppTest, V1SimpleTestCase) {
   auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(maybe_credential_book.ok());
 
@@ -47,7 +48,7 @@
 
   auto de = section.value().TryGetDataElement(0);
   ASSERT_TRUE(de.ok());
-  ASSERT_EQ(de.value().GetDataElementTypeCode(), 5);
+  ASSERT_EQ(de.value().GetDataElementTypeCode(), (uint8_t)5);
 
   auto payload = de.value().GetPayload();
   auto vec = payload.ToVector();
@@ -55,7 +56,7 @@
   ASSERT_EQ(vec, expected);
 }
 
-TEST(NpFfiDeserializeV1Tests, TestV1AdvMoveConstructor) {
+TEST_F(NpCppTest, TestV1AdvMoveConstructor) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V1AdvSimple, book);
@@ -91,7 +92,7 @@
                "");
 }
 
-TEST(NpFfiDeserializeV1Tests, TestV1AdvMoveAssignment) {
+TEST_F(NpCppTest, TestV1AdvMoveAssignment) {
   auto book = nearby_protocol::CredentialBook::TryCreate().value();
   auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
       V1AdvSimple, book);
@@ -151,9 +152,7 @@
          np_ffi::internal::DeserializeAdvertisementResultKind::V1;
 }
 
-TEST(NpFfiDeserializeV1Tests, TestSectionOwnership) {
-  // Capping the max number so we can verify that de-allocations are happening
-  nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(1);
+TEST_F(NpCppTest, TestSectionOwnership) {
   auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(maybe_credential_book.ok());
 
@@ -164,6 +163,12 @@
     ASSERT_EQ(section.NumberOfDataElements(), 1);
     ASSERT_TRUE(section.TryGetDataElement(0).ok());
 
+    auto section2 = GetSection(maybe_credential_book.value());
+    ASSERT_EQ(section2.GetIdentityKind(),
+              nearby_protocol::DeserializedV1IdentityKind::Plaintext);
+    ASSERT_EQ(section2.NumberOfDataElements(), 1);
+    ASSERT_TRUE(section2.TryGetDataElement(0).ok());
+
     ASSERT_FALSE(TryDeserializeNewV1Adv(maybe_credential_book.value()));
   }
 
@@ -175,7 +180,7 @@
 /*
  * Multiple sections are not supported in plaintext advertisements
  * TODO Update the below test to use encrypted sections
-TEST(NpFfiDeserializeV1Tests, V1MultipleSections) {
+TEST(NpCppTest, V1MultipleSections) {
   auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
   ASSERT_TRUE(maybe_credential_book.ok());
 
diff --git a/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc b/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc
deleted file mode 100644
index 6936f23..0000000
--- a/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "nearby_protocol.h"
-#include "shared_test_util.h"
-
-#include <iostream>
-
-#include "gtest/gtest.h"
-
-TEST(NpFfiGlobalConfigTests, TestPanicHandler) {
-  ASSERT_TRUE(
-      nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  auto deserialize_result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
-  ASSERT_EQ(deserialize_result.GetKind(),
-            np_ffi::internal::DeserializeAdvertisementResultKind::V0);
-
-  // Now try to cast the result into the wrong type and verify the process
-  // aborts
-  ASSERT_DEATH({ [[maybe_unused]] auto failure = deserialize_result.IntoV1(); },
-               "");
-}
-
-TEST(NpFfiGlobalConfigTests, TestPanicHandlerTwice) {
-  ASSERT_TRUE(
-      nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
-
-  // Second time trying to set should fail
-  ASSERT_FALSE(
-      nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
-}
-
-// There is not much we can actually test here since this will affect memory
-// consumption. This is more of just a simple check that things still work after
-// configuring this
-TEST(NpFfiGlobalConfigTests, TestSetMaxShardsDefault) {
-  // 0 should still work as default behavior
-  nearby_protocol::GlobalConfig::SetNumShards(0);
-
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  auto book2 = nearby_protocol::CredentialBook::TryCreate().value();
-  auto book3 = nearby_protocol::CredentialBook::TryCreate().value();
-  auto deserialize_result =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
-
-  // Should still work
-  ASSERT_EQ(deserialize_result.GetKind(),
-            np_ffi::internal::DeserializeAdvertisementResultKind::V0);
-
-  // Call again with a lower number, should have no effect. books 2 and 3 should
-  // still work.
-  nearby_protocol::GlobalConfig::SetNumShards(1);
-  auto deserialize_result_2 =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
-                                                              book2);
-  ASSERT_EQ(deserialize_result_2.GetKind(),
-            np_ffi::internal::DeserializeAdvertisementResultKind::V0);
-  auto deserialize_result_3 =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
-                                                              book3);
-  ASSERT_EQ(deserialize_result_3.GetKind(),
-            np_ffi::internal::DeserializeAdvertisementResultKind::V0);
-}
-
-TEST(NpFfiGlobalConfigTests, TestSetMaxShardsSmall) {
-  nearby_protocol::GlobalConfig::SetNumShards(1);
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
-
-  // should still be able to parse 2 payloads with only one shard
-  auto deserialize_result1 =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
-  ASSERT_EQ(deserialize_result1.GetKind(),
-            np_ffi::internal::DeserializeAdvertisementResultKind::V0);
-  auto deserialize_result2 =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
-  ASSERT_EQ(deserialize_result2.GetKind(),
-            np_ffi::internal::DeserializeAdvertisementResultKind::V0);
-}
-
-TEST(NpFfiGlobalConfigTests, TestSetMaxCredBooks) {
-  nearby_protocol::GlobalConfig::SetMaxNumCredentialBooks(1);
-  auto book1_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book1_result.ok());
-
-  auto book2_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_FALSE(book2_result.ok());
-  ASSERT_TRUE(absl::IsResourceExhausted(book2_result.status()));
-}
-
-TEST(NpFfiGlobalConfigTests, TestSetMaxCredBooksAfterFirstCall) {
-  auto book = nearby_protocol::CredentialBook::TryCreate().value();
-  auto book2 = nearby_protocol::CredentialBook::TryCreate().value();
-  auto book3 = nearby_protocol::CredentialBook::TryCreate().value();
-
-  // setting this after books have already been created should have no affect
-  nearby_protocol::GlobalConfig::SetMaxNumCredentialBooks(1);
-  auto book4_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book4_result.ok());
-}
-
-TEST(NpFfiGlobalConfigTests, TestSetMaxV0Advs) {
-  nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(1);
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book_result.ok());
-
-  {
-    auto deserialize_result =
-        nearby_protocol::Deserializer::DeserializeAdvertisement(
-            V0AdvSimple, book_result.value());
-    ASSERT_EQ(deserialize_result.GetKind(),
-              np_ffi::internal::DeserializeAdvertisementResultKind::V0);
-
-    // Going over max amount should result in error
-    auto deserialize_result2 =
-        nearby_protocol::Deserializer::DeserializeAdvertisement(
-            V0AdvSimple, book_result.value());
-    ASSERT_EQ(deserialize_result2.GetKind(),
-              np_ffi::internal::DeserializeAdvertisementResultKind::Error);
-  }
-
-  // Now that the first v0 adv is out of scope, it will be de-allocated which
-  // will create room for one more to be created.
-  auto deserialize_result3 =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(
-          V0AdvSimple, book_result.value());
-  ASSERT_EQ(deserialize_result3.GetKind(),
-            np_ffi::internal::DeserializeAdvertisementResultKind::V0);
-}
-
-TEST(NpFfiGlobalConfigTests, TestSetMaxV1Advs) {
-  nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(1);
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book_result.ok());
-
-  {
-    auto deserialize_result =
-        nearby_protocol::Deserializer::DeserializeAdvertisement(
-            V1AdvSimple, book_result.value());
-    ASSERT_EQ(deserialize_result.GetKind(),
-              np_ffi::internal::DeserializeAdvertisementResultKind::V1);
-
-    // Going over max amount should result in error
-    auto deserialize_result2 =
-        nearby_protocol::Deserializer::DeserializeAdvertisement(
-            V1AdvSimple, book_result.value());
-    ASSERT_EQ(deserialize_result2.GetKind(),
-              np_ffi::internal::DeserializeAdvertisementResultKind::Error);
-  }
-
-  // Now that the first v1 adv is out of scope, it will be de-allocated which
-  // will create room for one more to be created.
-  auto deserialize_result3 =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(
-          V1AdvSimple, book_result.value());
-  ASSERT_EQ(deserialize_result3.GetKind(),
-            np_ffi::internal::DeserializeAdvertisementResultKind::V1);
-}
-
-// Same test case as above, but verifies that the de-allocation still succeeds
-// after calling IntoV1() and that no double frees occur.
-TEST(NpFfiGlobalConfigTests, TestSetMaxV1AdvsFreeAfterInto) {
-  nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(1);
-  auto book_result = nearby_protocol::CredentialBook::TryCreate();
-  ASSERT_TRUE(book_result.ok());
-
-  {
-    auto deserialize_result =
-        nearby_protocol::Deserializer::DeserializeAdvertisement(
-            V1AdvSimple, book_result.value());
-    ASSERT_EQ(deserialize_result.GetKind(),
-              np_ffi::internal::DeserializeAdvertisementResultKind::V1);
-
-    // Going over max amount should result in error
-    auto deserialize_result2 =
-        nearby_protocol::Deserializer::DeserializeAdvertisement(
-            V1AdvSimple, book_result.value());
-    ASSERT_EQ(deserialize_result2.GetKind(),
-              np_ffi::internal::DeserializeAdvertisementResultKind::Error);
-
-    // Calling IntoV1() should move the underlying resources into the v0 object
-    // when both go out of scope only one should be freed
-    auto v0_adv = deserialize_result.IntoV1();
-  }
-
-  // Now that the first v1 adv is out of scope, it will be de-allocated which
-  // will create room for one more to be created.
-  auto deserialize_result3 =
-      nearby_protocol::Deserializer::DeserializeAdvertisement(
-          V1AdvSimple, book_result.value());
-  ASSERT_EQ(deserialize_result3.GetKind(),
-            np_ffi::internal::DeserializeAdvertisementResultKind::V1);
-}
\ No newline at end of file
diff --git a/nearby/presence/np_cpp_ffi/tests/np_cpp_test.cc b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.cc
new file mode 100644
index 0000000..52049e7
--- /dev/null
+++ b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.cc
@@ -0,0 +1,17 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "np_cpp_test.h"
+
+bool NpCppTest::panic_handler_set = false;
\ No newline at end of file
diff --git a/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h
new file mode 100644
index 0000000..4fc20e1
--- /dev/null
+++ b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h
@@ -0,0 +1,41 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NEARBY_PRESENCE_NP_CPP_FFI_TESTS_NP_CPP_TEST_H_
+#define NEARBY_PRESENCE_NP_CPP_FFI_TESTS_NP_CPP_TEST_H_
+
+#include "nearby_protocol.h"
+#include "shared_test_util.h"
+
+#include <gtest/gtest.h>
+
+class NpCppTest : public testing::Test {
+protected:
+  static void SetUpTestSuite() {
+    if (!panic_handler_set) {
+      ASSERT_TRUE(
+          nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
+      panic_handler_set = true;
+      nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(2);
+      nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(2);
+      nearby_protocol::GlobalConfig::SetMaxNumCredentialBooks(2);
+    } else {
+      ASSERT_FALSE(
+          nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
+    }
+  }
+  static bool panic_handler_set;
+};
+
+#endif // NEARBY_PRESENCE_NP_CPP_FFI_TESTS_NP_CPP_TEST_H_
diff --git a/nearby/presence/np_ffi_core/Cargo.toml b/nearby/presence/np_ffi_core/Cargo.toml
index cdcc7d3..126302f 100644
--- a/nearby/presence/np_ffi_core/Cargo.toml
+++ b/nearby/presence/np_ffi_core/Cargo.toml
@@ -6,7 +6,7 @@
 
 [dependencies]
 array_view.workspace = true
-np_adv.workspace = true
+np_adv = { workspace = true, features = ["alloc"] }
 handle_map.workspace = true
 crypto_provider.workspace = true
 crypto_provider_default = {workspace = true, default-features = false}
diff --git a/nearby/presence/np_ffi_core/src/deserialize/mod.rs b/nearby/presence/np_ffi_core/src/deserialize/mod.rs
index 9c20fde..38f59ba 100644
--- a/nearby/presence/np_ffi_core/src/deserialize/mod.rs
+++ b/nearby/presence/np_ffi_core/src/deserialize/mod.rs
@@ -87,7 +87,7 @@
     V1(DeserializedV1Advertisement),
 }
 
-struct DeserializeAdvertisementError;
+pub(crate) struct DeserializeAdvertisementError;
 
 impl From<HandleMapFullError> for DeserializeAdvertisementError {
     fn from(_: HandleMapFullError) -> Self {
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v0.rs b/nearby/presence/np_ffi_core/src/deserialize/v0.rs
index 7b9ee75..d8c008a 100644
--- a/nearby/presence/np_ffi_core/src/deserialize/v0.rs
+++ b/nearby/presence/np_ffi_core/src/deserialize/v0.rs
@@ -68,7 +68,7 @@
 
     pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>(
         contents: np_adv::V0AdvertisementContents<M>,
-    ) -> Result<Self, HandleMapFullError> {
+    ) -> Result<Self, DeserializeAdvertisementError> {
         match contents {
             np_adv::V0AdvertisementContents::Plaintext(plaintext_contents) => {
                 let adv = LegibleDeserializedV0Advertisement::allocate_with_plaintext_contents(
@@ -99,8 +99,11 @@
 impl LegibleDeserializedV0Advertisement {
     pub(crate) fn allocate_with_plaintext_contents(
         contents: np_adv::legacy::deserialize::PlaintextAdvContents,
-    ) -> Result<Self, HandleMapFullError> {
-        let data_elements = contents.to_data_elements();
+    ) -> Result<Self, DeserializeAdvertisementError> {
+        let data_elements = contents
+            .data_elements()
+            .collect::<Result<Vec<_>, _>>()
+            .map_err(|_| DeserializeAdvertisementError)?;
         let num_des = data_elements.len() as u8;
         let payload = V0Payload::allocate_with_data_elements(data_elements)?;
         Ok(Self { num_des, payload, identity: DeserializedV0Identity::Plaintext })
@@ -187,6 +190,8 @@
 }
 use v0_payload::V0Payload;
 
+use super::DeserializeAdvertisementError;
+
 impl LocksLongerThan<V0Payload> for CredentialBook {}
 
 impl V0Payload {
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v1.rs b/nearby/presence/np_ffi_core/src/deserialize/v1.rs
index be44fb7..819bb8c 100644
--- a/nearby/presence/np_ffi_core/src/deserialize/v1.rs
+++ b/nearby/presence/np_ffi_core/src/deserialize/v1.rs
@@ -13,11 +13,13 @@
 // limitations under the License.
 //! Core NP Rust FFI structures and methods for v1 advertisement deserialization.
 
+use super::DeserializeAdvertisementError;
 use crate::common::*;
 use crate::credentials::credential_book::CredentialBook;
 use crate::utils::*;
 use array_view::ArrayView;
-use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError};
+use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions};
+use legible_v1_sections::LegibleV1Sections;
 use np_adv::extended::deserialize::DataElementParseError;
 use std::vec::Vec;
 
@@ -59,12 +61,13 @@
 
     pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>(
         contents: np_adv::V1AdvertisementContents<M>,
-    ) -> Result<Self, HandleMapFullError> {
+    ) -> Result<Self, DeserializeAdvertisementError> {
         // 16-section limit enforced by np_adv
         let num_undecryptable_sections = contents.invalid_sections_count() as u8;
         let legible_sections = contents.into_sections();
         let num_legible_sections = legible_sections.len() as u8;
-        let legible_sections = LegibleV1Sections::allocate_with_contents(legible_sections)?;
+        let legible_sections =
+            LegibleV1Sections::allocate_with_contents(legible_sections.into_vec())?;
         Ok(Self { num_undecryptable_sections, num_legible_sections, legible_sections })
     }
 }
@@ -107,14 +110,18 @@
 }
 
 impl<'adv, M: np_adv::credential::MatchedCredential>
-    From<Vec<np_adv::V1DeserializedSection<'adv, M>>> for LegibleV1SectionsInternals
+    TryFrom<Vec<np_adv::V1DeserializedSection<'adv, M>>> for LegibleV1SectionsInternals
 {
-    fn from(contents: Vec<np_adv::V1DeserializedSection<'adv, M>>) -> Self {
+    type Error = DataElementParseError;
+
+    fn try_from(
+        contents: Vec<np_adv::V1DeserializedSection<'adv, M>>,
+    ) -> Result<Self, Self::Error> {
         let sections = contents
             .into_iter()
-            .filter_map(|s| DeserializedV1SectionInternals::try_from(s).ok())
-            .collect();
-        Self { sections }
+            .map(DeserializedV1SectionInternals::try_from)
+            .collect::<Result<Vec<_>, _>>()?;
+        Ok(Self { sections })
     }
 }
 
@@ -131,15 +138,16 @@
         type LegibleV1Sections: HandleLike<Object = super::LegibleV1SectionsInternals>;
     }
 }
-use legible_v1_sections::LegibleV1Sections;
 
 impl LocksLongerThan<LegibleV1Sections> for CredentialBook {}
 
 impl LegibleV1Sections {
     pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>(
         contents: Vec<np_adv::V1DeserializedSection<M>>,
-    ) -> Result<Self, HandleMapFullError> {
-        Self::allocate(move || LegibleV1SectionsInternals::from(contents))
+    ) -> Result<Self, DeserializeAdvertisementError> {
+        let section = LegibleV1SectionsInternals::try_from(contents)
+            .map_err(|_| DeserializeAdvertisementError)?;
+        Self::allocate(move || section).map_err(|e| e.into())
     }
 }
 
diff --git a/nearby/util/handle_map/Cargo.toml b/nearby/util/handle_map/Cargo.toml
index ee9dbf3..fbe3a20 100644
--- a/nearby/util/handle_map/Cargo.toml
+++ b/nearby/util/handle_map/Cargo.toml
@@ -6,7 +6,6 @@
 
 [dependencies]
 lock_adapter.workspace = true
-crypto_provider.workspace = true
 
 [dev-dependencies]
 criterion.workspace = true