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 §ion.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