diff options
Diffstat (limited to 'nearby/presence/np_ffi_core/src/deserialize/v0.rs')
-rw-r--r-- | nearby/presence/np_ffi_core/src/deserialize/v0.rs | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v0.rs b/nearby/presence/np_ffi_core/src/deserialize/v0.rs new file mode 100644 index 0000000..0f4b24b --- /dev/null +++ b/nearby/presence/np_ffi_core/src/deserialize/v0.rs @@ -0,0 +1,445 @@ +// 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. +//! Core NP Rust FFI structures and methods for v0 advertisement deserialization. + +use crate::common::*; +use crate::credentials::credential_book::CredentialBook; +use crate::utils::{FfiEnum, LocksLongerThan}; +use alloc::vec::Vec; +use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError}; +use np_adv::legacy::actions::ActionsDataElement; +use np_adv::legacy::{data_elements as np_adv_de, Ciphertext, PacketFlavorEnum, Plaintext}; + +/// Discriminant for possible results of V0 advertisement deserialization +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum DeserializedV0AdvertisementKind { + /// The deserialized V0 advertisement was legible. + /// The associated payload may be obtained via + /// `DeserializedV0Advertisement#into_legible`. + Legible = 0, + /// The deserialized V0 advertisement is illegible, + /// likely meaning that the receiver does not hold + /// the proper credentials to be able to read + /// the received advertisement. + NoMatchingCredentials = 1, +} + +/// Represents a deserialized V0 advertisement +#[repr(C)] +#[allow(missing_docs)] +pub enum DeserializedV0Advertisement { + Legible(LegibleDeserializedV0Advertisement), + NoMatchingCredentials, +} + +impl FfiEnum for DeserializedV0Advertisement { + type Kind = DeserializedV0AdvertisementKind; + fn kind(&self) -> Self::Kind { + match self { + DeserializedV0Advertisement::Legible(_) => DeserializedV0AdvertisementKind::Legible, + DeserializedV0Advertisement::NoMatchingCredentials => { + DeserializedV0AdvertisementKind::NoMatchingCredentials + } + } + } +} + +impl DeserializedV0Advertisement { + /// Attempts to deallocate memory utilized internally by this V0 advertisement + /// (which contains a handle to actual advertisement contents behind-the-scenes). + pub fn deallocate(self) -> DeallocateResult { + match self { + DeserializedV0Advertisement::Legible(adv) => adv.deallocate(), + DeserializedV0Advertisement::NoMatchingCredentials => DeallocateResult::Success, + } + } + + pub(crate) fn allocate_with_contents<'m, M: np_adv::credential::MatchedCredential<'m>>( + contents: np_adv::V0AdvContents<'m, M>, + ) -> Result<Self, HandleMapFullError> { + match contents { + np_adv::V0AdvContents::Plaintext(plaintext_contents) => { + let adv = LegibleDeserializedV0Advertisement::allocate_with_plaintext_contents( + plaintext_contents, + )?; + Ok(Self::Legible(adv)) + } + np_adv::V0AdvContents::Decrypted(_) => { + unimplemented!(); + } + np_adv::V0AdvContents::NoMatchingCredentials => Ok(Self::NoMatchingCredentials), + } + } + + declare_enum_cast! {into_legible, Legible, LegibleDeserializedV0Advertisement} +} + +/// Represents a deserialized V0 advertisement whose DE contents may be read +#[repr(C)] +pub struct LegibleDeserializedV0Advertisement { + num_des: u8, + payload: V0Payload, + identity: DeserializedV0Identity, +} + +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(); + 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 }) + } + /// Gets the number of data-elements in this adv's payload + /// Suitable as an iteration bound for `Self.into_payload().get_de(...)`. + pub fn num_des(&self) -> u8 { + self.num_des + } + /// Destructures this legible advertisement into just the payload + pub fn into_payload(self) -> V0Payload { + self.payload + } + /// Destructures this legible advertisement into just the identity information + pub fn into_identity(self) -> DeserializedV0Identity { + self.identity + } + /// Deallocates the underlying handle of the payload + pub fn deallocate(self) -> DeallocateResult { + self.payload.deallocate().map(|_| ()).into() + } +} + +/// Discriminant for `DeserializedV0Identity`. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum DeserializedV0IdentityKind { + /// The deserialized identity was a plaintext identity. + Plaintext = 0, + /// The deserialized identity was some decrypted identity. + Decrypted = 1, +} + +/// Represents deserialized information about the V0 identity utilized +/// by a deserialized V0 advertisement +#[repr(C)] +#[allow(missing_docs)] +pub enum DeserializedV0Identity { + Plaintext, + // TODO: This gets a payload once we support creds + Decrypted, +} + +impl FfiEnum for DeserializedV0Identity { + type Kind = DeserializedV0IdentityKind; + fn kind(&self) -> Self::Kind { + match self { + DeserializedV0Identity::Plaintext => DeserializedV0IdentityKind::Plaintext, + DeserializedV0Identity::Decrypted => DeserializedV0IdentityKind::Decrypted, + } + } +} + +/// The internal data-structure used for storing +/// the payload of a deserialized V0 advertisement. +pub struct V0PayloadInternals { + des: Vec<V0DataElement>, +} + +impl V0PayloadInternals { + /// Attempts to get the DE with the given index + /// in this v0 payload. + fn get_de(&self, index: u8) -> GetV0DEResult { + match self.des.get(index as usize) { + Some(de) => GetV0DEResult::Success(de.clone()), + None => GetV0DEResult::Error, + } + } +} + +fn get_v0_payload_handle_map_dimensions() -> HandleMapDimensions { + HandleMapDimensions { + num_shards: global_num_shards(), + max_active_handles: global_max_num_deserialized_v0_advertisements(), + } +} + +declare_handle_map! {v0_payload, V0Payload, super::V0PayloadInternals, super::get_v0_payload_handle_map_dimensions() } +use v0_payload::V0Payload; + +impl LocksLongerThan<V0Payload> for CredentialBook {} + +impl V0Payload { + pub(crate) fn allocate_with_data_elements<F: np_adv::legacy::PacketFlavor>( + data_elements: Vec<np_adv::legacy::deserialize::PlainDataElement<F>>, + ) -> Result<Self, HandleMapFullError> { + Self::allocate(move || { + let des = data_elements.into_iter().map(V0DataElement::from).collect(); + V0PayloadInternals { des } + }) + } + /// Gets the data-element with the given index in this v0 adv payload + pub fn get_de(&self, index: u8) -> GetV0DEResult { + match self.get() { + Ok(read_guard) => read_guard.get_de(index), + Err(_) => GetV0DEResult::Error, + } + } + + /// Deallocates any underlying data held by a V0Payload + pub fn deallocate_payload(&self) -> DeallocateResult { + self.deallocate().map(|_| ()).into() + } +} + +/// Discriminant of `GetV0DEResult`. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum GetV0DEResultKind { + /// The attempt to get the DE succeeded. + /// The associated payload may be obtained via + /// `GetV0DEResult#into_success`. + Success = 0, + /// The attempt to get the DE failed, + /// possibly due to the requested index being + /// out-of-bounds or due to the advertisement + /// having been previously deallocated. + Error = 1, +} + +/// The result of `V0Payload#get_de`. +#[repr(C)] +#[allow(missing_docs)] +pub enum GetV0DEResult { + Success(V0DataElement), + Error, +} + +impl FfiEnum for GetV0DEResult { + type Kind = GetV0DEResultKind; + fn kind(&self) -> Self::Kind { + match self { + GetV0DEResult::Success(_) => GetV0DEResultKind::Success, + GetV0DEResult::Error => GetV0DEResultKind::Error, + } + } +} + +impl GetV0DEResult { + declare_enum_cast! {into_success, Success, V0DataElement} +} + +/// Discriminant for `V0DataElement`. +#[repr(u8)] +pub enum V0DataElementKind { + /// A transmission Power (Tx Power) data-element. + /// The associated payload may be obtained via + /// `V0DataElement#into_tx_power`. + TxPower = 0, + /// The Actions data-element. + /// The associated payload may be obtained via + /// `V0DataElement#into_actions`. + Actions = 1, +} + +/// Representation of a V0 data element. +#[repr(C)] +#[allow(missing_docs)] +#[derive(Clone)] +pub enum V0DataElement { + TxPower(TxPower), + Actions(V0Actions), +} + +impl<F: np_adv::legacy::PacketFlavor> From<np_adv::legacy::deserialize::PlainDataElement<F>> + for V0DataElement +{ + fn from(de: np_adv::legacy::deserialize::PlainDataElement<F>) -> Self { + use np_adv::legacy::deserialize::PlainDataElement; + match de { + PlainDataElement::Actions(x) => V0DataElement::Actions(x.into()), + PlainDataElement::TxPower(x) => V0DataElement::TxPower(x.into()), + } + } +} + +impl FfiEnum for V0DataElement { + type Kind = V0DataElementKind; + fn kind(&self) -> Self::Kind { + match self { + V0DataElement::Actions(_) => V0DataElementKind::Actions, + V0DataElement::TxPower(_) => V0DataElementKind::TxPower, + } + } +} + +impl V0DataElement { + declare_enum_cast! {into_tx_power, TxPower, TxPower} + declare_enum_cast! {into_actions, Actions, V0Actions} +} + +/// Representation of a transmission power, +/// as used for the Tx Power DE in V0 and V1. +#[derive(Clone)] +#[repr(C)] +pub struct TxPower { + tx_power: i8, +} + +impl TxPower { + /// Yields this Tx Power value as an i8. + pub fn as_i8(&self) -> i8 { + self.tx_power + } +} + +impl From<np_adv_de::TxPowerDataElement> for TxPower { + fn from(de: np_adv_de::TxPowerDataElement) -> Self { + Self { tx_power: de.tx_power_value() } + } +} + +/// Representation of the Actions DE in V0. +#[derive(Clone)] +#[repr(C)] +pub enum V0Actions { + /// A set of action bits which were present in a plaintext identity advertisement + Plaintext(V0ActionBits), + /// A set of action bits which were present in a encrypted identity advertisement + Encrypted(V0ActionBits), +} + +impl<F: np_adv::legacy::PacketFlavor> From<np_adv::legacy::actions::ActionsDataElement<F>> + for V0Actions +{ + fn from(value: ActionsDataElement<F>) -> Self { + match F::ENUM_VARIANT { + PacketFlavorEnum::Plaintext => { + Self::Plaintext(V0ActionBits { bitfield: value.as_u32() }) + } + PacketFlavorEnum::Ciphertext => { + Self::Encrypted(V0ActionBits { bitfield: value.as_u32() }) + } + } + } +} + +#[repr(C)] +#[derive(Clone)] +/// The bitfield data of a VOActions data element +pub struct V0ActionBits { + bitfield: u32, +} + +#[allow(missing_docs)] +#[repr(u8)] +/// The possible boolean action types which can be present in an Actions data element +pub enum BooleanActionType { + ActiveUnlock = 8, + NearbyShare = 9, + InstantTethering = 10, + PhoneHub = 11, + PresenceManager = 12, + Finder = 13, + FastPairSass = 14, +} + +impl From<&BooleanActionType> for np_adv::legacy::actions::ActionType { + fn from(value: &BooleanActionType) -> Self { + match value { + BooleanActionType::ActiveUnlock => np_adv::legacy::actions::ActionType::ActiveUnlock, + BooleanActionType::NearbyShare => np_adv::legacy::actions::ActionType::NearbyShare, + BooleanActionType::InstantTethering => { + np_adv::legacy::actions::ActionType::InstantTethering + } + BooleanActionType::PhoneHub => np_adv::legacy::actions::ActionType::PhoneHub, + BooleanActionType::Finder => np_adv::legacy::actions::ActionType::Finder, + BooleanActionType::FastPairSass => np_adv::legacy::actions::ActionType::FastPairSass, + BooleanActionType::PresenceManager => { + np_adv::legacy::actions::ActionType::PresenceManager + } + } + } +} + +/// Error returned if action bits inside of a V0Actions struct are invalid. If the struct was +/// created by the np_adv deserializer, the bits will always be valid, they are only invalid if a +/// user reaches in and changes them to something invalid. +#[derive(Debug)] +pub struct InvalidActionBits; + +impl V0Actions { + /// Gets the V0 Action bits as represented by a u32 where the last 8 bits are + /// always 0 since V0 actions can only hold up to 24 bits. + pub fn as_u32(&self) -> u32 { + match self { + V0Actions::Plaintext(bits) => bits.bitfield, + V0Actions::Encrypted(bits) => bits.bitfield, + } + } + + /// Return whether a boolean action type is set in this data element + #[allow(clippy::expect_used)] + pub fn has_action(&self, action_type: &BooleanActionType) -> Result<bool, InvalidActionBits> { + match self { + V0Actions::Plaintext(action_bits) => { + let bits = np_adv::legacy::actions::ActionBits::<Plaintext>::try_from( + action_bits.bitfield, + ) + .map_err(|_| InvalidActionBits)?; + + let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits); + Ok(actions_de + .has_action(&action_type.into()) + .expect("BooleanActionType only has one bit")) + } + V0Actions::Encrypted(action_bits) => { + let bits = np_adv::legacy::actions::ActionBits::<Ciphertext>::try_from( + action_bits.bitfield, + ) + .map_err(|_| InvalidActionBits)?; + + let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits); + Ok(actions_de + .has_action(&action_type.into()) + .expect("BooleanActionType only has one bit")) + } + } + } + + /// Return the context sequence number from this data element + #[allow(clippy::expect_used)] + pub fn get_context_sync_seq_num(&self) -> Result<u8, InvalidActionBits> { + match self { + V0Actions::Plaintext(action_bits) => { + let bits = np_adv::legacy::actions::ActionBits::<Plaintext>::try_from( + action_bits.bitfield, + ) + .map_err(|_| InvalidActionBits)?; + + let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits); + Ok(actions_de.context_sync_seq_num().as_u8()) + } + V0Actions::Encrypted(action_bits) => { + let bits = np_adv::legacy::actions::ActionBits::<Ciphertext>::try_from( + action_bits.bitfield, + ) + .map_err(|_| InvalidActionBits)?; + let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits); + Ok(actions_de.context_sync_seq_num().as_u8()) + } + } + } +} |