Welcome to rustls-ffi! We're excited to work with you. If you've got a big chunk of work that you'd like to do, please file an issue before starting on the code, so we can get on the same page. If you've just got a small tweak, it's fine to send a PR without filing an issue first.
After you've sent a PR, we ask that you don't rebase, squash, or force push your branch. This makes it easier to see changes as your PR evolves. Once we approve your PR, we will squash it into a single commit, with the summary line set to the summary of your PR, and the description set to the description of your PR. That way we maintain a linear git history, where each commit corresponds to a fully reviewed PR that passed tests.
In README.md, under the "Conventions" section, are described the the API conventions we follow.
All code must be rustfmt'ed, which we enforce in CI. Check .github/workflows/test.yml for the current Rust version against which we enforce rustfmt, since rustfmt's output sometimes changes between Rust versions.
If you're making changes to rustls-ffi, you'll need
cbindgen
(run cargo install cbindgen
). After you've made your changes,
regenerate the header file:
make src/rustls.h
Many types exposed in this API are wrapped in a Box
or an Arc
, which can be
straightforwardly turned into a raw pointer and shared with C using into_raw
.
However, Rust has a category of Dynamically Sized Types (DSTs), which in particular
includes trait objects (i.e. dyn Foo
). DSTs must always be wrapped in a
pointer type, e.g. Box
, Arc
, &
, &mut
, *mut
, or *const
. When a pointer
type wraps a DST it's colloquially called a "fat pointer" because it's twice the
size of a pointer to a sized type. In the case of trait objects, the extra data
is a pointer to the vtable.
Even though Rust supports raw, fat pointers, they are not FFI-safe. Consider this example:
extern "C" fn foo(_: *const dyn ToString) { }
warning: `extern` fn uses type `dyn ToString`, which is not FFI-safe
--> foo.rs:1:22
|
1 | extern "C" fn foo(_: *const dyn ToString) { }
| ^^^^^^^^^^^^^^^^^^^ not FFI-safe
|
= note: trait objects have no C equivalent
= note: `#[warn(improper_ctypes_definitions)]` on by default
That makes sense: in the C ABI, all pointers are the same size. There is no concept of a fat pointer.
Since the Rustls API includes some use of trait objects, we need a way to
represent them in the C ABI. We do that by creating two pointers: an outer,
thin pointer (usually a Box
), and an inner, fat pointer (usually an Arc
).
For instance:
Box<Arc<dyn ServerCertVerifier>>
This allows us to convert the outer pointer with into_raw()
and pass it back
and forth across the FFI boundary.
We have an API guideline that we always check input pointers for NULL
, and
return an error or default value rather than dereferencing a NULL
pointer.
To help with that, we have a number of helper macros that early-return if a
pointer is NULL
:
try_ref_from_ptr!
try_mut_from_ptr!
try_box_from_ptr!
try_clone_arc!
try_callback!
try_slice!
try_take!
These are defined in src/lib.rs. The Castable
trait determines which
C pointers can be cast to which Rust pointer types. These macros rely
on that trait to ensure correct typing of conversions.
The struct
types rustls-ffi uses are often meant to be opaque to C code, meaning that
C code should know the types exist, but not what they contain. To achieve this we rely on
the opaque struct pattern described in the Nomicon FFI guide.
For example:
/// A cipher suite supported by rustls.
pub struct rustls_supported_ciphersuite {
// Makes this type opaque to C code.
_private: [u8; 0],
}