Skip to content

Commit a827ee3

Browse files
committed
feat: generate a usable include directory for mbedtls
The include directory currently requires compiler flags to work correctly. Without `MBEDTLS_CONFIG_FILE` defined, headers use MbedTLS's default configuration, causing link failures or even runtime crashes. The hooks used by the sys crate also end up passing important config over -D flags. We now dynamically generate the MbedTLS configuration header in the build script instead of passing options via -D flags. This makes the include directory usable without requiring any compiler flags. The process happens in two stages: 1. Compile using a "staging include directory" with the generated configuration and headers for hooks. 2. Merge the staging directory into the include directory, replacing the default configuration while keeping the hook headers accessible. With this, bindgen can run without any custom compiler flags. This enables publicly exposing the include directory for other crates. See: <#109>
1 parent fe6c3ed commit a827ee3

File tree

11 files changed

+465
-4466
lines changed

11 files changed

+465
-4466
lines changed

mbedtls-rs-sys/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,4 @@ enumset = "1"
6464
env_logger = "0.11"
6565
fs_extra = "1.3"
6666
log = "0.4"
67+
regex = "1"

mbedtls-rs-sys/build.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ fn main() -> Result<()> {
7474
force_esp_riscv_gcc,
7575
);
7676

77-
let libs_dir = builder.compile(&out, None)?;
78-
let bindings = builder.generate_bindings(&out, None)?;
77+
let artifacts = builder.compile(&out, None)?;
78+
let bindings = builder.generate_bindings(&out, &artifacts.include, None)?;
7979

80-
Some((bindings, libs_dir))
80+
Some((bindings, artifacts.libraries))
8181
};
8282

