blob: eb2ef0f5beb7ab5f4648a0cdb10ca55f600e357e [file] [log] [blame]
// 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<DeserializedAdvertisement<'adv, B::Matched>, 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::<B, P>(encoding, cred_book, remaining)
.map(DeserializedAdvertisement::V0),
NpVersionHeader::V1(header) => {
deser_decrypt_v1::<B, P>(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<V0AdvertisementContents<'adv, M>> {
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<V1AdvertisementContents<'adv, M>> {
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<legacy::deserialize::AdvDeserializeError> 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 {}
}