From 126a8a6457082342da11d0c7ef1c9a0bf02487f3 Mon Sep 17 00:00:00 2001 From: Jesper Brynolf Date: Sat, 7 Oct 2023 09:59:34 +0200 Subject: [PATCH] WORK IN PROGRESS Signed-off-by: Jesper Brynolf --- tss-esapi-sys/Cargo.toml | 2 + tss-esapi-sys/build.rs | 347 ++++++++++++++++++++++++++++----------- 2 files changed, 256 insertions(+), 93 deletions(-) diff --git a/tss-esapi-sys/Cargo.toml b/tss-esapi-sys/Cargo.toml index a421315e..0e8615fc 100644 --- a/tss-esapi-sys/Cargo.toml +++ b/tss-esapi-sys/Cargo.toml @@ -17,6 +17,8 @@ rust-version = "1.66.0" bindgen = { version = "0.66.1", optional = true } pkg-config = "0.3.18" target-lexicon = "0.12.0" +cfg-if = "1.0.0" +semver = "1.0.7" [features] generate-bindings = ["bindgen"] diff --git a/tss-esapi-sys/build.rs b/tss-esapi-sys/build.rs index 379eba01..94413963 100644 --- a/tss-esapi-sys/build.rs +++ b/tss-esapi-sys/build.rs @@ -1,10 +1,7 @@ // Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "generate-bindings")] -use std::path::PathBuf; - -const MINIMUM_VERSION: &str = "3.2.2"; +//#[cfg(feature = "generate-bindings")] fn main() { if std::env::var("DOCS_RS").is_ok() { @@ -12,103 +9,267 @@ fn main() { return; } - #[cfg(feature = "generate-bindings")] - { - let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); - let esys_path = out_path.join("tss_esapi_bindings.rs"); - generate_from_system(esys_path); + cfg_if::cfg_if! { + if #[cfg(feature = "generate-bindings")] { + let installation = tpm2_tss::Installation::probe(true); + } else { + let installation = tpm2_tss::Installation::probe(false); + } } +} - #[cfg(not(feature = "generate-bindings"))] - { - use std::str::FromStr; - use target_lexicon::{Architecture, OperatingSystem, Triple}; +pub mod target { + use std::str::FromStr; + use target_lexicon::{Architecture, OperatingSystem, Triple}; + const TARGET_ENV_VAR_NAME: &str = "TARGET"; - let target = Triple::from_str(&std::env::var("TARGET").unwrap()) - .expect("Failed to parse target triple"); + /// Ensures that the `TARGET` is valid for cross compilation. + pub fn ensure_supported() { + let target = Triple::from_str( + &std::env::var(TARGET_ENV_VAR_NAME) + .unwrap_or_else(|_| { + panic!("Missing environment variable `{}`.", TARGET_ENV_VAR_NAME); + })) + .expect("Failed to parse target triple."); match (target.architecture, target.operating_system) { - (Architecture::Arm(_), OperatingSystem::Linux) => {} - (Architecture::Aarch64(_), OperatingSystem::Linux) => {} - (Architecture::X86_64, OperatingSystem::Darwin) => {} - (Architecture::X86_64, OperatingSystem::Linux) => {} + (Architecture::Arm(_), OperatingSystem::Linux) | + (Architecture::Aarch64(_), OperatingSystem::Linux) | + (Architecture::X86_64, OperatingSystem::Darwin) | + (Architecture::X86_64, OperatingSystem::Linux) | + (Architecture::X86_64, OperatingSystem::Windows) => {} (arch, os) => { - panic!("Compilation target (architecture, OS) tuple ({}, {}) is not part of the supported tuples. Please compile with the \"generate-bindings\" feature or add support for your platform :)", arch, os); + panic!( + "Compilation target (architecture, OS) tuple ({}, {}) is not part of the supported tuples. Please compile with the \"generate-bindings\" feature or add support for your platform.", + arch, + os); } } - - pkg_config::Config::new() - .atleast_version(MINIMUM_VERSION) - .probe("tss2-sys") - .expect("Failed to find tss2-sys library."); - let tss2_esys = pkg_config::Config::new() - .atleast_version(MINIMUM_VERSION) - .probe("tss2-esys") - .expect("Failed to find tss2-esys library."); - pkg_config::Config::new() - .atleast_version(MINIMUM_VERSION) - .probe("tss2-tctildr") - .expect("Failed to find tss2-tctildr library."); - pkg_config::Config::new() - .atleast_version(MINIMUM_VERSION) - .probe("tss2-mu") - .expect("Failed to find tss2-mu library."); - - println!("cargo:version={}", tss2_esys.version); } } -#[cfg(feature = "generate-bindings")] -pub fn generate_from_system(esapi_out: PathBuf) { - pkg_config::Config::new() - .atleast_version(MINIMUM_VERSION) - .probe("tss2-sys") - .expect("Failed to find tss2-sys library."); - let tss2_esys = pkg_config::Config::new() - .atleast_version(MINIMUM_VERSION) - .probe("tss2-esys") - .expect("Failed to find tss2-esys"); - let tss2_tctildr = pkg_config::Config::new() - .atleast_version(MINIMUM_VERSION) - .probe("tss2-tctildr") - .expect("Failed to find tss2-tctildr"); - let tss2_mu = pkg_config::Config::new() - .atleast_version(MINIMUM_VERSION) - .probe("tss2-mu") - .expect("Failed to find tss2-mu"); - - println!("cargo:version={}", tss2_esys.version); - - // These three pkg-config files should contain only one include/lib path. - let tss2_esys_include_path = tss2_esys.include_paths[0] - .clone() - .into_os_string() - .into_string() - .expect("Error converting OsString to String."); - let tss2_tctildr_include_path = tss2_tctildr.include_paths[0] - .clone() - .into_os_string() - .into_string() - .expect("Error converting OsString to String."); - let tss2_mu_include_path = tss2_mu.include_paths[0] - .clone() - .into_os_string() - .into_string() - .expect("Error converting OsString to String."); - - bindgen::Builder::default() - .size_t_is_usize(false) - .clang_arg(format!("-I{}/tss2/", tss2_esys_include_path)) - .clang_arg(format!("-I{}/tss2/", tss2_tctildr_include_path)) - .clang_arg(format!("-I{}/tss2/", tss2_mu_include_path)) - .header(format!("{}/tss2/tss2_esys.h", tss2_esys_include_path)) - .header(format!("{}/tss2/tss2_tctildr.h", tss2_tctildr_include_path)) - .header(format!("{}/tss2/tss2_mu.h", tss2_mu_include_path)) - // See this issue: https://github.com/parallaxsecond/rust-cryptoki/issues/12 - .blocklist_type("max_align_t") - .generate_comments(false) - .derive_default(true) - .generate() - .expect("Unable to generate bindings to TSS2 ESYS APIs.") - .write_to_file(esapi_out) - .expect("Couldn't write ESYS bindings!"); +pub mod tpm2_tss { + use std::path::{PathBuf, Path}; + use semver::{Version, VersionReq}; + const MINIMUM_VERSION: &str = "3.2.2"; + const PATH_ENV_VAR_NAME: &str = "TPM2_TSS_PATH"; + + /// The installed tpm2-tss libraries that are of + /// interest. + pub struct Installation { + tss2_sys: Library, + tss2_esys: Library, + tss2_tctildr: Library, + tss2_mu: Library, + tss2_tcti_tbs: Option, + } + + impl Installation { + /// Probes the system for an installation. + pub fn probe(with_headers: bool) -> Self { + let install_path = Installation::installation_path_from_env_var(); + Installation { + tss2_sys: Library::probe_required("tss2-sys", install_path.as_ref()), + tss2_esys: Library::probe_required("tss2-esys", install_path.as_ref()), + tss2_tctildr: Library::probe_required("tss2-tctildr", install_path.as_ref()), + tss2_mu: Library::probe_required("tss2-mu", install_path.as_ref()), + tss2_tcti_tbs: Library::probe_optional("tss2-tcti-tbs", install_path.as_ref()), + } + } + + /// Generates bindings for the Installation. + pub fn generate_bindings(&self, esapi_out: &Path) { + self.bindgen_builder() + .generate() + .expect("Unable to generate bindings to TSS2 ESYS APIs.") + .write_to_file(esapi_out) + .expect("Couldn't write ESYS bindings!"); + } + + /// The bindgen builder to use. + fn bindgen_builder(&self) -> bindgen::Builder { + let mut builder = bindgen::Builder::default() + .size_t_is_usize(false) + .clang_arg(self.tss2_esys.include_dir_arg()) + .clang_arg(self.tss2_tctildr.include_dir_arg()) + .clang_arg(self.tss2_mu.include_dir_arg()) + .rustfmt_bindings(true) + .header(self.tss2_esys.header_file_arg()) + .header(self.tss2_tctildr.header_file_arg()) + .header(self.tss2_mu.header_file_arg()) + //See this issue: https://github.com/parallaxsecond/rust-cryptoki/issues/12 + .blocklist_type("max_align_t") + .generate_comments(false) + .derive_default(true); + if let Some(tss2_tcti_tbs) = &self.tss2_tcti_tbs { + builder = builder + .clang_arg(tss2_tcti_tbs.include_dir_arg()) + .header(tss2_tcti_tbs.header_file_arg()); + } + builder + } + + /// Retrieves the installation path from the environment variable and validates it. + fn installation_path_from_env_var() -> Option<(PathBuf, String)> { + std::env::var(PATH_ENV_VAR_NAME).map_or_else(|e| { + match e { + std::env::VarError::NotUnicode(invalid_value) => { + panic!("Invalid `{}` env var: `{:?}`.", PATH_ENV_VAR_NAME, invalid_value); + }, + std::env::VarError::NotPresent => None, + } + }, |var| { + Some(Installation::ensure_valid_installation_path(var)) + }) + } + + /// Ensures that the installation path is valid. + /// + /// # Details + /// In order to be considered valid the following + /// requirements needs to be full filled: + /// 1. The directory must exist. + /// 2. Sub directories `include` and `lib` must exist. + /// 3. A `VERSION` file must be present in the directory and it needs to be + /// be specifying a version that is greater then the minimum supported version. + /// + /// # Arguments + /// env_var - The value of the environment variable that contains the installation path. + /// + /// # Returns + /// A tuple containing the validated installation path and the version associated with it. + fn ensure_valid_installation_path(env_var: String) -> (PathBuf, String) { + let install_path = PathBuf::from(env_var); + if !install_path.is_dir() { + panic!("`{}` specifies a path `{}`, that does not exist", PATH_ENV_VAR_NAME, install_path.to_string_lossy()); + } + if !install_path.join("include").is_dir() { + panic!("`{}` specifies a path `{}`, that does not contain an `include` directory", PATH_ENV_VAR_NAME, install_path.to_string_lossy()); + } + if !install_path.join("lib").is_dir() { + panic!("`{}` specifies a path `{}`, that does not contain an `lib` directory", PATH_ENV_VAR_NAME, install_path.to_string_lossy()); + } + let version_str = std::fs::read_to_string(install_path.join("VERSION")) + .expect("Failed to read VERSION file."); + let version = Version::parse(version_str.as_str()) + .expect("Failed to parse the content of the VERSION file."); + let min_version_req_str = format!(">={}",MINIMUM_VERSION); + let min_version_req = VersionReq::parse(&min_version_req_str) + .expect("Failed to parse minimum version requirement."); + if !min_version_req.matches(&version) { + panic!("Minimum supported version is {}, found installed version {}", MINIMUM_VERSION, version_str); + } + (install_path, version_str) + } + } + + + /// Struct holding the information for a library. + struct Library { + header_file: PathBuf, + version: String, + name: String, + } + + impl Library { + /// Probes the different options for a required library. + /// + /// # Arguments + /// lib_name - The name of the library. + /// install_path - Optional path and version of installation. + /// + /// # Returns + /// The detected installed library. + /// # Panics + /// - If the library is not found. + pub fn probe_required(lib_name: &str, install_path: Option<&(PathBuf, String)>) -> Self { + Self::probe_optional(lib_name, install_path) + .unwrap_or_else(|| panic!("Failed to find {} library.", lib_name)) + } + + /// Probes the different options for an optional library. + /// + /// # Arguments + /// lib_name - The name of the library. + /// install_path - Optional path and version of installation. + /// + /// # Returns + /// The detected installed library or None if no library was found. + pub fn probe_optional(lib_name: &str, install_path: Option<&(PathBuf, String)>) -> Option { + install_path + .map(|(path, version)| { + Self::probe_install_path(lib_name, &path, &version) + }) + .or_else(|| Self::probe_pkg_config_optional(lib_name)) + } + + /// The include dir `clang_arg` bindgen builder argument. + /// + /// # Panics + /// - If the library specifies a header file does not have a parent directory. + /// - If the library specifies a header file path that contain invalid utf-8 characters. + pub fn include_dir_arg(&self) -> String { + self.header_file + .parent() + .unwrap_or_else(|| panic!("Inconsistent `{}` header file path.", self.name)) + .as_os_str() + .to_str() + .map_or_else(|| { + panic!("Error converting OsString to &str when processing `{}` include dir.", self.name); + }, |v| format!("-I{}", v)) + } + + /// The header file path to a `header` bindgen argument. + /// + /// # Panics + /// - If the library specifies a header file path that contain invalid utf-8 characters. + pub fn header_file_arg(&self) -> &str { + self.header_file + .as_os_str() + .to_str() + .unwrap_or_else(|| panic!("Error converting OsString to &str when processing `{}` include dir.", self.name)) + } + + /// Probe the system for an optional library using pkg-config. + fn probe_pkg_config_optional(lib_name: &str) -> Option { + pkg_config::Config::new() + .atleast_version(MINIMUM_VERSION) + .probe(lib_name) + .ok() + .map(|pkg_config| { + let header_file = pkg_config.include_paths[0] + .join("tss2") + .join(lib_name.replace("-", "_") + ".h"); + if !header_file.is_file() { + panic!("Header file `{}` does not exist.", header_file.to_string_lossy()); + } + Self {header_file, version: pkg_config.version, name: lib_name.to_string()} + }) + } + + /// Probe the install path for a library. + /// + /// # Arguments + /// `lib_name` - The name of the library to probe for. + /// `path` - The path to probe for the library. + /// `version` - The version of the library. + /// + /// # Returns + /// A `Library` object containing the information retrieved. + /// + /// # Panics + /// - If no `.lib` file for the library was found. + /// - If no `.h` file for the library was found. + fn probe_install_path(lib_name: &str, path: &Path, version: &str) -> Self { + let file_name = PathBuf::from(lib_name.replace("-", "_")); + let lib_file = path.join("lib").join(file_name.with_extension("lib")); + if !lib_file.is_file() { + panic!("Lib file `{}`, does not exist.", lib_file.to_string_lossy()); + } + let header_file = path.join("include").join(file_name.with_extension("h")); + if !header_file.is_file() { + panic!("Header file `{}`, does not exist.", header_file.to_string_lossy()); + } + Self {header_file, version: version.to_string(), name: lib_name.to_string()} + } + } }