mc_sgx_core_build/lib.rs
1// Copyright (c) 2022-2024 The MobileCoin Foundation
2
3#![doc = include_str!("../README.md")]
4
5use bindgen::callbacks::DeriveInfo;
6use bindgen::{
7 callbacks::{IntKind, ParseCallbacks},
8 Builder, EnumVariation,
9};
10use std::collections::BTreeSet;
11use std::{env, path::PathBuf};
12
13static DEFAULT_SGX_SDK_PATH: &str = "/opt/intel/sgxsdk";
14
15/// Type name prefixes that need the underscore prefix stripped from them.
16///
17/// For example `_foo_bar` should be `foo_bar`.
18///
19/// While it would be nice to add the `sgx` prefix to any non `_sgx` named type,
20/// the name does not propagate to the types used in the generated bindings.
21/// For example mapping `_foo_bar` to `sgx_foo_bar` would fail because the
22/// following function would still be looking for `foo_bar`.
23/// ```C
24/// void some_function(foo_bar arg);
25/// ```
26const STRIP_UNDERSCORE_PREFIX: &[&str] = &["_sgx", "_tee", "_quote3", "_pck", "_rsa"];
27
28/// Normalizes a type encountered by bindgen
29///
30/// Provides a default [bindgen::callbacks::ParseCallbacks::item_name]
31/// implementation that works with most SGX types.
32/// The type should come back in the form of `sgx_<main_text_from_c_interface>`
33///
34/// Returns `None` if the type is already normalized
35///
36/// # Arguments
37/// * `name` - The name of the type to determine the bindgen name of.
38pub fn normalize_item_name(name: &str) -> Option<String> {
39 let mut name = name.to_string();
40
41 // all of the exposed sgx types end in `_t`, but at times the underlying
42 // type may be missing it.
43 if !name.ends_with("_t") {
44 name.push_str("_t");
45 }
46
47 // Some types come through as `_sgx_thread_mutex_attr_t` which map to a real
48 // type of `sgx_thread_mutexattr_t`
49 let mut name = name.replace("_attr_", "attr_");
50
51 if STRIP_UNDERSCORE_PREFIX
52 .iter()
53 .any(|prefix| name.starts_with(prefix))
54 {
55 name.strip_prefix('_').map(str::to_string)
56 } else if name.starts_with('_') {
57 name.insert_str(0, "sgx");
58 Some(name)
59 } else {
60 None
61 }
62}
63
64/// Returns a builder configured with the defaults for using bindgen with the
65/// SGX libraries.
66pub fn sgx_builder() -> Builder {
67 let mut builder = Builder::default()
68 .derive_copy(false)
69 .derive_debug(false)
70 .default_enum_style(EnumVariation::NewType {
71 is_bitfield: false,
72 is_global: false,
73 })
74 .prepend_enum_name(false)
75 .use_core()
76 .ctypes_prefix("core::ffi")
77 .allowlist_recursively(false)
78 .clang_args(env_c_flags());
79
80 for include_path in vendored_include_paths() {
81 let include_path = include_path.display();
82 builder = builder.clang_arg(format!("-I{include_path}"));
83 cargo_emit::rerun_if_changed!(include_path);
84 }
85
86 builder
87}
88
89// Gets the `CFLAGS` from the environment, if any. When there are no `CLFAGS`
90// will return an empty vector.
91// The `CFLAGS` will be split on whitespace in order to allow for multiple
92// arguments. This does *not* attempt to handle escaped shell characters,
93// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
94fn env_c_flags() -> Vec<String> {
95 let env_flags = env::var("CFLAGS").ok();
96 env_flags.map_or_else(Vec::new, |flags| {
97 flags
98 .split_whitespace()
99 .map(String::from)
100 .collect::<Vec<_>>()
101 })
102}
103
104/// SGXParseCallbacks to be used with [bindgen::Builder::parse_callbacks]
105///
106/// This provides a default implementation for most of the SGX libraries
107#[derive(Debug, Default)]
108pub struct SgxParseCallbacks {
109 // types that are copyable and thus should derive `Copy`
110 //
111 // These are usually small types or packed types
112 copyable_types: Vec<String>,
113
114 // types that need to derive `Default`
115 default_types: Vec<String>,
116
117 // types that are enums
118 enum_types: Vec<String>,
119
120 // Dynamically Sized types
121 dynamically_sized_types: Vec<String>,
122
123 // types that need to derive `Serialize` and `Deserialize`
124 serialize_types: Vec<String>,
125}
126
127impl SgxParseCallbacks {
128 /// Types that are enums
129 ///
130 /// Bindgen derives some attributes by default for enums, in order to
131 /// properly handle them they must be known.
132 ///
133 /// Note: Enums will also derive `Copy`, there is no need to specify in
134 /// [`derive_copy()`](SgxParseCallbacks::derive_copy())
135 ///
136 /// # Arguments
137 /// * `enum_types` - Types that are enums.
138 pub fn enum_types<'a, E, I>(mut self, enum_types: I) -> Self
139 where
140 I: IntoIterator<Item = &'a E>,
141 E: ToString + 'a + ?Sized,
142 {
143 let enum_types = enum_types
144 .into_iter()
145 .map(ToString::to_string)
146 .collect::<Vec<_>>();
147 self.enum_types.extend(enum_types.clone());
148
149 // Enum types (from C interfaces) are small enough to always be
150 // copyable.
151 self.copyable_types.extend(enum_types);
152
153 self
154 }
155
156 /// Types to derive copy for, usually packed types
157 ///
158 /// # Arguments
159 /// * `copyable_types` - Types to derive copy for.
160 pub fn derive_copy<'a, E, I>(mut self, copyable_types: I) -> Self
161 where
162 I: IntoIterator<Item = &'a E>,
163 E: ToString + 'a + ?Sized,
164 {
165 self.copyable_types
166 .extend(copyable_types.into_iter().map(ToString::to_string));
167 self
168 }
169
170 /// Types to derive default for
171 ///
172 /// # Arguments
173 /// * `default_types` - Types to derive default for.
174 pub fn derive_default<'a, E, I>(mut self, default_types: I) -> Self
175 where
176 I: IntoIterator<Item = &'a E>,
177 E: ToString + 'a + ?Sized,
178 {
179 self.default_types
180 .extend(default_types.into_iter().map(ToString::to_string));
181 self
182 }
183
184 /// Dynamically Sized Types
185 ///
186 /// # Arguments
187 /// * `dynamically_sized_types` - Types that are dynamically sized. Due to
188 /// the dynamic size certain traits like `Eq` can't be derived.
189 pub fn dynamically_sized_types<'a, E, I>(mut self, dynamically_sized_types: I) -> Self
190 where
191 I: IntoIterator<Item = &'a E>,
192 E: ToString + 'a + ?Sized,
193 {
194 self.dynamically_sized_types
195 .extend(dynamically_sized_types.into_iter().map(ToString::to_string));
196 self
197 }
198
199 /// Types to derive serde `Serialize` and `Deserialize` for
200 pub fn serialize_types<'a, E, I>(mut self, serialize_types: I) -> Self
201 where
202 I: IntoIterator<Item = &'a E>,
203 E: ToString + 'a + ?Sized,
204 {
205 self.serialize_types
206 .extend(serialize_types.into_iter().map(ToString::to_string));
207 self
208 }
209
210 /// Get the derives that should be applied to the provided named type.
211 fn derives(&self, type_name: &str) -> Vec<String> {
212 let mut attributes = BTreeSet::new();
213
214 if self.default_types.iter().any(|n| *n == type_name) {
215 attributes.insert("Default");
216 }
217
218 // The [enum_types] method adds enums to the [copyable_types]
219 if self.copyable_types.iter().any(|n| *n == type_name) {
220 attributes.insert("Copy");
221 }
222
223 if !self.dynamically_sized_types.iter().any(|n| *n == type_name) {
224 // For dynamically sized types we don't even derive Debug, because
225 // they are often times packed and packed types can't derive Debug
226 // without deriving Copy, however by the dynamic nature one can't
227 // derive Copy
228 attributes.insert("Debug");
229 if !self.enum_types.iter().any(|n| *n == type_name) {
230 attributes.extend(["Eq", "PartialEq", "Hash", "Clone"]);
231 }
232 };
233
234 if self.serialize_types.iter().any(|n| *n == type_name) {
235 attributes.extend(["Serialize", "Deserialize"]);
236 }
237
238 attributes.into_iter().map(String::from).collect::<Vec<_>>()
239 }
240}
241
242impl ParseCallbacks for SgxParseCallbacks {
243 fn item_name(&self, name: &str) -> Option<String> {
244 normalize_item_name(name)
245 }
246
247 fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> {
248 self.derives(info.name)
249 }
250
251 fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
252 const USIZE_SUFFIXES: &[&str] = &["_SIZE", "_BYTES", "_IDX", "_COUNT"];
253 if USIZE_SUFFIXES.iter().any(|suffix| name.ends_with(suffix)) {
254 Some(IntKind::Custom {
255 name: "usize",
256 is_signed: false,
257 })
258 } else if name.starts_with("SGX_KEYSELECT_") || name.starts_with("SGX_KEYPOLICY_") {
259 Some(IntKind::U16)
260 } else {
261 None
262 }
263 }
264}
265
266/// Return the SGX SDK path, if it exists.
267fn sgx_sdk_dir() -> Option<PathBuf> {
268 env::var("SGX_SDK").ok().map(PathBuf::from)
269}
270
271/// This constant contains the manifest dir of crate-build, which will contain
272/// the headers, which allows all the headers to live in one dir, rather than
273/// scattered about the repo.
274const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");
275
276/// Return the SGX include paths
277///
278/// The paths to the vendored headers are always used.
279fn vendored_include_paths() -> Vec<PathBuf> {
280 let headers = PathBuf::from(CARGO_MANIFEST_DIR).join("headers");
281 vec![headers.join("tlibc"), headers]
282}
283
284/// Return the SGX library path.
285///
286/// Will first attempt to look at the environment variable `SGX_SDK`, if that
287/// isn't present then `/opt/intel/sgxsdk` will be used.
288pub fn sgx_library_dir() -> PathBuf {
289 // As of INTEL-SA-00615, 32-on-64bit enclaves are insecure, so we don't support
290 // them.
291 sgx_sdk_dir()
292 .unwrap_or_else(|| PathBuf::from(DEFAULT_SGX_SDK_PATH))
293 .join("lib64")
294}
295
296/// Return the SGX library path as a string
297///
298/// Calls sgx_library_dir() and converts to a string.
299pub fn sgx_library_string() -> String {
300 sgx_library_dir()
301 .to_str()
302 .expect("SGX_SDK contained invalid UTF-8 that wasn't caught by rust")
303 .to_owned()
304}
305
306/// Return the SGX binary path.
307///
308/// Will first attempt to look at the environment variable `SGX_SDK`, if that
309/// isn't present then `/opt/intel/sgxsdk` will be used.
310pub fn sgx_bin_x64_dir() -> PathBuf {
311 let mut retval = sgx_sdk_dir().unwrap_or_else(|| PathBuf::from(DEFAULT_SGX_SDK_PATH));
312 retval.push("bin");
313 retval.push("x64");
314 retval
315}
316
317/// Return the build output path.
318pub fn build_output_dir() -> PathBuf {
319 PathBuf::from(env::var("OUT_DIR").expect("Missing env.OUT_DIR"))
320}
321
322/// Return the SGX library suffix
323///
324/// Some SGX libraries have a suffix for example `sgx_trts.a` versus
325/// `sgx_trts_sim.a`. This will result the suffix based on the presence of the
326/// feature `sim`.
327pub fn sgx_library_suffix() -> &'static str {
328 // See https://doc.rust-lang.org/cargo/reference/features.html#build-scripts
329 // for description of `CARGO_FEATURE_<name>`
330 match env::var("CARGO_FEATURE_SIM") {
331 Ok(_) => "_sim",
332 _ => "",
333 }
334}
335
336#[cfg(test)]
337mod test {
338 use super::*;
339
340 #[test]
341 fn verify_consistency_of_derive_attributes() {
342 // This test prevents a regression where `HashSet` was used to prevent
343 // duplicates in the derives. Using `HashSet` resulted in the derives
344 // coming back in varying order based on seed of the default hasher.
345 // The seed of the default hasher is randomized per process run.
346 // This inconsistent ordering of the derives prevented reproducible
347 // builds as the derived implementations would move around in the
348 // resultant binary.
349 let mut callbacks = SgxParseCallbacks::default();
350 callbacks = callbacks.serialize_types(["foo"]);
351 let derives = callbacks.derives("foo");
352
353 let expected = [
354 "Clone",
355 "Debug",
356 "Deserialize",
357 "Eq",
358 "Hash",
359 "PartialEq",
360 "Serialize",
361 ]
362 .into_iter()
363 .map(String::from)
364 .collect::<Vec<_>>();
365 assert_eq!(derives, expected);
366 }
367}