Skip to content

Latest commit

 

History

History
110 lines (85 loc) · 4.06 KB

CONTRIBUTING.md

File metadata and controls

110 lines (85 loc) · 4.06 KB

Contributing

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.

Dev dependencies

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

Dynamically Sized Types

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.

Helper macros

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.

Opaque Struct Pattern

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],
}