1 // Copyright 2023 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 //! Common externally-accessible FFI constructs which are needed
15 //! in order to define the interfaces in this crate's various modules.
16 
17 use array_view::ArrayView;
18 use crypto_provider::{CryptoProvider, CryptoRng};
19 use crypto_provider_default::CryptoProviderImpl;
20 use handle_map::HandleNotPresentError;
21 use lock_adapter::stdlib::{RwLock, RwLockWriteGuard};
22 use lock_adapter::RwLock as _;
23 
24 const MAX_HANDLES: u32 = u32::MAX - 1;
25 
26 /// Configuration for top-level constants to be used
27 /// by the rest of the FFI which are independent of
28 /// the programming language which we ultimately
29 /// interface with at a higher level.
30 #[repr(C)]
31 pub struct CommonConfig {
32     /// The number of shards to employ in all handle-maps,
33     /// or zero if we want to use the default.
34     ///
35     /// The default number of shards will depend on whether
36     /// this crate was compiled with the `std` feature or not:
37     /// - If compiled with the `std` feature, the default number
38     ///   of shards will be set to
39     ///   `min(16, std::thread::available_parallelism().unwrap())`,
40     ///   assuming that that call completes successfully.
41     /// - In all other cases, 16 shards will be used by default.
42     num_shards: u8,
43 }
44 
45 impl Default for CommonConfig {
default() -> Self46     fn default() -> Self {
47         Self::new()
48     }
49 }
50 
51 impl CommonConfig {
new() -> Self52     pub(crate) const fn new() -> Self {
53         Self { num_shards: 0 }
54     }
55     #[cfg(feature = "std")]
num_shards(&self) -> u856     pub(crate) fn num_shards(&self) -> u8 {
57         if self.num_shards == 0 {
58             match std::thread::available_parallelism() {
59                 Ok(parallelism) => 16u8.min(parallelism),
60                 Err(_) => 16u8,
61             }
62         } else {
63             self.num_shards
64         }
65     }
66     #[cfg(not(feature = "std"))]
num_shards(&self) -> u867     pub(crate) fn num_shards(&self) -> u8 {
68         if self.num_shards == 0 {
69             16u8
70         } else {
71             self.num_shards
72         }
73     }
set_num_shards(&mut self, num_shards: u8)74     pub(crate) fn set_num_shards(&mut self, num_shards: u8) {
75         self.num_shards = num_shards
76     }
77 }
78 
default_handle_map_dimensions() -> handle_map::HandleMapDimensions79 pub(crate) fn default_handle_map_dimensions() -> handle_map::HandleMapDimensions {
80     handle_map::HandleMapDimensions {
81         num_shards: global_num_shards(),
82         max_active_handles: MAX_HANDLES,
83     }
84 }
85 
86 static COMMON_CONFIG: RwLock<CommonConfig> = RwLock::new(CommonConfig::new());
87 
global_num_shards() -> u888 fn global_num_shards() -> u8 {
89     COMMON_CONFIG.read().num_shards()
90 }
91 
92 /// Sets an override to the number of shards to employ in the NP FFI's
93 /// internal handle-maps, which places an upper bound on the number
94 /// of writing threads which may make progress at any one time
95 /// when concurrently accessing handles of the same type.
96 ///
97 /// By default, this value will be set to 16, or in `std` environments,
98 /// the minimum of 16 and the number of available hardware threads.
99 /// A shard value override of zero will be interpreted the same
100 /// as this default.
101 ///
102 /// Setting this value will have no effect if the handle-maps for the
103 /// API have already begun being used by the client code, and any
104 /// values set will take effect upon the first usage of _any_ non-`global_config_set`
105 /// API call.
global_config_set_num_shards(num_shards: u8)106 pub fn global_config_set_num_shards(num_shards: u8) {
107     let mut config = COMMON_CONFIG.write();
108     config.set_num_shards(num_shards);
109 }
110 
111 /// Holds the count of handles currently allocated for each handle type
112 #[repr(C)]
113 pub struct CurrentHandleAllocations {
114     cred_book: u32,
115     cred_slab: u32,
116     decrypted_metadata: u32,
117     v0_payload: u32,
118     legible_v1_sections: u32,
119     v0_advertisement_builder: u32,
120     v1_advertisement_builder: u32,
121 }
122 
123 /// Returns the count of currently allocated handle types being held by the rust layer. Useful
124 /// for debugging, logging, and testing.
global_config_get_current_allocation_count() -> CurrentHandleAllocations125 pub fn global_config_get_current_allocation_count() -> CurrentHandleAllocations {
126     CurrentHandleAllocations {
127         cred_book: crate::credentials::credential_book::get_current_allocation_count(),
128         cred_slab: crate::credentials::credential_slab::get_current_allocation_count(),
129         decrypted_metadata: crate::deserialize::decrypted_metadata::get_current_allocation_count(),
130         v0_payload: crate::deserialize::v0::v0_payload::get_current_allocation_count(),
131         legible_v1_sections:
132             crate::deserialize::v1::legible_v1_sections::get_current_allocation_count(),
133         v0_advertisement_builder:
134             crate::serialize::v0::advertisement_builder::get_current_allocation_count(),
135         v1_advertisement_builder:
136             crate::serialize::v1::advertisement_builder::get_current_allocation_count(),
137     }
138 }
139 
140 /// A result-type enum which tells the caller whether/not a deallocation
141 /// succeeded or failed due to the requested handle not being present.
142 #[repr(C)]
143 pub enum DeallocateResult {
144     /// The requested handle to deallocate was not present in the map
145     NotPresent = 1,
146     /// The object behind the handle was successfully deallocated
147     Success = 2,
148 }
149 
150 impl From<Result<(), HandleNotPresentError>> for DeallocateResult {
from(result: Result<(), HandleNotPresentError>) -> Self151     fn from(result: Result<(), HandleNotPresentError>) -> Self {
152         match result {
153             Ok(_) => DeallocateResult::Success,
154             Err(_) => DeallocateResult::NotPresent,
155         }
156     }
157 }
158 
159 /// Represents the raw contents of the service payload data
160 /// under the Nearby Presence service UUID
161 #[repr(C)]
162 pub struct RawAdvertisementPayload {
163     bytes: ByteBuffer<255>,
164 }
165 
166 impl RawAdvertisementPayload {
167     /// Yields a slice of the bytes in this raw advertisement payload.
168     #[allow(clippy::unwrap_used)]
as_slice(&self) -> &[u8]169     pub fn as_slice(&self) -> &[u8] {
170         // The unwrapping here will never trigger a panic,
171         // because the byte-buffer is 255 bytes, the byte-length
172         // of which is the maximum value storable in a u8.
173         self.bytes.as_slice().unwrap()
174     }
175 }
176 
177 /// A byte-string with a maximum size of N,
178 /// where only the first `len` bytes are considered
179 /// to contain the actual payload. N is only
180 /// permitted to be between 0 and 255.
181 #[derive(Clone)]
182 #[repr(C)]
183 // TODO: Once generic const exprs are stabilized,
184 // we could instead make N into a compile-time u8.
185 pub struct ByteBuffer<const N: usize> {
186     len: u8,
187     bytes: [u8; N],
188 }
189 
190 /// A FFI safe wrapper of a fixed size array
191 #[derive(Clone)]
192 #[repr(C)]
193 pub struct FixedSizeArray<const N: usize>([u8; N]);
194 
195 impl<const N: usize> FixedSizeArray<N> {
196     /// Constructs a byte-buffer from a Rust-side-derived owned array
from_array(bytes: [u8; N]) -> Self197     pub(crate) fn from_array(bytes: [u8; N]) -> Self {
198         Self(bytes)
199     }
200     /// Yields a slice of the bytes
as_slice(&self) -> &[u8]201     pub fn as_slice(&self) -> &[u8] {
202         self.0.as_slice()
203     }
204     /// De-structures this FFI-compatible fixed-size array
205     /// into a bare Rust fixed size array.
into_array(self) -> [u8; N]206     pub fn into_array(self) -> [u8; N] {
207         self.0
208     }
209 }
210 
211 impl<const N: usize> ByteBuffer<N> {
212     /// Constructs a byte-buffer from a Rust-side-derived
213     /// ArrayView, which is assumed to be trusted to be
214     /// properly initialized, and with a size-bound
215     /// under 255 bytes.
from_array_view(array_view: ArrayView<u8, N>) -> Self216     pub(crate) fn from_array_view(array_view: ArrayView<u8, N>) -> Self {
217         let (len, bytes) = array_view.into_raw_parts();
218         let len = len as u8;
219         Self { len, bytes }
220     }
221     /// Yields a slice of the first `self.len` bytes of `self.bytes`.
as_slice(&self) -> Option<&[u8]>222     pub fn as_slice(&self) -> Option<&[u8]> {
223         if self.len as usize <= N {
224             Some(&self.bytes[..(self.len as usize)])
225         } else {
226             None
227         }
228     }
229 }
230 
231 pub(crate) type CryptoRngImpl = <CryptoProviderImpl as CryptoProvider>::CryptoRng;
232 
233 pub(crate) struct LazyInitCryptoRng {
234     maybe_rng: Option<CryptoRngImpl>,
235 }
236 
237 impl LazyInitCryptoRng {
new() -> Self238     const fn new() -> Self {
239         Self { maybe_rng: None }
240     }
get_rng(&mut self) -> &mut CryptoRngImpl241     pub(crate) fn get_rng(&mut self) -> &mut CryptoRngImpl {
242         self.maybe_rng.get_or_insert_with(CryptoRngImpl::new)
243     }
244 }
245 
246 /// Shared, lazily-initialized cryptographically-secure
247 /// RNG for all operations in the FFI core.
248 static CRYPTO_RNG: RwLock<LazyInitCryptoRng> = RwLock::new(LazyInitCryptoRng::new());
249 
250 /// Gets a write guard to the (lazily-init) library-global crypto rng.
get_global_crypto_rng() -> RwLockWriteGuard<'static, LazyInitCryptoRng>251 pub(crate) fn get_global_crypto_rng() -> RwLockWriteGuard<'static, LazyInitCryptoRng> {
252     CRYPTO_RNG.write()
253 }
254 
255 /// Error returned if the bit representation of a supposedly-Rust-constructed
256 /// -and-validated type actually doesn't correspond to the format of the
257 /// data structure expected on the Rust side of the boundary, and performing
258 /// further operations on the structure would yield unintended behavior.
259 /// If this kind of error is being raised, the foreign lang code must
260 /// be messing with stack-allocated data structures for this library
261 /// in an entirely unexpected way.
262 #[derive(Debug)]
263 pub struct InvalidStackDataStructure;
264 
265 /// Error raised when attempting to cast an enum to
266 /// one of its variants, but the value is actually
267 /// of a different variant than the requested one.
268 pub struct EnumCastError {
269     pub(crate) projection_method_name: String,
270     pub(crate) variant_enum_name: String,
271     pub(crate) variant_type_name: String,
272 }
273 
274 impl core::fmt::Debug for EnumCastError {
fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result275     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
276         write!(
277             f,
278             "Attempted to cast a non-{} to a {} via {}",
279             &self.variant_enum_name, &self.variant_type_name, &self.projection_method_name
280         )
281     }
282 }
283