8383
if let Some((bindings, libs_dir)) = dirs {

mbedtls-rs-sys/gen/builder.rs

Lines changed: 224 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use std::path::{Path, PathBuf};
22
use std::process::Command;
33

4+
use self::config::MbedtlsConfig;
45
use anyhow::{anyhow, Result};
56
use bindgen::Builder;
67
use cmake::Config;
78
use enumset::{EnumSet, EnumSetType};
89

10+
mod config;
11+
912
/// What hooks to install in MbedTLS
1013
#[derive(EnumSetType, Debug)]
1114
pub enum Hook {
@@ -19,6 +22,61 @@ pub enum Hook {
1922
ExpMod,
2023
}
2124

25+
impl Hook {
26+
const fn work_area_size(self) -> Option<usize> {
27+
match self {
28+
Self::Sha1 => Some(208),
29+
Self::Sha256 => Some(208),
30+
Self::Sha512 => Some(304),
31+
Self::ExpMod => None,
32+
}
33+
}
34+
35+
/// Returns the header name that MbedTLS expects for this hook, if any.
36+
///
37+
/// These header names are hard-coded in MbedTLS.
38+
/// We cannot change them to make them less ambiguous.
39+
const fn header_name(self) -> Option<&'static str> {
40+
match self {
41+
Self::Sha1 => Some("sha1_alt.h"),
42+
Self::Sha256 => Some("sha256_alt.h"),
43+
Self::Sha512 => Some("sha512_alt.h"),
44+
Self::ExpMod => None,
45+
}
46+
}
47+
48+
/// Returns the config identifier corresponding to this hook.
49+
const fn config_ident(self) -> &'static str {
50+
match self {
51+
Self::Sha1 => "SHA1_ALT",
52+
Self::Sha256 => "SHA256_ALT",
53+
Self::Sha512 => "SHA512_ALT",
54+
Self::ExpMod => "MPI_EXP_MOD_ALT_FALLBACK",
55+
}
56+
}
57+
58+
fn apply_to_config(self, config: &mut MbedtlsConfig) {
59+
config.set(self.config_ident(), true);
60+
if let Some(work_area_size) = self.work_area_size() {
61+
// This is not relevant for MbedTLS itself, but our
62+
// implementation needs to know the work area size.
63+
let size_ident = format!("{}_WORK_AREA_SIZE", self.config_ident());
64+
config.add(&size_ident, work_area_size.to_string());
65+
}
66+
}
67+
}
68+
69+
/// Compilation artifacts.
70+
///
71+
/// Returned by [`MbedtlsBuilder::compile`].
72+
pub struct MbedtlsArtifacts {
73+
/// Include directory containing the MbedTLS headers.
74+
pub include: PathBuf,
75+
/// Directory containing the compiled MbedTLS libraries to link against.
76+
#[allow(unused, reason = "xtask doesn't use this")]
77+
pub libraries: PathBuf,
78+
}
79+
2280
/// The MbedTLS builder
2381
pub struct MbedtlsBuilder {
2482
hooks: EnumSet<Hook>,
@@ -79,9 +137,14 @@ impl MbedtlsBuilder {
79137
///
80138
/// Arguments:
81139
/// - `out_path`: Path to write the bindings to
140+
/// - `include_dir`: Path to the directory containing the MbedTLS headers
141+
/// to generate bindings for.
142+
/// - `copy_file_path`: Optional path to copy the generated bindings to
143+
/// (e.g. for caching or pre-generation purposes)
82144
pub fn generate_bindings(
83145
&self,
84146
out_path: &Path,
147+
include_dir: &Path,
85148
copy_file_path: Option<&Path>,
86149
) -> Result<PathBuf> {
87150
log::info!("Generating MbedTLS bindings");
@@ -128,16 +191,7 @@ impl MbedtlsBuilder {
128191
.join("include.h")
129192
.to_string_lossy(),
130193
)
131-
.clang_args([
132-
&format!(
133-
"-I{}",
134-
canon(&self.crate_root_path.join("mbedtls").join("include"))
135-
),
136-
&format!(
137-
"-I{}",
138-
canon(&self.crate_root_path.join("gen").join("include"))
139-
),
140-
]);
194+
.clang_arg(format!("-I{}", canon(include_dir)));
141195

142196
if self.short_enums() {
143197
builder = builder.clang_arg("-fshort-enums");
@@ -158,16 +212,6 @@ impl MbedtlsBuilder {
158212
builder = builder.clang_arg(format!("--target={target}"));
159213
}
160214

161-
for hook in self.hooks {
162-
let def = self.hook_def(hook);
163-
164-
builder = builder.clang_arg(format!("-D{def}"));
165-
166-
if let Some(size_def) = self.hook_work_area_size_def(hook) {
167-
builder = builder.clang_arg(format!("-D{def}_WORK_AREA_SIZE={size_def}"));
168-
}
169-
}
170-
171215
let bindings = builder
172216
.generate()
173217
.map_err(|_| anyhow!("Failed to generate bindings"))?;
@@ -194,13 +238,86 @@ impl MbedtlsBuilder {
194238
Ok(bindings_file)
195239
}
196240

197-
/// Compile mbedtls
241+
/// Generates the MbedTLS config header and writes it to the specified path.
242+
pub fn generate_config(&self, out_file: &Path) -> Result<()> {
243+
let config_source_path = self
244+
.cmake_configurer
245+
.project_path
246+
.join("include/mbedtls/mbedtls_config.h");
247+
let mut config = MbedtlsConfig::load_from_header(&config_source_path)?;
248+
// HACK: For some reason the 'MPI_EXP_MOD_ALT_FALLBACK' config option
249+
// is missing in the `mbedtls_config.h` file, but it's used in
250+
// `bignum.c`.
251+
config.add("MPI_EXP_MOD_ALT_FALLBACK", false);
252+
253+
config
254+
.set("DEPRECATED_REMOVED", true)
255+
.set("HAVE_TIME", false)
256+
.set("HAVE_TIME_DATE", false)
257+
.set("PLATFORM_MEMORY", true)
258+
// We want to provide our own Rust-backed zeroization function.
259+
.set("PLATFORM_ZEROIZE_ALT", true)
260+
.set("AES_ROM_TABLES", true)
261+
.set("PK_PARSE_EC_COMPRESSED", false)
262+
.set("GENPRIME", false)
263+
.set("FS_IO", false)
264+
.set("NO_PLATFORM_ENTROPY", true)
265+
.set("PSA_CRYPTO_EXTERNAL_RNG", true)
266+
.set("PSA_KEY_STORE_DYNAMIC", false)
267+
.set("SSL_KEYING_MATERIAL_EXPORT", false)
268+
.set("AESNI_C", false)
269+
.set("AESCE_C", false)
270+
.set("NET_C", false)
271+
.set("PSA_CRYPTO_STORAGE_C", false)
272+
.set("PSA_ITS_FILE_C", false)
273+
.set("SHA3_C", false)
274+
.set("TIMING_C", false);
275+
276+
self.hooks
277+
.iter()
278+
.for_each(|hook| hook.apply_to_config(&mut config));
279+
280+
config.write_to_path(out_file)?;
281+
Ok(())
282+
}
283+
284+
/// Compile MbedTLS.
285+
///
286+
/// Uses CMake to compile MbedTLS and prepares the headers for consumption
287+
/// by bindgen and other crates.
288+
/// The tricky part is the MbedTLS configuration. In a CMake-based world,
289+
/// MbedTLS uses "public" compile definitions to bubble up the external
290+
/// config file as well as other relevant configuration options.
291+
/// This simply doesn't work well when using Cargo. To make everyone's life
292+
/// easier, we manually patch up the headers after compiling MbedTLS such
293+
/// that no other compile definitions are necessary when consuming the
294+
/// generated libraries and headers.
198295
///
199296
/// Arguments:
200297
/// - `out_path`: Path to write the compiled libraries to
201-
pub fn compile(&self, out_path: &Path, copy_path: Option<&Path>) -> Result<PathBuf> {
298+
pub fn compile(&self, out_path: &Path, copy_path: Option<&Path>) -> Result<MbedtlsArtifacts> {
299+
// This directory is temporarily added to the include path when
300+
// compiling MbedTLS. Afterwards, we merge it into MbedTLS's include
301+
// directory for consumption by downstream crates.
302+
let mut staging_include_dir =
303+
StagingIncludeDirectory::new(out_path.join("staging").join("include"))?;
304+
305+
let config_file_path = staging_include_dir.path.join("mbedtls_rs_sys_config.h");
306+
self.generate_config(&config_file_path)?;
307+
// We want our generated config file to fully replace MbedTLS's default
308+
// config file.
309+
staging_include_dir.track(&config_file_path, "mbedtls/mbedtls_config.h");
310+
311+
// Copy all the relevant hook headers to the staging include directory.
312+
let hook_header_dir = self.crate_root_path.join("gen").join("hook");
313+
self.hooks
314+
.iter()
315+
.filter_map(|hook| hook.header_name())
316+
.try_for_each(|name| staging_include_dir.add(hook_header_dir.join(name)))?;
317+
202318
let target_dir = out_path.join("mbedtls").join("build");
203319
std::fs::create_dir_all(&target_dir)?;
320+
let target_include_dir = target_dir.join("include");
204321

205322
let target_lib_dir = out_path.join("mbedtls").join("lib");
206323

@@ -220,37 +337,21 @@ impl MbedtlsBuilder {
220337
.define("CMAKE_EXPORT_COMPILE_COMMANDS", "ON")
221338
// Clang will complain about some documentation formatting in mbedtls
222339
.define("MBEDTLS_FATAL_WARNINGS", "OFF")
223-
.define(
224-
"MBEDTLS_CONFIG_FILE",
225-
self.crate_root_path
226-
.join("gen")
227-
.join("include")
228-
.join("config.h"),
229-
)
230-
.cflag(format!(
231-
"-I{}",
232-
self.crate_root_path.join("gen").join("include").display()
233-
))
234-
.cflag("-DMBEDTLS_CONFIG_FILE='<config.h>'")
235-
.cxxflag("-DMBEDTLS_CONFIG_FILE='<config.h>'")
340+
.define("MBEDTLS_CONFIG_FILE", config_file_path)
341+
.cflag(format!("-I{}", staging_include_dir.path.display()))
236342
.profile("Release")
237343
.out_dir(&target_dir);
238344

239-
for hook in self.hooks {
240-
let def = self.hook_def(hook);
241-
242-
config.cflag(format!("-D{def}")).cxxflag(format!("-D{def}"));
243-
244-
if let Some(size_def) = self.hook_work_area_size_def(hook) {
245-
config
246-
.cflag(format!("-D{def}_WORK_AREA_SIZE={size_def}"))
247-
.cxxflag(format!("-D{def}_WORK_AREA_SIZE={size_def}"));
248-
}
249-
}
250-
251345
config.build();
252346

253-
Ok(lib_dir.to_path_buf())
347+
// Now that MbedTLS is compiled, we merge the staging include
348+
// directory into its include directory.
349+
staging_include_dir.merge_into(&target_include_dir)?;
350+
351+
Ok(MbedtlsArtifacts {
352+
include: target_include_dir,
353+
libraries: lib_dir.to_path_buf(),
354+
})
254355
}
255356

256357
/// Re-run the build script if the file or directory has changed.
@@ -259,24 +360,6 @@ impl MbedtlsBuilder {
259360
println!("cargo:rerun-if-changed={}", file_or_dir.display())
260361
}
261362

262-
fn hook_def(&self, hook: Hook) -> &'static str {
263-
match hook {
264-
Hook::Sha1 => "MBEDTLS_SHA1_ALT",
265-
Hook::Sha256 => "MBEDTLS_SHA256_ALT",
266-
Hook::Sha512 => "MBEDTLS_SHA512_ALT",
267-
Hook::ExpMod => "MBEDTLS_MPI_EXP_MOD_ALT_FALLBACK",
268-
}
269-
}
270-
271-
fn hook_work_area_size_def(&self, hook: Hook) -> Option<usize> {
272-
match hook {
273-
Hook::Sha1 => Some(208),
274-
Hook::Sha256 => Some(208),
275-
Hook::Sha512 => Some(304),
276-
_ => None,
277-
}
278-
}
279-
280363
/// A heuristics (we don't have anything better) to signal to `bindgen` whether the GCC toolchain
281364
/// for the target emits short enums or not.
282365
///
@@ -289,6 +372,81 @@ impl MbedtlsBuilder {
289372
}
290373
}
291374

375+
/// Helper for managing the staging include directory used when compiling MbedTLS.
376+
struct StagingIncludeDirectory {
377+
/// Path to the staging include directory.
378+
path: PathBuf,
379+
// List of (source, destination) pairs of files to copy to the staging
380+
// include directory.
381+
// The source is always relative to `path` and the destination is relative
382+
// to the final include directory path.
383+
files: Vec<(PathBuf, PathBuf)>,
384+
}
385+
386+
impl StagingIncludeDirectory {
387+
fn new(path: PathBuf) -> Result<Self> {
388+
std::fs::create_dir_all(&path)?;
389+
Ok(Self {
390+
path,
391+
files: Vec::new(),
392+
})
393+
}
394+
395+
/// Merges the staging include directory into the final include directory.
396+
///
397+
/// This should be called after MbedTLS is compiled, and the final include
398+
/// directory is ready to be consumed by downstream crates.
399+
fn merge_into(&self, include_dir: &Path) -> Result<()> {
400+
for (rel_src, rel_dest) in &self.files {
401+
let dest = include_dir.join(rel_dest);
402+
if let Some(parent) = dest.parent() {
403+
std::fs::create_dir_all(parent)?;
404+
}
405+
let src = self.path.join(rel_src);
406+
std::fs::copy(src, dest)?;
407+
}
408+
Ok(())
409+
}
410+
411+
/// Adds an external file to be tracked and copied.
412+
///
413+
/// The file will be copied into the staging include directory under the
414+
/// same name. When merged, the file will be copied to the final include
415+
/// directory, again under the same name.
416+
///
417+
/// # Panics
418+
///
419+
/// If `source` is not a path containing a file name.
420+
fn add(&mut self, source: impl AsRef<Path>) -> Result<()> {
421+
let source = source.as_ref();
422+
let source_file_name = source.file_name().expect("'source' must have a file name");
423+
let staging_path = self.path.join(source_file_name);
424+
std::fs::copy(source, &staging_path)?;
425+
self.track(staging_path, source_file_name);
426+
Ok(())
427+
}
428+
429+
/// Tracks a file to be copied from the staging directory into the final
430+
/// include directory.
431+
///
432+
/// # Panics
433+
///
434+
/// If `source` is not within the staging include directory, or if
435+
/// `destination` is not a relative path.
436+
fn track(&mut self, source: impl AsRef<Path>, destination: impl AsRef<Path>) {
437+
let source = source
438+
.as_ref()
439+
.strip_prefix(&self.path)
440+
.expect("'source' must be within the staging include directory");
441+
let destination = destination.as_ref();
442+
if destination.is_absolute() {
443+
panic!("'destination' must be a relative path");
444+
}
445+
self.files
446+
.push((source.to_path_buf(), destination.to_path_buf()));
447+
}
448+
}
449+
292450
// TODO: Move to `embuild`
293451
#[derive(Debug, Clone)]
294452
pub struct CMakeConfigurer {

0 commit comments

Comments
 (0)