1 // Copyright 2022 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use array_view::ArrayView; 16 use crypto_provider::CryptoProvider; 17 use np_hkdf::v1_salt::DataElementOffset; 18 use sink::Sink as _; 19 20 use crate::extended::{ 21 serialize::{ 22 section::encoder::SectionEncoder, AdvBuilder, CapacityLimitedVec, WriteDataElement, 23 }, 24 to_array_view, NP_ADV_MAX_SECTION_LEN, 25 }; 26 27 pub(crate) mod encoder; 28 pub(crate) mod header; 29 30 /// Accumulates data elements and encodes them into a section. 31 #[derive(Debug)] 32 pub struct SectionBuilder<R: AsMut<AdvBuilder>, SE: SectionEncoder> { 33 /// The length of the header produced by `section_encoder` 34 pub(crate) header_len: usize, 35 /// Contains the section header, the identity-specified overhead, and any DEs added 36 pub(crate) section: CapacityLimitedVec<u8, { NP_ADV_MAX_SECTION_LEN }>, 37 pub(crate) section_encoder: SE, 38 /// mut ref-able to enforce only one active section builder at a time 39 pub(crate) adv_builder: R, 40 next_de_offset: DataElementOffset, 41 } 42 43 impl<'a, SE: SectionEncoder> SectionBuilder<&'a mut AdvBuilder, SE> { 44 /// Add this builder to the advertisement that created it. add_to_advertisement<C: CryptoProvider>(self)45 pub fn add_to_advertisement<C: CryptoProvider>(self) { 46 let _ = self.add_to_advertisement_internal::<C>(); 47 } 48 } 49 50 impl<SE: SectionEncoder> SectionBuilder<AdvBuilder, SE> { 51 /// Gets the 0-based index of the section currently under construction 52 /// in the context of the containing advertisement. section_index(&self) -> usize53 pub fn section_index(&self) -> usize { 54 self.adv_builder.section_count() 55 } 56 /// Add this builder to the advertisement that created it, 57 /// and returns the containing advertisement back to the caller. add_to_advertisement<C: CryptoProvider>(self) -> AdvBuilder58 pub fn add_to_advertisement<C: CryptoProvider>(self) -> AdvBuilder { 59 self.add_to_advertisement_internal::<C>() 60 } 61 } 62 63 impl<R: AsMut<AdvBuilder>, SE: SectionEncoder> SectionBuilder<R, SE> { new( header_len: usize, section: CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>, section_encoder: SE, adv_builder: R, ) -> Self64 pub(crate) fn new( 65 header_len: usize, 66 section: CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>, 67 section_encoder: SE, 68 adv_builder: R, 69 ) -> Self { 70 Self { 71 header_len, 72 section, 73 section_encoder, 74 adv_builder, 75 next_de_offset: DataElementOffset::ZERO, 76 } 77 } 78 79 /// Add this builder to the advertisement that created it. 80 /// Returns the mut-refable to the advertisement builder 81 /// which the contents of this section builder were added to. 82 //TODO: make this fallible, if the section being added is invalid, right now it is possible to 83 // create invalid adv's that don't parse add_to_advertisement_internal<C: CryptoProvider>(mut self) -> R84 fn add_to_advertisement_internal<C: CryptoProvider>(mut self) -> R { 85 let adv_builder = self.adv_builder.as_mut(); 86 adv_builder.add_section(Self::build_section::<C>( 87 self.header_len, 88 self.section.into_inner(), 89 self.section_encoder, 90 )); 91 self.adv_builder 92 } 93 94 /// Gets the derived salt which will be employed for the next DE offset. 95 /// 96 /// Suitable for scenarios (like FFI) where a closure would be inappropriate 97 /// for DE construction, and interaction with the client is preferred. next_de_salt(&self) -> SE::DerivedSalt98 pub fn next_de_salt(&self) -> SE::DerivedSalt { 99 self.section_encoder.de_salt(self.next_de_offset) 100 } 101 102 /// Add a data element to the section with a closure that returns a `Result`. 103 /// 104 /// The provided `build_de` closure will be invoked with the derived salt for this DE. add_de_res<W: WriteDataElement, E, F: FnOnce(SE::DerivedSalt) -> Result<W, E>>( &mut self, build_de: F, ) -> Result<(), AddDataElementError<E>>105 pub fn add_de_res<W: WriteDataElement, E, F: FnOnce(SE::DerivedSalt) -> Result<W, E>>( 106 &mut self, 107 build_de: F, 108 ) -> Result<(), AddDataElementError<E>> { 109 let writer = build_de(self.next_de_salt()).map_err(AddDataElementError::BuildDeError)?; 110 111 let orig_len = self.section.len(); 112 // since we own the writer, and it's immutable, no race risk writing header w/ len then 113 // the contents as long as it's not simply an incorrect impl 114 let de_header = writer.de_header(); 115 let content_len = self 116 .section 117 .try_extend_from_slice(de_header.serialize().as_slice()) 118 .ok_or(AddDataElementError::InsufficientSectionSpace) 119 .and_then(|_| { 120 let after_header_len = self.section.len(); 121 writer 122 .write_de_contents(&mut self.section) 123 .ok_or(AddDataElementError::InsufficientSectionSpace) 124 .map(|_| self.section.len() - after_header_len) 125 }) 126 .map_err(|e| { 127 // if anything went wrong, truncate any partial writes (e.g. just the header) 128 self.section.truncate(orig_len); 129 e 130 })?; 131 132 if content_len != usize::from(de_header.len.as_u8()) { 133 // TODO eliminate this possibility by keeping a 127-byte buffer 134 // to write DEs into, then calculating the written length, so the 135 // DE impl doesn't have to do it 136 panic!( 137 "Buggy WriteDataElement impl: header len {}, actual written len {}", 138 de_header.len.as_u8(), 139 content_len 140 ); 141 } 142 143 self.next_de_offset = self.next_de_offset.incremented(); 144 145 Ok(()) 146 } 147 148 /// Add a data element to the section with a closure that returns the data element directly. 149 /// 150 /// The provided `build_de` closure will be invoked with the derived salt for this DE. add_de<W: WriteDataElement, F: FnOnce(SE::DerivedSalt) -> W>( &mut self, build_de: F, ) -> Result<(), AddDataElementError<()>>151 pub fn add_de<W: WriteDataElement, F: FnOnce(SE::DerivedSalt) -> W>( 152 &mut self, 153 build_de: F, 154 ) -> Result<(), AddDataElementError<()>> { 155 self.add_de_res(|derived_salt| Ok::<_, ()>(build_de(derived_salt))) 156 } 157 158 /// Convert a section builder's contents into an encoded section. 159 /// 160 /// `section_contents` must have size > 0. 161 /// 162 /// `header_len` is the length of the prefix of `section_contents` that has been populated 163 /// with the data returned from [SectionEncoder::header()] which does NOT include the length byte. 164 /// 165 /// Implemented without self to avoid partial-move issues. build_section<C: CryptoProvider>( header_len: usize, mut section_contents: tinyvec::ArrayVec<[u8; NP_ADV_MAX_SECTION_LEN]>, mut section_encoder: SE, ) -> EncodedSection166 pub(crate) fn build_section<C: CryptoProvider>( 167 header_len: usize, 168 mut section_contents: tinyvec::ArrayVec<[u8; NP_ADV_MAX_SECTION_LEN]>, 169 mut section_encoder: SE, 170 ) -> EncodedSection { 171 // there is space because the capacity for DEs was restricted to allow it 172 section_contents.resize(section_contents.len() + SE::SUFFIX_LEN, 0); 173 174 let (format_and_salt_and_identity_token, rest_of_contents) = 175 section_contents.split_at_mut(header_len); 176 177 let (section_length_byte, rest_of_contents) = rest_of_contents.split_at_mut(1); 178 179 let section_len = rest_of_contents.len().try_into().expect( 180 "section length will always fit into a u8 and has been validated by the section builder", 181 ); 182 // set the section length byte 183 section_length_byte[0] = section_len; 184 section_encoder.postprocess::<C>( 185 format_and_salt_and_identity_token, 186 section_len, 187 rest_of_contents, 188 ); 189 190 to_array_view(section_contents) 191 } 192 } 193 194 /// Errors for adding a DE to a section 195 #[derive(Debug, PartialEq, Eq)] 196 pub enum AddDataElementError<E> { 197 /// An error occurred when invoking the DE builder closure. 198 BuildDeError(E), 199 /// Too much data to fit into the section 200 InsufficientSectionSpace, 201 } 202 203 /// The encoded form of an advertisement section 204 pub(crate) type EncodedSection = ArrayView<u8, NP_ADV_MAX_SECTION_LEN>; 205