// Copyright 2022 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. //! Serialization and deserialization for v0 (legacy) and v1 (extended) Nearby Presence //! advertisements. //! //! See `tests/examples_v0.rs` and `tests/examples_v1.rs` for some tests that show common //! deserialization scenarios. #![no_std] #![allow(clippy::expect_used, clippy::indexing_slicing, clippy::panic)] #[cfg(any(test, feature = "alloc"))] extern crate alloc; pub use strum; use crate::credential::matched::MatchedCredential; use crate::extended::deserialize::{deser_decrypt_v1, V1AdvertisementContents}; use crate::{ credential::book::CredentialBook, header::NpVersionHeader, legacy::{deser_decrypt_v0, V0AdvertisementContents}, }; use core::fmt::Debug; use crypto_provider::CryptoProvider; use deserialization_arena::DeserializationArena; use legacy::data_elements::DataElementDeserializeError; #[cfg(test)] mod tests; pub mod credential; pub mod deserialization_arena; pub mod extended; pub mod filter; pub mod legacy; pub mod shared_data; mod array_vec; mod header; mod helpers; /// Canonical form of NP's service UUID. /// /// Note that UUIDs are encoded in BT frames in little-endian order, so these bytes may need to be /// reversed depending on the host BT API. pub const NP_SVC_UUID: [u8; 2] = [0xFC, 0xF1]; /// 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>( arena: DeserializationArena<'adv>, adv: &'adv [u8], cred_book: &'cred B, ) -> Result, AdvDeserializationError> where B: CredentialBook<'cred>, P: CryptoProvider, { let (remaining, header) = NpVersionHeader::parse(adv) .map_err(|_e| AdvDeserializationError::VersionHeaderParseError)?; match header { NpVersionHeader::V0(encoding) => deser_decrypt_v0::(encoding, cred_book, remaining) .map(DeserializedAdvertisement::V0), NpVersionHeader::V1(header) => { deser_decrypt_v1::(arena, cred_book, remaining, header) .map(DeserializedAdvertisement::V1) } } } /// 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<'adv, M>), /// V1 advertisement V1(V1AdvertisementContents<'adv, M>), } 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> { match self { Self::V0(x) => Some(x), _ => None, } } /// Attempts to cast this deserialized advertisement into the `V1AdvertisementContents` /// variant. If the underlying advertisement is not V1, this will instead return `None`. pub fn into_v1(self) -> Option> { match self { Self::V1(x) => Some(x), _ => None, } } } /// Errors that can occur during advertisement deserialization. #[derive(PartialEq)] pub enum AdvDeserializationError { /// The advertisement header could not be parsed VersionHeaderParseError, /// The advertisement content could not be parsed ParseError { /// Potentially hazardous details about deserialization errors. Read the documentation for /// [AdvDeserializationErrorDetailsHazmat] before using this field. details_hazmat: AdvDeserializationErrorDetailsHazmat, }, } impl Debug for AdvDeserializationError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { AdvDeserializationError::VersionHeaderParseError => { write!(f, "VersionHeaderParseError") } AdvDeserializationError::ParseError { .. } => write!(f, "ParseError"), } } } /// Potentially hazardous details about deserialization errors. These error information can /// potentially expose side-channel information about the plaintext of the advertisements and/or /// the keys used to decrypt them. For any place that you avoid exposing the keys directly /// (e.g. across FFIs, print to log, etc), avoid exposing these error details as well. #[derive(PartialEq)] pub enum AdvDeserializationErrorDetailsHazmat { /// Parsing the overall advertisement or DE structure failed AdvertisementDeserializeError, /// Deserializing an individual DE from its DE contents failed V0DataElementDeserializeError(DataElementDeserializeError), /// Non-identity DE contents must not be empty NoPublicDataElements, } impl From for AdvDeserializationError { fn from(err: legacy::deserialize::AdvDeserializeError) -> Self { match err { legacy::deserialize::AdvDeserializeError::NoDataElements => { AdvDeserializationError::ParseError { details_hazmat: AdvDeserializationErrorDetailsHazmat::NoPublicDataElements, } } legacy::deserialize::AdvDeserializeError::InvalidStructure => { AdvDeserializationError::ParseError { details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError, } } } } } /// DE length is out of range (e.g. > 4 bits for encoded V0, > max DE size for actual V0, >127 for /// V1) or invalid for the relevant DE type. #[derive(Debug, PartialEq, Eq)] pub struct DeLengthOutOfRange; pub(crate) mod private { /// A marker trait to prevent other crates from implementing traits that /// are intended to be only implemented internally. pub trait Sealed {} }