diff options
Diffstat (limited to 'nearby/presence/np_ffi_core/src/serialize/v1.rs')
-rw-r--r-- | nearby/presence/np_ffi_core/src/serialize/v1.rs | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/nearby/presence/np_ffi_core/src/serialize/v1.rs b/nearby/presence/np_ffi_core/src/serialize/v1.rs new file mode 100644 index 0000000..ccc6ef7 --- /dev/null +++ b/nearby/presence/np_ffi_core/src/serialize/v1.rs @@ -0,0 +1,589 @@ +// 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. + +//! NP Rust FFI structures and methods for V1 advertisement serialization. + +use crate::common::*; +use crate::credentials::V1BroadcastCredential; +use crate::serialize::AdvertisementBuilderKind; +use crate::utils::FfiEnum; +use crate::v1::V1VerificationMode; +use crypto_provider_default::CryptoProviderImpl; +use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError}; +use np_adv; +use np_adv_dynamic; + +/// A handle to a builder for V1 advertisements. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct V1AdvertisementBuilder { + kind: AdvertisementBuilderKind, + handle: V1AdvertisementBuilderHandle, +} + +impl V1AdvertisementBuilder { + /// Attempts to create a builder for a new public section within + /// this advertisement, returning a handle to the newly-created + /// section builder if successful. + /// + /// This method may fail if there is another currently-active + /// section builder for the same advertisement builder, if the + /// kind of section being added does not match the advertisement + /// type (public/encrypted), or if the section would not manage + /// to fit within the enclosing advertisement. + pub fn public_section_builder(&self) -> CreateV1SectionBuilderResult { + self.section_builder_internals(|internals| internals.public_section_builder()) + } + /// Attempts to create a builder for a new encrypted section within + /// this advertisement, returning a handle to the newly-created + /// section builder if successful. + /// + /// The identity details for the new section builder may be specified + /// via providing the broadcast credential data, the kind of encrypted + /// identity being broadcast (private/trusted/provisioned), and the + /// verification mode (MIC/Signature) to be used for the encrypted section. + /// + /// This method may fail if there is another currently-active + /// section builder for the same advertisement builder, if the + /// kind of section being added does not match the advertisement + /// type (public/encrypted), or if the section would not manage + /// to fit within the enclosing advertisement. + pub fn encrypted_section_builder( + &self, + broadcast_cred: V1BroadcastCredential, + identity_type: EncryptedIdentityType, + verification_mode: V1VerificationMode, + ) -> CreateV1SectionBuilderResult { + self.section_builder_internals(move |internals| { + internals.encrypted_section_builder(broadcast_cred, identity_type, verification_mode) + }) + } + + fn section_builder_internals( + &self, + builder_supplier: impl FnOnce( + &mut V1AdvertisementBuilderInternals, + ) -> Result<usize, SectionBuilderError>, + ) -> CreateV1SectionBuilderResult { + match self.handle.get_mut() { + Ok(mut adv_builder_write_guard) => { + match builder_supplier(&mut adv_builder_write_guard) { + Ok(section_index) => CreateV1SectionBuilderResult::Success(V1SectionBuilder { + adv_builder: *self, + section_index: section_index as u8, + }), + Err(e) => e.into(), + } + } + Err(_) => CreateV1SectionBuilderResult::InvalidAdvBuilderHandle, + } + } +} + +/// Discriminant for `CreateV1AdvertisementBuilderResult` + +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum CreateV1AdvertisementBuilderResultKind { + /// The attempt to create a new advertisement builder + /// failed since there are no more available + /// slots for V1 advertisement builders in their handle-map. + NoSpaceLeft = 0, + /// The attempt succeeded. The wrapped advertisement builder + /// may be obtained via + /// `CreateV1AdvertisementBuilderResult#into_success`. + Success = 1, +} + +/// The result of attempting to create a new V1 advertisement builder. +#[repr(C)] +#[allow(missing_docs)] +pub enum CreateV1AdvertisementBuilderResult { + NoSpaceLeft, + Success(V1AdvertisementBuilder), +} + +impl From<Result<V1AdvertisementBuilder, HandleMapFullError>> + for CreateV1AdvertisementBuilderResult +{ + fn from(result: Result<V1AdvertisementBuilder, HandleMapFullError>) -> Self { + match result { + Ok(builder) => CreateV1AdvertisementBuilderResult::Success(builder), + Err(_) => CreateV1AdvertisementBuilderResult::NoSpaceLeft, + } + } +} + +impl FfiEnum for CreateV1AdvertisementBuilderResult { + type Kind = CreateV1AdvertisementBuilderResultKind; + fn kind(&self) -> Self::Kind { + match self { + CreateV1AdvertisementBuilderResult::NoSpaceLeft => { + CreateV1AdvertisementBuilderResultKind::NoSpaceLeft + } + CreateV1AdvertisementBuilderResult::Success(_) => { + CreateV1AdvertisementBuilderResultKind::Success + } + } + } +} + +impl CreateV1AdvertisementBuilderResult { + declare_enum_cast! {into_success, Success, V1AdvertisementBuilder } +} + +/// Creates a new V1 advertisement builder for the given advertisement +/// kind (public/encrypted). +pub fn create_v1_advertisement_builder( + kind: AdvertisementBuilderKind, +) -> CreateV1AdvertisementBuilderResult { + V1AdvertisementBuilderHandle::allocate(move || V1AdvertisementBuilderInternals::new(kind)) + .map(|handle| V1AdvertisementBuilder { kind, handle }) + .into() +} + +impl V1AdvertisementBuilder { + /// Gets the kind of advertisement builder (public/encrypted) + pub fn kind(&self) -> AdvertisementBuilderKind { + self.kind + } +} + +pub(crate) enum V1AdvertisementBuilderState { + /// Internal state for when we have an active advertisement + /// builder, but no currently-active section builder. + Advertisement(np_adv_dynamic::extended::BoxedAdvBuilder), + /// Internal state for when we have both an active advertisement + /// builder and an active section builder. + Section( + np_adv_dynamic::extended::BoxedSectionBuilder< + np_adv::extended::serialize::AdvBuilder, + CryptoProviderImpl, + >, + ), +} + +/// Internal version of errors which may be raised when +/// attempting to derive a new section builder from an +/// advertisement builder. +pub(crate) enum SectionBuilderError { + /// We're currently in the middle of building a section. + UnclosedActiveSection, + /// We're attempting to build a section with an identity + /// kind (public/encrypted) which doesn't match the kind + /// for the entire advertisement. + IdentityKindMismatch, + /// There isn't enough space for a new section, either + /// because the maximum section count has been exceeded + /// or because the advertisement is almost full, and + /// the minimum size of a section wouldn't fit. + NoSpaceLeft, +} + +impl From<np_adv_dynamic::extended::BoxedAddSectionError> for SectionBuilderError { + fn from(err: np_adv_dynamic::extended::BoxedAddSectionError) -> Self { + use np_adv::extended::serialize::AddSectionError; + use np_adv_dynamic::extended::BoxedAddSectionError; + match err { + BoxedAddSectionError::IdentityRequiresSaltError + | BoxedAddSectionError::Underlying(AddSectionError::IncompatibleSectionType) => { + SectionBuilderError::IdentityKindMismatch + } + BoxedAddSectionError::Underlying(AddSectionError::InsufficientAdvSpace) + | BoxedAddSectionError::Underlying(AddSectionError::MaxSectionCountExceeded) => { + SectionBuilderError::NoSpaceLeft + } + } + } +} + +/// Internal, Rust-side implementation of a V1 advertisement builder. +pub struct V1AdvertisementBuilderInternals { + // Note: This is actually always populated from an external + // perspective in the absence of panics. We only need + // the `Option` in order to be able to take ownership + // and perform a state transition when needed. + state: Option<V1AdvertisementBuilderState>, +} + +impl V1AdvertisementBuilderInternals { + pub(crate) fn new(kind: AdvertisementBuilderKind) -> Self { + let adv_type = kind.as_internal_v1(); + let builder = np_adv::extended::serialize::AdvBuilder::new(adv_type); + let builder = builder.into(); + let state = Some(V1AdvertisementBuilderState::Advertisement(builder)); + Self { state } + } + /// Internals of section_builder-type routines. Upon success, yields the index + /// of the newly-added section builder. + pub(crate) fn section_builder_internal( + &mut self, + identity: np_adv_dynamic::extended::BoxedIdentity<CryptoProviderImpl>, + ) -> Result<usize, SectionBuilderError> { + let state = self.state.take(); + match state { + Some(V1AdvertisementBuilderState::Advertisement(adv_builder)) => { + match adv_builder.into_section_builder::<CryptoProviderImpl>(identity) { + Ok(section_builder) => { + let section_index = section_builder.section_index(); + self.state = Some(V1AdvertisementBuilderState::Section(section_builder)); + Ok(section_index) + } + Err((adv_builder, err)) => { + self.state = Some(V1AdvertisementBuilderState::Advertisement(adv_builder)); + Err(err.into()) + } + } + } + x => { + // Note: Technically, this case also would leave the `None` state + // if we ever entered into it, but we never transition to that + // state during normal operation. + self.state = x; + Err(SectionBuilderError::UnclosedActiveSection) + } + } + } + + pub(crate) fn public_section_builder(&mut self) -> Result<usize, SectionBuilderError> { + let identity = np_adv_dynamic::extended::BoxedIdentity::PublicIdentity; + self.section_builder_internal(identity) + } + pub(crate) fn encrypted_section_builder( + &mut self, + broadcast_cred: V1BroadcastCredential, + identity_type: EncryptedIdentityType, + verification_mode: V1VerificationMode, + ) -> Result<usize, SectionBuilderError> { + let mut rng = get_global_crypto_rng(); + let rng = rng.get_rng(); + let identity_type = identity_type.into(); + let internal_broadcast_cred = broadcast_cred.into_internal(); + let identity = match verification_mode { + V1VerificationMode::Mic => { + let encoder = np_adv::extended::serialize::MicEncryptedSectionEncoder::< + CryptoProviderImpl, + >::new_random_salt( + rng, identity_type, &internal_broadcast_cred + ); + np_adv_dynamic::extended::BoxedIdentity::MicEncrypted(encoder) + } + V1VerificationMode::Signature => { + let encoder = np_adv::extended::serialize::SignedEncryptedSectionEncoder::< + CryptoProviderImpl, + >::new_random_salt( + rng, identity_type, &internal_broadcast_cred + ); + np_adv_dynamic::extended::BoxedIdentity::SignedEncrypted(encoder) + } + }; + self.section_builder_internal(identity) + } +} + +fn get_v1_advertisement_builder_handle_map_dimensions() -> HandleMapDimensions { + HandleMapDimensions { + num_shards: global_num_shards(), + max_active_handles: global_max_num_v1_advertisement_builders(), + } +} + +declare_handle_map! { + mod advertisement_builder { + #[dimensions = super::get_v1_advertisement_builder_handle_map_dimensions()] + type V1AdvertisementBuilderHandle: HandleLike<Object = super::V1AdvertisementBuilderInternals>; + } +} +use crate::serialize::v1::advertisement_builder::V1AdvertisementBuilderHandle; + +/// Discriminant for `CreateV1SectionBuilderResult` +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum CreateV1SectionBuilderResultKind { + /// The attempt to create a new section builder succeeded. + Success = 0, + /// We're currently in the middle of building a section. + UnclosedActiveSection = 1, + /// The advertisement builder handle was invalid. + InvalidAdvBuilderHandle = 2, + /// We're attempting to build a section with an identity + /// kind (public/encrypted) which doesn't match the kind + /// for the entire advertisement. + IdentityKindMismatch = 3, + /// There isn't enough space for a new section, either + /// because the maximum section count has been exceeded + /// or because the advertisement is almost full, and + /// the minimum size of a section wouldn't fit. + NoSpaceLeft = 4, +} + +/// The result of attempting to create a new V1 section builder. +#[repr(C)] +#[allow(missing_docs)] +pub enum CreateV1SectionBuilderResult { + Success(V1SectionBuilder), + UnclosedActiveSection, + InvalidAdvBuilderHandle, + IdentityKindMismatch, + NoSpaceLeft, +} + +impl FfiEnum for CreateV1SectionBuilderResult { + type Kind = CreateV1SectionBuilderResultKind; + fn kind(&self) -> Self::Kind { + match self { + Self::Success(_) => CreateV1SectionBuilderResultKind::Success, + Self::UnclosedActiveSection => CreateV1SectionBuilderResultKind::UnclosedActiveSection, + Self::InvalidAdvBuilderHandle => { + CreateV1SectionBuilderResultKind::InvalidAdvBuilderHandle + } + Self::IdentityKindMismatch => CreateV1SectionBuilderResultKind::IdentityKindMismatch, + Self::NoSpaceLeft => CreateV1SectionBuilderResultKind::NoSpaceLeft, + } + } +} + +impl CreateV1SectionBuilderResult { + declare_enum_cast! {into_success, Success, V1SectionBuilder} +} + +impl From<SectionBuilderError> for CreateV1SectionBuilderResult { + fn from(err: SectionBuilderError) -> Self { + match err { + SectionBuilderError::UnclosedActiveSection => Self::UnclosedActiveSection, + SectionBuilderError::IdentityKindMismatch => Self::IdentityKindMismatch, + SectionBuilderError::NoSpaceLeft => Self::NoSpaceLeft, + } + } +} + +/// Result code for [`V1SectionBuilder#add_to_advertisement`]. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum AddV1SectionToAdvertisementResult { + /// The section referenced by the given handle + /// couldn't be added to the containing advertisement, + /// possibly because the handle is invalid or the section + /// has already been added to the containing section. + Error = 0, + /// The section referenced by the given handle + /// was successfully added to the containing advertisement. + /// After obtaining this result code, the section + /// handle will no longer be valid. + Success = 1, +} + +/// Result code for operations adding DEs to a section builder. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum AddV1DEResult { + /// The DE was successfully added to the section builder + /// behind the given handle. + Success = 0, + /// The handle for the section builder was invalid. + InvalidSectionHandle = 1, + /// There was no more space left in the advertisement + /// to fit the DE in the containing section. + InsufficientSectionSpace = 2, + /// The data element itself had invalid characteristics, + /// most likely a length above 127. + InvalidDataElement = 3, +} + +/// Discriminant for `NextV1DE16ByteSaltResult`. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum NextV1DE16ByteSaltResultKind { + /// We couldn't return a 16-byte DE salt, possibly + /// because the handle to the section builder + /// was invalid, or possibly because the section + /// builder was for a public section. + Error = 0, + /// A 16-byte DE salt was returned successfully. + Success = 1, +} + +/// The result of attempting to get the derived V1 DE +/// 16-byte salt for the next-added DE to the section +/// builder behind the given handle. +#[derive(Clone, Copy)] +#[repr(C)] +#[allow(missing_docs)] +pub enum NextV1DE16ByteSaltResult { + Error, + Success([u8; 16]), +} + +impl FfiEnum for NextV1DE16ByteSaltResult { + type Kind = NextV1DE16ByteSaltResultKind; + fn kind(&self) -> Self::Kind { + match self { + Self::Error => NextV1DE16ByteSaltResultKind::Error, + Self::Success(_) => NextV1DE16ByteSaltResultKind::Success, + } + } +} + +impl NextV1DE16ByteSaltResult { + declare_enum_cast! {into_success, Success, [u8; 16] } +} + +impl From<Option<np_adv::extended::serialize::DeSalt<CryptoProviderImpl>>> + for NextV1DE16ByteSaltResult +{ + fn from(maybe_salt: Option<np_adv::extended::serialize::DeSalt<CryptoProviderImpl>>) -> Self { + match maybe_salt.and_then(|salt| salt.derive::<16>()) { + Some(salt) => NextV1DE16ByteSaltResult::Success(salt), + None => NextV1DE16ByteSaltResult::Error, + } + } +} + +/// A handle to a builder for V1 sections. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct V1SectionBuilder { + adv_builder: V1AdvertisementBuilder, + section_index: u8, +} + +impl V1SectionBuilder { + /// Attempts to add the section constructed behind this handle + /// to a section builder to the containing advertisement it + /// originated from. + pub fn add_to_advertisement(self) -> AddV1SectionToAdvertisementResult { + match self.adv_builder.handle.get_mut() { + Ok(mut adv_builder) => { + let state = adv_builder.state.take(); + match state { + Some(V1AdvertisementBuilderState::Section(section_builder)) => { + // Make sure the index of the section we're trying to close + // matches the index of the section currently under construction. + let actual_section_index = section_builder.section_index() as u8; + if self.section_index == actual_section_index { + let updated_adv_builder = section_builder.add_to_advertisement(); + adv_builder.state = Some(V1AdvertisementBuilderState::Advertisement( + updated_adv_builder, + )); + AddV1SectionToAdvertisementResult::Success + } else { + adv_builder.state = + Some(V1AdvertisementBuilderState::Section(section_builder)); + AddV1SectionToAdvertisementResult::Error + } + } + x => { + adv_builder.state = x; + AddV1SectionToAdvertisementResult::Error + } + } + } + Err(_) => AddV1SectionToAdvertisementResult::Error, + } + } + + /// Attempts to get the derived 16-byte V1 DE salt for the next + /// DE to be added to this section builder. May fail if this + /// section builder handle is invalid, or if the section + /// is a public section. + pub fn next_de_salt(&self) -> NextV1DE16ByteSaltResult { + self.try_apply_to_internals( + |section_builder| section_builder.next_de_salt().into(), + NextV1DE16ByteSaltResult::Error, + ) + } + + /// Attempts to add the given DE to the section builder behind + /// this handle. The passed DE may have a payload of up to 127 + /// bytes, the maximum for a V1 DE. + pub fn add_127_byte_buffer_de(&self, de: V1DE127ByteBuffer) -> AddV1DEResult { + match de.into_internal() { + Some(generic_de) => self + .add_de_internals(np_adv_dynamic::extended::BoxedWriteDataElement::new(generic_de)), + None => AddV1DEResult::InvalidDataElement, + } + } + + fn add_de_internals( + &self, + de: np_adv_dynamic::extended::BoxedWriteDataElement, + ) -> AddV1DEResult { + self.try_apply_to_internals( + move |section_builder| match section_builder.add_de(move |_| de) { + Ok(_) => AddV1DEResult::Success, + Err(_) => AddV1DEResult::InsufficientSectionSpace, + }, + AddV1DEResult::InvalidSectionHandle, + ) + } + + fn try_apply_to_internals<R>( + &self, + func: impl FnOnce( + &mut np_adv_dynamic::extended::BoxedSectionBuilder< + np_adv::extended::serialize::AdvBuilder, + CryptoProviderImpl, + >, + ) -> R, + invalid_handle_error: R, + ) -> R { + match self.adv_builder.handle.get_mut() { + Ok(mut adv_builder) => { + match adv_builder.state.as_mut() { + Some(V1AdvertisementBuilderState::Section(ref mut section_builder)) => { + // Check to make sure that the section index matches, otherwise + // we have an invalid handle. + let current_section_index = section_builder.section_index() as u8; + if current_section_index == self.section_index { + func(section_builder) + } else { + invalid_handle_error + } + } + Some(V1AdvertisementBuilderState::Advertisement(_)) => invalid_handle_error, + None => invalid_handle_error, + } + } + Err(_) => invalid_handle_error, + } + } +} + +/// Represents the contents of a V1 DE whose payload +/// is stored in a buffer which may contain up to 127 bytes, +/// which is the maximum for any V1 DE. +/// +/// This representation is stable, and so you may directly +/// reference this struct's fields if you wish. +#[repr(C)] +//TODO: Partial unification with `deserialize::v1::GenericV1DataElement`? +pub struct V1DE127ByteBuffer { + /// The DE type code of this generic data-element. + pub de_type: u32, + /// The raw data-element byte payload, up to + /// 127 bytes in length. + pub payload: ByteBuffer<127>, +} + +impl V1DE127ByteBuffer { + /// Attempts to convert this FFI-friendly DE with a byte-buffer size of 127 + /// to the internal representation of a generic DE. May fail in the case + /// where the underlying payload byte-buffer has an invalid length above 127. + fn into_internal(self) -> Option<np_adv::extended::data_elements::GenericDataElement> { + let de_type = np_adv::extended::de_type::DeType::from(self.de_type); + self.payload.as_slice().and_then(move |payload_slice| { + np_adv::extended::data_elements::GenericDataElement::try_from(de_type, payload_slice) + .ok() + }) + } +} |