blob: 5111affa28bc0ec07deb7f5e658fbb9a28d543d8 [file] [log] [blame]
// 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 C FFI
pub mod credentials;
pub mod deserialize;
pub mod serialize;
pub mod v0;
pub mod v1;
use lock_adapter::stdlib::RwLock;
use lock_adapter::RwLock as _;
use np_ffi_core::common::{CurrentHandleAllocations, InvalidStackDataStructure};
/// Structure for categorized reasons for why a NP C FFI call may
/// be panicking.
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum PanicReason {
/// Some enum cast to a variant failed. Utilized
/// for failed enum casts of all enums.
///
/// (That is, this is the catch-all panic reason for enum
/// casts where there is not a more specific reason
/// in some other variant of this enum.)
EnumCastFailed = 0,
/// The panic handler is used to assert conditions are true to avoid programmer errors.
/// If a failed assert condition is hit, this panic handler is invoked with this reason.
AssertFailed = 1,
/// Error returned if the bit representation of a supposedly-Rust-constructed
/// -and-validated type actually doesn't correspond to the format of the
/// data structure expected on the Rust side of the boundary, and performing
/// further operations on the structure would yield unintended behavior.
/// If this kind of error is being raised, the C code must
/// be messing with stack-allocated data structures for this library
/// in an entirely unexpected way.
InvalidStackDataStructure = 2,
/// The maximum amount of allocations per type is `u32::MAX`, this panic handler is invoked
/// with this reason when this is exceeded. Clients should never need more than 4 Billions
/// handles and would certainly run into other issues before reaching that point
ExceededMaxHandleAllocations = 3,
}
/// Structure which maintains information about the panic-handler
/// for panicking calls in the NP C FFI.
struct PanicHandler {
/// Optional function-pointer to client-specified panic behavior.
handler: Option<unsafe extern "C" fn(PanicReason) -> ()>,
/// Fuse to prevent setting the panic-handler more than once.
/// We do not use the presence/absence of `self.handler` for this,
/// since it's possible for the client to explicitly pass "NULL"
/// to set the panic-handler to the platform-default (and ensure
/// that the panic-handler never changes from the platform-default.)
handler_set_by_client: bool,
}
impl PanicHandler {
pub(crate) const fn new() -> Self {
Self { handler: None, handler_set_by_client: false }
}
pub(crate) fn set_handler(
&mut self,
handler: Option<unsafe extern "C" fn(PanicReason) -> ()>,
) -> bool {
// Only allow setting the panic handler once
if !self.handler_set_by_client {
self.handler = handler;
self.handler_set_by_client = true;
true
} else {
false
}
}
pub(crate) fn panic(&self, panic_reason: PanicReason) -> ! {
if let Some(handler) = self.handler {
unsafe { handler(panic_reason) }
}
Self::system_handler(panic_reason)
}
fn system_handler(panic_reason: PanicReason) -> ! {
std::eprintln!("NP FFI Panicked: {:?}", panic_reason);
let backtrace = std::backtrace::Backtrace::capture();
std::eprintln!("Stack trace: {}", backtrace);
std::process::abort()
}
}
static PANIC_HANDLER: RwLock<PanicHandler> = RwLock::new(PanicHandler::new());
pub(crate) fn panic(reason: PanicReason) -> ! {
PANIC_HANDLER.read().panic(reason)
}
pub(crate) fn panic_if_invalid<T>(value: Result<T, InvalidStackDataStructure>) -> T {
match value {
Ok(x) => x,
Err(_) => panic(PanicReason::InvalidStackDataStructure),
}
}
pub(crate) fn unwrap<T>(value: Option<T>, panic_reason: PanicReason) -> T {
match value {
Some(x) => x,
None => panic(panic_reason),
}
}
/// Overrides the global panic handler to be used when NP C FFI calls panic.
/// This method will only have an effect on the global panic-handler
/// the first time it's called, and this method will return `true`
/// to indicate that the panic handler was successfully set.
/// All subsequent calls to this method
/// will simply ignore the argument and return `false`.
///
/// If the passed function pointer is non-null,
/// then we will call it upon every panic,
/// followed by the default panicking behavior for
/// the platform (in the case where the user-specified
/// function does not terminate or hang the running process.)
///
/// Otherwise, we will resort to the
/// default panicking behavior for the system, which
/// is a printed stack trace followed by an abort
/// when this crate is compiled with `std`,
/// but a bare `loop { }` when this crate is compiled without.
#[no_mangle]
pub extern "C" fn np_ffi_global_config_panic_handler(
handler: Option<unsafe extern "C" fn(PanicReason) -> ()>,
) -> bool {
let mut panic_handler = PANIC_HANDLER.write();
panic_handler.set_handler(handler)
}
/// Checks the current count of all outstanding handle allocations, useful for debugging,
/// logging, and testing
#[no_mangle]
pub extern "C" fn np_ffi_global_config_get_current_allocation_count() -> CurrentHandleAllocations {
np_ffi_core::common::global_config_get_current_allocation_count()
}
/// Sets an override to the number of shards to employ in the NP FFI's
/// internal handle-maps, which places an upper bound on the number
/// of writing threads which may make progress at any one time
/// when concurrently accessing handles of the same type.
///
/// By default, this value will be set to 16, or in `std` environments,
/// the minimum of 16 and the number of available hardware threads.
/// A shard value override of zero will be interpreted the same
/// as this default.
///
/// Setting this value will have no effect if the handle-maps for the
/// API have already begun being used by the client code, and any
/// values set will take effect upon the first usage of _any_ non-`np_ffi_global_config_set`
/// API call.
#[no_mangle]
pub extern "C" fn np_ffi_global_config_set_num_shards(num_shards: u8) {
np_ffi_core::common::global_config_set_num_shards(num_shards)
}