pcs 0.8.3

Provisioning Certification Service (PCS) data structures. Data structures related to the Intel Provisioning Certification Service. DCAP attestation requires handling of DCAP artifacts (e.g., PCK certs, TCB info, ...). This crate provides an easy interface for these artifacts.
Documentation
/* Copyright (c) Fortanix, Inc.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

use std::fs::File;
use std::io::{BufReader, Write};
use std::path::{Path, PathBuf};

use serde::de::DeserializeOwned;

use crate::Error;

///Additional parameters to be passed to the IO write related function.
pub struct WriteOptions {
    no_overwrite: bool,
}

///Builds [WriteOptions] instance
pub struct WriteOptionsBuilder {
    no_overwrite: bool,
}

impl WriteOptionsBuilder {
    /// Instantiate a new [WriteOptionsBuilder] instance.
    pub fn new() -> Self {
        Self {
            no_overwrite: false
        }
    }

    ///Prevent the write operation to overwrite existing file.
    pub fn disallow_overwrite(mut self) -> Self {
        self.no_overwrite = true;
        self
    }

    ///Instantiate the WriteOptions instance based on the current state of the builder.
    pub fn build(self) -> WriteOptions {
        WriteOptions {
            no_overwrite : self.no_overwrite,
        }
    }
}

/// Write the serializable object `obj` into JSON on a given path `dir`/`filename`. See [WriteOptionsBuilder]
/// to specify the parameters for file writing operations.
pub fn write_to_file<T: serde::ser::Serialize>(obj: &T, dir: &str, filename: &str, options: WriteOptions) -> Result<Option<PathBuf>, Error> {
    let path = Path::new(dir);
    let path = path.join(filename);

    if options.no_overwrite && path.exists() {
        return Ok(None)
    }

    write_to_path(&path, obj)?;
    Ok(Some(path))
}

fn write_to_path<T: serde::ser::Serialize>(path: &PathBuf, obj: &T) -> Result<(), Error> {
    let mut fp = File::create(&path)?;

    fp.write_all(&serde_json::ser::to_vec(obj).unwrap())
        .map_err(|e| Error::IoError(e))
}

///Function to read a deserializable object from JSON file from a given path `dir`/`filename`.
pub fn read_from_file<T: DeserializeOwned>(dir: &str, filename: impl AsRef<Path>) -> Result<T, Error> {
    let path = Path::new(dir);
    let path = path.join(filename);
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    let obj = serde_json::from_reader(reader)?;
    Ok(obj)
}

pub(crate) fn compose_filename(prefix: &str, extension: &str, evaluation_data_number: Option<u64>) -> String {
    if let Some(evaluation_data_number) = evaluation_data_number {
        format!("{}-{evaluation_data_number}{}", prefix, extension)
    } else {
        format!("{}{}", prefix, extension)
    }
}

pub(crate) fn all_files<'a>(input_dir: &str, prefix: impl AsRef<str> + 'a, extension: &'a str) -> impl Iterator<Item = Result<std::fs::DirEntry, Error>> + 'a {
    // Construct an iterator where if read_dir() returns an error, that is the
    // first and only item. If it returns success, the directory entries are
    // the items.
    let (first, second) = match std::fs::read_dir(input_dir) {
        Ok(it) => (Some(it), None),
        Err(e) => (None, Some(Err(e))),
    };
    let entries = first.into_iter().flat_map(|it| it).chain(second.into_iter());

    // match filenames that are generated by compose_filename() above
    entries.filter_map(move |item| {
            let prefix = prefix.as_ref();
            match item {
                Ok(entry) => {
                    let fname = entry.file_name();
                    let fname = fname.to_str()?;
                    if !(fname.starts_with(prefix) && fname.ends_with(extension)) {
                        return None;
                    }
                    if fname.len() != prefix.len() + extension.len() {
                        let middle = &fname[(prefix.len())..(fname.len() - extension.len())];
                        if !middle.starts_with("-") {
                            return None;
                        }
                        let _: u64 = middle[1..].parse().ok()?;
                    }
                    Some(Ok(entry))
                },
                Err(e) => Some(Err(e.into()))
            }
        })
}

#[cfg(test)]
mod tests {
    #[cfg(not(target_env = "sgx"))]
    #[test]
    fn test_all_files() {
        use std::{collections::HashSet, ffi::OsString};

        let a: HashSet<_> = Result::unwrap(super::all_files("./tests/data/read-dir-test", "prefix", ".ext").map(|i| i.map(|entry| entry.file_name()) ).collect());
        let b: HashSet<_> = <_>::into_iter(["prefix-1.ext", "prefix-2.ext", "prefix-3.ext", "prefix.ext"]).map(|i| OsString::from(i)).collect();
        assert_eq!(a, b);
    }
}