Преглед изворни кода

* Factored SwtpmHarness into its own create.
* Removed incorrect implementations of FileReadWriteVolatile.

Matthew Carr пре 2 година
родитељ
комит
1e2c6afba7

+ 13 - 2
Cargo.lock

@@ -146,6 +146,7 @@ dependencies = [
  "env_logger",
  "fuse-backend-rs",
  "log",
+ "swtpm-harness",
  "tempdir",
  "tss-esapi",
 ]
@@ -159,7 +160,6 @@ dependencies = [
  "btserde",
  "chrono",
  "ctor",
- "dbus",
  "env_logger",
  "foreign-types",
  "fuse-backend-rs",
@@ -167,7 +167,6 @@ dependencies = [
  "lazy_static",
  "libc",
  "log",
- "nix 0.25.0",
  "openssl",
  "os_pipe",
  "serde",
@@ -175,6 +174,7 @@ dependencies = [
  "static_assertions",
  "strum",
  "strum_macros",
+ "swtpm-harness",
  "tempdir",
  "tss-esapi",
  "tss-esapi-sys",
@@ -1081,6 +1081,17 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "swtpm-harness"
+version = "0.1.0"
+dependencies = [
+ "dbus",
+ "log",
+ "nix 0.25.0",
+ "tempdir",
+ "tss-esapi",
+]
+
 [[package]]
 name = "syn"
 version = "1.0.100"

+ 1 - 0
Cargo.toml

@@ -6,4 +6,5 @@ members = [
     "crates/harness",
     "crates/test-harness",
     "crates/btfuse",
+    "crates/swtpm-harness",
 ]

+ 0 - 2
crates/btapp/README.md

@@ -1,2 +0,0 @@
-## Overview
-This create implements a sandbox in which user code written in WebAssembly is run.

+ 1 - 1
crates/btfuse/Cargo.toml

@@ -7,11 +7,11 @@ edition = "2021"
 
 [dependencies]
 btlib = { path = "../btlib" }
+swtpm-harness = { path = "../swtpm-harness" }
 tss-esapi = { version = "7.1.0", features = ["generate-bindings"] }
 fuse-backend-rs = "0.9.6"
 log = "0.4.17"
 env_logger = "0.9.0"
 
 [dev-dependencies]
-btlib = { path = "../btlib", features = ["testing"] }
 tempdir = "0.3.7"

+ 4 - 3
crates/btfuse/src/main.rs

@@ -151,20 +151,21 @@ fn main() {
 mod test {
     use std::time::Duration;
 
-    use btlib::{crypto::Creds, test_helpers, Epoch, Principaled, btlog::BuilderExt};
+    use btlib::{crypto::Creds, log::BuilderExt, Epoch, Principaled};
+    use swtpm_harness::SwtpmHarness;
     use tempdir::TempDir;
 
     use super::*;
 
     /// Creates a new file system and mounts it at `/tmp/btfuse.<random>/mnt`.
-    #[test]
+    //#[test]
     #[allow(dead_code)]
     fn server_start() {
         const ROOT_PASSWD: &str = "Gnurlingwheel";
         std::env::set_var("RUST_LOG", "debug");
         env_logger::Builder::from_default_env().btformat().init();
         let temp_dir = TempDir::new("btfuse").expect("failed to create TempDir");
-        let swtpm = test_helpers::SwtpmHarness::new().expect("failed to start swtpm");
+        let swtpm = SwtpmHarness::new().expect("failed to start swtpm");
         let daemon = FuseDaemon::new(temp_dir.path(), swtpm.tabrmd_config());
         {
             let context = swtpm.context().expect("failed to create TPM context");

+ 7 - 22
crates/btlib/Cargo.toml

@@ -6,21 +6,10 @@ edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
-[features]
-# This feature allows test only code to be used in other crates. See the comment below for more
-# information.
-testing=[
-    "dep:tempdir",
-    "dep:ctor",
-    "dep:nix",
-    "dep:lazy_static",
-    "dep:dbus",
-    "dep:vm-memory"
-]
-
 [dependencies]
 btserde = { path = "../btserde" }
 harness = { path = "../harness" }
+swtpm-harness = { path = "../swtpm-harness" }
 serde = { version = "^1.0.136", features = ["derive"] }
 serde-big-array = { version = "^0.4.1" }
 openssl = { version = "^0.10.38", features = ["vendored"] }
@@ -40,13 +29,9 @@ fuse-backend-rs = "0.9.6"
 libc = "0.2.137"
 env_logger = { version = "0.9.0" }
 chrono = "0.4.23"
-# These are dev-dependencies. They are declared this way so test_helpers can be used in other
-# crates. The build.rs script also supports this by turning on "cfg(test)" when
-# the testing feature is enabled. This is a hack and should be removed once this issue is resolved:
-# https://github.com/rust-lang/cargo/issues/8379
-tempdir = { version = "0.3.7", optional = true }
-ctor = { version = "0.1.22", optional = true }
-nix = { version = "0.25.0", optional = true }
-lazy_static = { version = "1.4.0", optional = true }
-dbus = { version = "0.9.6", optional = true }
-vm-memory = { version = "0.9.0", optional = true }
+
+[dev-dependencies]
+tempdir = { version = "0.3.7" }
+ctor = { version = "0.1.22" }
+lazy_static = { version = "1.4.0" }
+vm-memory = { version = "0.9.0" }

+ 0 - 5
crates/btlib/build.rs

@@ -1,5 +0,0 @@
-fn main() {
-    // Turn on the testing feature when "cfg(test)" is given.
-    #[cfg(feature = "test")]
-    println!("cargo:rustc-cfg=feature=\"testing\"")
-}

+ 0 - 1
crates/btlib/src/block_path.rs

@@ -186,7 +186,6 @@ mod private {
     }
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use crate::{

+ 159 - 41
crates/btlib/src/blocktree.rs

@@ -8,7 +8,7 @@ mod private {
             Context, DirEntry as FuseDirEntry, Entry, FileSystem, FsOptions, OpenOptions,
         },
     };
-    use log::{error, debug};
+    use log::{debug, error};
     use serde::{Deserialize, Serialize};
     use std::{
         collections::hash_map::{self, HashMap},
@@ -24,8 +24,8 @@ mod private {
     };
 
     use crate::{
-        crypto::Creds,
-        Block, BlockOpenOptions, BlockPath, BoxInIoErr, DirEntry, Directory, Error, Result, Epoch,
+        crypto::Creds, Block, BlockOpenOptions, BlockPath, BoxInIoErr, DirEntry, Directory, Epoch,
+        Error, Result,
     };
 
     type Inode = u64;
@@ -48,8 +48,8 @@ mod private {
     }
 
     impl FileType {
-        const S_IFDIR: u32 = 0o040000;
-        const S_IFREG: u32 = 0o100000;
+        const S_IFDIR: u32 = libc::S_IFDIR;
+        const S_IFREG: u32 = libc::S_IFREG;
 
         /// Returns the underlying mode bits for this file type.
         fn value(self) -> u32 {
@@ -85,6 +85,25 @@ mod private {
         }
     }
 
+    trait SeekFromExt {
+        /// Converts the c-style `(whence, offset)` pair into a [SeekFrom] enum value.
+        /// See the POSIX man page for `lseek` for more details.
+        fn whence_offset(whence: u32, offset: u64) -> io::Result<SeekFrom> {
+            let whence = whence as i32;
+            match whence {
+                libc::SEEK_SET => Ok(SeekFrom::Start(offset)),
+                libc::SEEK_CUR => Ok(SeekFrom::Current(offset as i64)),
+                libc::SEEK_END => Ok(SeekFrom::End(-(offset as i64))),
+                _ => Err(io::Error::new(
+                    io::ErrorKind::InvalidInput,
+                    "`whence` was not one of `libc::{SEEK_SET, SEEK_CUR, SEEK_END}`",
+                )),
+            }
+        }
+    }
+
+    impl SeekFromExt for SeekFrom {}
+
     struct InodeTableValue {
         block: Option<Box<dyn Block>>,
         /// The path to the block. This is cloned from the block's metadata when the entry is
@@ -393,7 +412,7 @@ mod private {
                     .map_err(|err| Error::custom(err.to_string()))?;
                 let entry = inodes
                     .get_mut(&inode)
-                    .ok_or(Error::custom("inode table entry was removed unexpectedly"))?;
+                    .ok_or_else(|| Error::custom("inode table entry was removed unexpectedly"))?;
                 entry.block = None;
             }
             Ok(())
@@ -502,7 +521,7 @@ mod private {
             ctx: &Context,
             inode: Self::Inode,
             flags: u32,
-            handle: Self::Handle,
+            _handle: Self::Handle,
             flush: bool,
             flock_release: bool,
             lock_owner: Option<u64>,
@@ -626,8 +645,7 @@ mod private {
                             if written > 0 {
                                 error!("error while reading: {err}");
                                 return Ok(written);
-                            }
-                            else {
+                            } else {
                                 return Err(err);
                             }
                         }
@@ -641,8 +659,7 @@ mod private {
                         if written > 0 {
                             error!("error while writing: {err}");
                             return Ok(written);
-                        }
-                        else {
+                        } else {
                             return Err(err);
                         }
                     }
@@ -686,8 +703,7 @@ mod private {
                             if read > 0 {
                                 error!("error while reading from block: {err}");
                                 return Ok(read);
-                            }
-                            else {
+                            } else {
                                 return Err(err);
                             }
                         }
@@ -701,8 +717,7 @@ mod private {
                         if read > 0 {
                             error!("error while writing: {err}");
                             return Ok(read);
-                        }
-                        else {
+                        } else {
                             return Err(err);
                         }
                     }
@@ -771,6 +786,19 @@ mod private {
             debug!("Blocktree::forget called for inode {inode}");
         }
 
+        fn lseek(
+            &self,
+            ctx: &Context,
+            inode: Self::Inode,
+            _handle: Self::Handle,
+            offset: u64,
+            whence: u32,
+        ) -> io::Result<u64> {
+            debug!("Blocktree::lseek called for inode {inode}");
+            let seek_from = SeekFrom::whence_offset(whence, offset)?;
+            self.access_block(inode, |block| block.seek(seek_from))
+        }
+
         //////////////////////////////////
         // METHODS WHICH ARE NOT SUPPORTED
         //////////////////////////////////
@@ -890,18 +918,6 @@ mod private {
             Self::not_supported()
         }
 
-        fn lseek(
-            &self,
-            _ctx: &Context,
-            inode: Self::Inode,
-            _handle: Self::Handle,
-            _offset: u64,
-            _whence: u32,
-        ) -> io::Result<u64> {
-            debug!("Blocktree::lseek called for inode {inode}");
-            Self::not_supported()
-        }
-
         fn mkdir(
             &self,
             _ctx: &Context,
@@ -966,7 +982,7 @@ mod private {
             Self::not_supported()
         }
 
-        fn removexattr(&self, ctx: &Context, inode: Self::Inode, _name: &CStr) -> io::Result<()> {
+        fn removexattr(&self, _ctx: &Context, inode: Self::Inode, _name: &CStr) -> io::Result<()> {
             debug!("Blocktree::removexattr called for inode {inode}");
             Self::not_supported()
         }
@@ -1062,27 +1078,53 @@ mod private {
     }
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use std::ffi::CString;
 
-    use fuse_backend_rs::api::filesystem::FileSystem;
+    use fuse_backend_rs::api::filesystem::{FileSystem, FsOptions};
     use tempdir::TempDir;
 
     use test_helpers::*;
 
-    use crate::test_helpers;
+    use crate::{crypto::ConcreteCreds, test_helpers, Decompose};
 
     use super::{private::SpecInodes, *};
 
+    struct BtTestCase {
+        dir: TempDir,
+        bt: Blocktree<ConcreteCreds>,
+    }
+
+    impl BtTestCase {
+        fn new_empty() -> BtTestCase {
+            let dir = TempDir::new("fuse").expect("failed to create temp dir");
+            let bt = Blocktree::new_empty(dir.path().to_owned(), 0, Self::creds())
+                .expect("failed to create empty blocktree");
+            bt.init(FsOptions::empty()).expect("init failed");
+            BtTestCase { dir, bt }
+        }
+
+        fn new_existing(dir: TempDir) -> BtTestCase {
+            let bt = Blocktree::new_existing(dir.path().to_owned(), Self::creds())
+                .expect("failed to create blocktree from existing directory");
+            bt.init(FsOptions::empty()).expect("init failed");
+            BtTestCase { dir, bt }
+        }
+
+        fn creds() -> ConcreteCreds {
+            test_helpers::NODE_CREDS.clone()
+        }
+    }
+
+    /// Tests that a new file can be created, written to and the written data can be read from it.
     #[test]
-    fn create_write_open_read() {
-        let dir = TempDir::new("fuse").expect("failed to create temp dir");
-        let creds = test_helpers::NODE_CREDS.clone();
-        let bt = Blocktree::new_empty(dir.path().to_owned(), 0, creds)
-            .expect("failed to create empty blocktree");
+    fn create_open_write_read() {
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+
         let name = CString::new("README.md").unwrap();
+
         let (entry, ..) = bt
             .create(
                 &Default::default(),
@@ -1091,10 +1133,12 @@ mod tests {
                 Default::default(),
             )
             .expect("failed to create file");
+
         bt.open(&Default::default(), entry.inode, 0, 0)
             .expect("failed to open file");
         const LEN: usize = 32;
         let mut expected = BtCursor::new([1u8; LEN]);
+
         let written = bt
             .write(
                 &Default::default(),
@@ -1110,8 +1154,10 @@ mod tests {
             )
             .expect("write failed");
         assert_eq!(LEN, written);
-
         let mut actual = BtCursor::new([0u8; LEN]);
+        bt.lseek(&Default::default(), entry.inode, Default::default(), 0, 0)
+            .expect("lseek failed");
+
         let read = bt
             .read(
                 &Default::default(),
@@ -1131,10 +1177,9 @@ mod tests {
 
     #[test]
     fn lookup() {
-        let dir = TempDir::new("fuse").expect("failed to create temp dir");
-        let creds = test_helpers::NODE_CREDS.clone();
-        let bt = Blocktree::new_empty(dir.path().to_owned(), 0, creds)
-            .expect("failed to create empty blocktree");
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+
         let name = CString::new("README.md").unwrap();
         let (expected, ..) = bt
             .create(
@@ -1152,4 +1197,77 @@ mod tests {
         assert_eq!(expected.generation, actual.generation);
         assert_eq!(expected.inode, actual.inode);
     }
+
+    /// Tests that data written by one instance of [Blocktree] can be read by a subsequent
+    /// instance.
+    #[test]
+    fn new_existing() {
+        const EXPECTED: &[u8] = b"cool as cucumbers";
+        let name = CString::new("RESIGNATION.docx").unwrap();
+
+        let case = BtTestCase::new_empty();
+        let bt = &case.bt;
+
+        {
+            let (entry, ..) = bt
+                .create(
+                    &Default::default(),
+                    SpecInodes::RootDir.into(),
+                    name.as_c_str(),
+                    Default::default(),
+                )
+                .expect("failed to create file");
+
+            bt.open(&Default::default(), entry.inode, 0, 0)
+                .expect("open failed");
+
+            let mut vec = Vec::with_capacity(EXPECTED.len());
+            vec.extend_from_slice(EXPECTED);
+            let mut cursor = BtCursor::new(vec);
+            let written = bt
+                .write(
+                    &Default::default(),
+                    entry.inode,
+                    Default::default(),
+                    &mut cursor,
+                    EXPECTED.len() as u32,
+                    0,
+                    None,
+                    false,
+                    0,
+                    0,
+                )
+                .expect("write failed");
+            assert_eq!(EXPECTED.len(), written);
+
+            bt.flush(&Default::default(), entry.inode, Default::default(), 0)
+                .expect("flush failed");
+        }
+
+        let case = BtTestCase::new_existing(case.dir);
+        let bt = &case.bt;
+
+        let entry = bt
+            .lookup(&Default::default(), SpecInodes::RootDir.into(), &name)
+            .expect("lookup failed");
+
+        bt.open(&Default::default(), entry.inode, 0, 0)
+            .expect("open failed");
+
+        let mut actual = BtCursor::new([0u8; EXPECTED.len()]);
+        let _ = bt
+            .read(
+                &Default::default(),
+                entry.inode,
+                Default::default(),
+                &mut actual,
+                EXPECTED.len() as u32,
+                0,
+                None,
+                0,
+            )
+            .expect("read failed");
+
+        assert_eq!(EXPECTED, actual.into_inner().as_slice())
+    }
 }

+ 10 - 39
crates/btlib/src/crypto/merkle_stream.rs

@@ -7,9 +7,9 @@ mod private {
     use crate::{
         crypto::{Error, HashKind, Result},
         trailered::Trailered,
-        BoxInIoErr, Decompose, MetaAccess, Sectored, TryCompose, WriteInteg, SECTOR_SZ_DEFAULT,
+        BlockMeta, BoxInIoErr, Decompose, MetaAccess, Sectored, TryCompose, WriteInteg,
+        SECTOR_SZ_DEFAULT,
     };
-    use fuse_backend_rs::file_traits::FileReadWriteVolatile;
     use serde::{Deserialize, Serialize};
     use std::io::{self, Read, Seek, Write};
     use strum::EnumDiscriminants;
@@ -645,50 +645,21 @@ mod private {
         }
     }
 
-    impl<T: MetaAccess> MetaAccess for MerkleStream<T> {
-        fn meta(&self) -> &crate::BlockMeta {
-            self.trailered.meta()
-        }
-
-        fn mut_meta(&mut self) -> &mut crate::BlockMeta {
-            self.trailered.mut_meta()
+    impl<T: AsRef<BlockMeta>> AsRef<BlockMeta> for MerkleStream<T> {
+        fn as_ref(&self) -> &BlockMeta {
+            self.trailered.as_ref()
         }
     }
 
-    impl<T: FileReadWriteVolatile> FileReadWriteVolatile for MerkleStream<T> {
-        fn read_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        ) -> io::Result<usize> {
-            self.trailered.read_volatile(slice)
-        }
-
-        fn write_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        ) -> io::Result<usize> {
-            self.trailered.write_volatile(slice)
-        }
-
-        fn read_at_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-            offset: u64,
-        ) -> io::Result<usize> {
-            self.trailered.read_at_volatile(slice, offset)
-        }
-
-        fn write_at_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-            offset: u64,
-        ) -> io::Result<usize> {
-            self.trailered.write_at_volatile(slice, offset)
+    impl<T: AsMut<BlockMeta>> AsMut<BlockMeta> for MerkleStream<T> {
+        fn as_mut(&mut self) -> &mut BlockMeta {
+            self.trailered.as_mut()
         }
     }
+
+    impl<T: MetaAccess> MetaAccess for MerkleStream<T> {}
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 pub(crate) mod tests {
     use std::io::{Read, Seek, SeekFrom, Write};

+ 3 - 4
crates/btlib/src/crypto/mod.rs

@@ -740,8 +740,8 @@ impl Signature {
         Signature { kind, data }
     }
 
-    #[cfg(feature = "testing")]
-    pub(crate) fn copy_from(kind: Sign, from: &[u8]) -> Signature {
+    #[cfg(test)]
+    pub fn copy_from(kind: Sign, from: &[u8]) -> Signature {
         let mut data = vec![0; kind.key_len() as usize];
         data.as_mut_slice().copy_from_slice(from);
         Signature { kind, data }
@@ -1583,7 +1583,7 @@ impl ConcreteCreds {
         }
     }
 
-    #[cfg(feature = "testing")]
+    #[cfg(test)]
     pub fn generate() -> Result<ConcreteCreds> {
         let encrypt = Encrypt::RSA_OAEP_3072_SHA_256.generate()?;
         let sign = Sign::RSA_PSS_3072_SHA_256.generate()?;
@@ -2059,7 +2059,6 @@ impl Writecap {
     }
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use super::*;

+ 10 - 41
crates/btlib/src/crypto/secret_stream.rs

@@ -1,11 +1,9 @@
 pub use private::SecretStream;
 
 mod private {
-    use fuse_backend_rs::file_traits::FileReadWriteVolatile;
-
     use crate::{
         crypto::{Error, Result, SymKey},
-        Block, Decompose, MetaAccess, Sectored, TryCompose,
+        Block, BlockMeta, Decompose, MetaAccess, Sectored, TryCompose,
     };
     use std::io::{self, Read, Seek, SeekFrom, Write};
 
@@ -166,52 +164,23 @@ mod private {
         }
     }
 
-    impl<T: MetaAccess> MetaAccess for SecretStream<T> {
-        fn meta(&self) -> &crate::BlockMeta {
-            self.inner.meta()
-        }
-
-        fn mut_meta(&mut self) -> &mut crate::BlockMeta {
-            self.inner.mut_meta()
+    impl<T: AsRef<BlockMeta>> AsRef<BlockMeta> for SecretStream<T> {
+        fn as_ref(&self) -> &BlockMeta {
+            self.inner.as_ref()
         }
     }
 
-    impl<T: FileReadWriteVolatile> FileReadWriteVolatile for SecretStream<T> {
-        fn read_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        ) -> io::Result<usize> {
-            self.inner.read_volatile(slice)
-        }
-
-        fn write_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        ) -> io::Result<usize> {
-            self.inner.write_volatile(slice)
-        }
-
-        fn read_at_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-            offset: u64,
-        ) -> io::Result<usize> {
-            self.inner.read_at_volatile(slice, offset)
-        }
-
-        fn write_at_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-            offset: u64,
-        ) -> io::Result<usize> {
-            self.inner.write_at_volatile(slice, offset)
+    impl<T: AsMut<BlockMeta>> AsMut<BlockMeta> for SecretStream<T> {
+        fn as_mut(&mut self) -> &mut BlockMeta {
+            self.inner.as_mut()
         }
     }
 
-    impl<T: Read + Write + Seek + MetaAccess + FileReadWriteVolatile> Block for SecretStream<T> {}
+    impl<T: MetaAccess> MetaAccess for SecretStream<T> {}
+
+    impl<T: Read + Write + Seek + MetaAccess> Block for SecretStream<T> {}
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use std::io::{Read, Seek, SeekFrom, Write};

+ 8 - 11
crates/btlib/src/crypto/tpm.rs

@@ -1194,15 +1194,12 @@ impl CredStore for TpmCredStore {
     ) -> Result<()> {
         handle.writecap = Some(writecap.clone());
         let mut state = self.state.write()?;
-        state
-            .node_creds
-            .as_mut()
-            .map(|e| e.writecap = Some(writecap.clone()));
-        state
-            .storage
-            .node
-            .as_mut()
-            .map(|e| e.writecap = Some(writecap));
+        if let Some(creds) = state.node_creds.as_mut() {
+            creds.writecap = Some(writecap.clone());
+        }
+        if let Some(creds) = state.storage.node.as_mut() {
+            creds.writecap = Some(writecap);
+        }
         self.save_storage(&mut state)
     }
 }
@@ -1531,10 +1528,10 @@ impl HasResponseCode for TSS2_RC {
     }
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 mod test {
-    use crate::test_helpers::{BtCursor, SwtpmHarness};
+    use crate::test_helpers::BtCursor;
+    use swtpm_harness::SwtpmHarness;
 
     use super::*;
 

+ 25 - 57
crates/btlib/src/lib.rs

@@ -1,24 +1,24 @@
 #![allow(incomplete_features)]
 #![feature(trait_upcasting)]
 
-mod block_path;
 pub use block_path::{BlockPath, BlockPathError};
 pub mod blocktree;
 /// Code which enables cryptographic operations.
 pub mod crypto;
+pub mod log;
 pub mod msg;
+
+mod block_path;
 mod sectored_buf;
+#[cfg(test)]
+mod test_helpers;
 mod trailered;
-pub mod btlog;
-
-#[cfg(feature = "testing")]
-pub mod test_helpers;
 
 #[macro_use]
 extern crate static_assertions;
 
-#[cfg(feature = "testing")]
 #[macro_use]
+#[cfg(test)]
 extern crate lazy_static;
 
 use brotli::{CompressorWriter, Decompressor};
@@ -27,11 +27,8 @@ use crypto::{
     MerkleStream, PublicCreds, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind, VarHash,
 };
 
-use fuse_backend_rs::{
-    abi::fuse_abi::{stat64, Attr},
-    file_traits::FileReadWriteVolatile,
-};
-use log::error;
+use ::log::error;
+use fuse_backend_rs::abi::fuse_abi::{stat64, Attr};
 use sectored_buf::SectoredBuf;
 use serde::{Deserialize, Serialize};
 use serde_big_array::BigArray;
@@ -153,7 +150,7 @@ impl<T, E: Display> StrInIoErr<T> for std::result::Result<T, E> {
 const SECTOR_SZ_DEFAULT: usize = 4096;
 
 /// Trait for types which provide read and write access to blocks.
-pub trait Block: Read + Write + Seek + MetaAccess + FileReadWriteVolatile {}
+pub trait Block: Read + Write + Seek + MetaAccess {}
 
 // A trait for streams which only allow reads and writes in fixed sized units called sectors.
 pub trait Sectored {
@@ -197,9 +194,13 @@ impl<T, U: Decompose<T>, S: TryCompose<T, U, Error = Infallible>> Compose<T, U>
 }
 
 /// Trait for accessing the metadata associated with a block.
-pub trait MetaAccess {
-    fn meta(&self) -> &BlockMeta;
-    fn mut_meta(&mut self) -> &mut BlockMeta;
+pub trait MetaAccess: AsRef<BlockMeta> + AsMut<BlockMeta> {
+    fn meta(&self) -> &BlockMeta {
+        self.as_ref()
+    }
+    fn mut_meta(&mut self) -> &mut BlockMeta {
+        self.as_mut()
+    }
 
     fn meta_body(&self) -> &BlockMetaBody {
         self.meta().body()
@@ -581,52 +582,21 @@ impl<T: Seek, C> Seek for BlockStream<T, C> {
     }
 }
 
-impl<T, C: Decrypter + Principaled> MetaAccess for BlockStream<T, C> {
-    fn meta(&self) -> &BlockMeta {
+impl<T, C> AsRef<BlockMeta> for BlockStream<T, C> {
+    fn as_ref(&self) -> &BlockMeta {
         &self.meta
     }
+}
 
-    fn mut_meta(&mut self) -> &mut BlockMeta {
+impl<T, C> AsMut<BlockMeta> for BlockStream<T, C> {
+    fn as_mut(&mut self) -> &mut BlockMeta {
         &mut self.meta
     }
 }
 
-impl<T: FileReadWriteVolatile, C> FileReadWriteVolatile for BlockStream<T, C> {
-    fn read_volatile(
-        &mut self,
-        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-    ) -> io::Result<usize> {
-        self.trailered.read_volatile(slice)
-    }
-
-    fn write_volatile(
-        &mut self,
-        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-    ) -> io::Result<usize> {
-        self.trailered.write_volatile(slice)
-    }
-
-    fn read_at_volatile(
-        &mut self,
-        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        offset: u64,
-    ) -> io::Result<usize> {
-        self.trailered.read_at_volatile(slice, offset)
-    }
+impl<T, C> MetaAccess for BlockStream<T, C> {}
 
-    fn write_at_volatile(
-        &mut self,
-        slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        offset: u64,
-    ) -> io::Result<usize> {
-        self.trailered.write_at_volatile(slice, offset)
-    }
-}
-
-impl<T: Read + Write + Seek + MetaAccess + FileReadWriteVolatile, C: Creds> Block
-    for BlockStream<T, C>
-{
-}
+impl<T: Read + Write + Seek + MetaAccess, C: Creds> Block for BlockStream<T, C> {}
 
 pub struct BlockOpenOptions<T, C> {
     inner: T,
@@ -685,9 +655,7 @@ impl<T, C> BlockOpenOptions<T, C> {
     }
 }
 
-impl<T: Read + Write + Seek + FileReadWriteVolatile + 'static, C: Creds + 'static>
-    BlockOpenOptions<T, C>
-{
+impl<T: Read + Write + Seek + 'static, C: Creds + 'static> BlockOpenOptions<T, C> {
     pub fn open(self) -> Result<Box<dyn Block>> {
         let block_path = self.block_path.ok_or(Error::NoBlockPath)?;
         let stream = BlockStream::new(self.inner, self.creds, block_path)?;
@@ -1070,7 +1038,6 @@ impl<F: FnOnce()> Drop for DropTrigger<F> {
     }
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use std::{fs::OpenOptions, io::Cursor, path::PathBuf};
@@ -1082,6 +1049,7 @@ mod tests {
 
     use super::*;
     use btserde::{from_vec, to_vec};
+    use swtpm_harness::SwtpmHarness;
     use tempdir::TempDir;
     use test_helpers::*;
 

+ 2 - 2
crates/btlib/src/btlog.rs → crates/btlib/src/log.rs

@@ -1,6 +1,6 @@
+use chrono;
 use env_logger;
 use std::io::Write;
-use chrono;
 
 pub trait BuilderExt {
     /// Uses a standard format for log messages which includes the source file and line number
@@ -22,4 +22,4 @@ impl BuilderExt for env_logger::Builder {
             )
         })
     }
-}
+}

+ 0 - 1
crates/btlib/src/msg.rs

@@ -167,7 +167,6 @@ mod private {
     }
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use crate::test_helpers;

+ 24 - 41
crates/btlib/src/sectored_buf.rs

@@ -1,14 +1,14 @@
 pub use private::SectoredBuf;
 
 mod private {
-    use fuse_backend_rs::file_traits::FileReadWriteVolatile;
     use log::{error, warn};
     use std::io::{self, Read, Seek, SeekFrom, Write};
 
     use btserde::{read_from, write_to};
 
     use crate::{
-        Block, BoxInIoErr, Decompose, Error, MetaAccess, ReadExt, Result, Sectored, TryCompose,
+        Block, BlockMeta, BoxInIoErr, Decompose, Error, MetaAccess, ReadExt, Result, Sectored,
+        TryCompose,
     };
 
     /// A stream which buffers writes and read such that the inner stream only sees reads and writes
@@ -183,6 +183,7 @@ mod private {
                 src = &src[sz..];
                 self.dirty = sz > 0;
                 self.pos += sz;
+                self.len = self.len.max(self.pos);
             }
             Ok(src_len_start - src.len())
         }
@@ -218,7 +219,6 @@ mod private {
             self.inner.write_all(&self.buf)?;
 
             // Update the stored length.
-            self.len = self.len.max(self.pos);
             self.inner.mut_meta_body().access_secrets(|secrets| {
                 secrets.size = (self.len - Self::RESERVED).try_into().box_err()?;
                 Ok(())
@@ -313,52 +313,23 @@ mod private {
         }
     }
 
-    impl<T: MetaAccess> MetaAccess for SectoredBuf<T> {
-        fn meta(&self) -> &crate::BlockMeta {
-            self.inner.meta()
-        }
-
-        fn mut_meta(&mut self) -> &mut crate::BlockMeta {
-            self.inner.mut_meta()
+    impl<T: AsRef<BlockMeta>> AsRef<BlockMeta> for SectoredBuf<T> {
+        fn as_ref(&self) -> &BlockMeta {
+            self.inner.as_ref()
         }
     }
 
-    impl<T: FileReadWriteVolatile> FileReadWriteVolatile for SectoredBuf<T> {
-        fn read_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        ) -> io::Result<usize> {
-            self.inner.read_volatile(slice)
-        }
-
-        fn write_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        ) -> io::Result<usize> {
-            self.inner.write_volatile(slice)
-        }
-
-        fn read_at_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-            offset: u64,
-        ) -> io::Result<usize> {
-            self.inner.read_at_volatile(slice, offset)
-        }
-
-        fn write_at_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-            offset: u64,
-        ) -> io::Result<usize> {
-            self.inner.write_at_volatile(slice, offset)
+    impl<T: AsMut<BlockMeta>> AsMut<BlockMeta> for SectoredBuf<T> {
+        fn as_mut(&mut self) -> &mut BlockMeta {
+            self.inner.as_mut()
         }
     }
 
-    impl<T: Read + Write + Seek + MetaAccess + FileReadWriteVolatile> Block for SectoredBuf<T> {}
+    impl<T: MetaAccess> MetaAccess for SectoredBuf<T> {}
+
+    impl<T: Read + Write + Seek + MetaAccess> Block for SectoredBuf<T> {}
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use std::io::{Read, Seek, SeekFrom, Write};
@@ -460,6 +431,18 @@ mod tests {
         }
     }
 
+    /// Tests that data written can be read from the buffer without an intervening call to `flush`.
+    #[test]
+    fn sectored_buf_write_then_read() {
+        const EXPECTED: &[u8] = b"alpha";
+        let mut sectored = make_sectored_buf(4096, 1);
+        sectored.write(EXPECTED).expect("write failed");
+        sectored.seek(SeekFrom::Start(0)).expect("seek failed");
+        let mut actual = [0u8; EXPECTED.len()];
+        sectored.read(&mut actual).expect("read failed");
+        assert_eq!(EXPECTED, actual);
+    }
+
     #[test]
     fn sectored_buf_write_read_random() {
         const SECT_SZ: usize = 16;

+ 7 - 234
crates/btlib/src/test_helpers.rs

@@ -5,28 +5,13 @@ use crypto::{self, *};
 use fuse_backend_rs::{
     api::filesystem::{ZeroCopyReader, ZeroCopyWriter},
     file_buf::FileVolatileSlice,
-};
-use nix::{
-    sys::signal::{self, Signal},
-    unistd::Pid,
+    file_traits::FileReadWriteVolatile,
 };
 use std::{
     cell::RefCell,
     fmt::Write as FmtWrite,
     fs::File,
     io::{Cursor, Write},
-    path::PathBuf,
-    process::{Child, Command, ExitStatus, Stdio},
-    str::FromStr,
-    sync::{
-        atomic::{AtomicU16, Ordering},
-        mpsc::{channel, Receiver, TryRecvError},
-    },
-};
-use tempdir::TempDir;
-use tss_esapi::{
-    tcti_ldr::{TabrmdConfig, TctiNameConf},
-    Context,
 };
 use vm_memory::bytes::Bytes;
 
@@ -642,228 +627,16 @@ impl<T: FromVec> Seek for SectoredCursor<T> {
     }
 }
 
-impl<T: FromVec> MetaAccess for SectoredCursor<T> {
-    fn meta(&self) -> &BlockMeta {
+impl<T: FromVec> AsRef<BlockMeta> for SectoredCursor<T> {
+    fn as_ref(&self) -> &BlockMeta {
         &self.meta
     }
-
-    fn mut_meta(&mut self) -> &mut BlockMeta {
-        &mut self.meta
-    }
-}
-
-trait ExitStatusExt {
-    fn success_or_err(&self) -> Result<()>;
-}
-
-impl ExitStatusExt for ExitStatus {
-    fn success_or_err(&self) -> btserde::Result<()> {
-        match self.code() {
-            Some(0) => Ok(()),
-            Some(code) => Err(btserde::Error::Message(format!(
-                "ExitCode was non-zero: {}",
-                code
-            ))),
-            None => Err(btserde::Error::Message("ExitCode was None.".to_string())),
-        }
-    }
-}
-
-/// A DBus message which is sent when the ownership of a name changes.
-struct NameOwnerChanged {
-    name: String,
-    old_owner: String,
-    new_owner: String,
-}
-
-impl dbus::arg::AppendAll for NameOwnerChanged {
-    fn append(&self, iter: &mut dbus::arg::IterAppend) {
-        dbus::arg::RefArg::append(&self.name, iter);
-        dbus::arg::RefArg::append(&self.old_owner, iter);
-        dbus::arg::RefArg::append(&self.new_owner, iter);
-    }
 }
 
-impl dbus::arg::ReadAll for NameOwnerChanged {
-    fn read(iter: &mut dbus::arg::Iter) -> std::result::Result<Self, dbus::arg::TypeMismatchError> {
-        Ok(NameOwnerChanged {
-            name: iter.read()?,
-            old_owner: iter.read()?,
-            new_owner: iter.read()?,
-        })
-    }
-}
-
-impl dbus::message::SignalArgs for NameOwnerChanged {
-    const NAME: &'static str = "NameOwnerChanged";
-    const INTERFACE: &'static str = "org.freedesktop.DBus";
-}
-
-/// A struct used to block until a specific name appears on DBus.
-struct DbusBlocker {
-    receiver: Receiver<()>,
-    conn: dbus::blocking::Connection,
-}
-
-impl DbusBlocker {
-    fn new_session(name: String) -> Result<DbusBlocker> {
-        use dbus::{blocking::Connection, Message};
-        const DEST: &str = "org.freedesktop.DBus";
-
-        let (sender, receiver) = channel();
-        let conn = Connection::new_session().map_err(Error::custom)?;
-        let proxy = conn.with_proxy(DEST, "/org/freedesktop/DBus", Duration::from_secs(1));
-        let _ = proxy.match_signal(move |h: NameOwnerChanged, _: &Connection, _: &Message| {
-            let name_appeared = h.name == name;
-            if name_appeared {
-                if let Err(err) = sender.send(()) {
-                    error!("failed to send unblocking signal: {err}");
-                }
-            }
-            let remove_match = !name_appeared;
-            remove_match
-        });
-        Ok(DbusBlocker { receiver, conn })
-    }
-
-    fn block(&mut self, timeout: Duration) -> Result<()> {
-        let time_limit = SystemTime::now() + timeout;
-        loop {
-            self.conn
-                .process(Duration::from_millis(100))
-                .map_err(Error::custom)?;
-            match self.receiver.try_recv() {
-                Ok(_) => break,
-                Err(err) => match err {
-                    TryRecvError::Empty => (),
-                    _ => return Err(Error::custom(err)),
-                },
-            }
-            if SystemTime::now() > time_limit {
-                return Err(Error::custom("timed out"));
-            }
-        }
-        Ok(())
+impl<T: FromVec> AsMut<BlockMeta> for SectoredCursor<T> {
+    fn as_mut(&mut self) -> &mut BlockMeta {
+        &mut self.meta
     }
 }
 
-pub struct SwtpmHarness {
-    dir: TempDir,
-    state_path: PathBuf,
-    pid_path: PathBuf,
-    tabrmd: Child,
-    tabrmd_config: String,
-}
-
-impl SwtpmHarness {
-    const HOST: &'static str = "127.0.0.1";
-
-    fn dbus_name(port: u16) -> String {
-        let port_str: String = port
-            .to_string()
-            .chars()
-            // Shifting each code point by 17 makes the digits into capital letters.
-            .map(|e| ((e as u8) + 17) as char)
-            .collect();
-        format!("com.intel.tss2.Tabrmd.{port_str}")
-    }
-
-    pub fn new() -> crypto::Result<SwtpmHarness> {
-        static PORT: AtomicU16 = AtomicU16::new(21901);
-        let port = PORT.fetch_add(2, Ordering::SeqCst);
-        let ctrl_port = port + 1;
-        let dir = TempDir::new(format!("swtpm_harness.{port}").as_str())?;
-        let dir_path = dir.path();
-        let dir_path_display = dir_path.display();
-        let conf_path = dir_path.join("swtpm_setup.conf");
-        let state_path = dir_path.join("state.bt");
-        let pid_path = dir_path.join("swtpm.pid");
-        let dbus_name = Self::dbus_name(port);
-        let addr = Self::HOST;
-        std::fs::write(
-            &conf_path,
-            r#"# Program invoked for creating certificates
-#create_certs_tool= /usr/bin/swtpm_localca
-# Comma-separated list (no spaces) of PCR banks to activate by default
-active_pcr_banks = sha256
-"#,
-        )?;
-        Command::new("swtpm_setup")
-            .stdout(Stdio::null())
-            .args([
-                "--tpm2",
-                "--config",
-                conf_path.to_str().unwrap(),
-                "--tpm-state",
-                format!("{dir_path_display}").as_str(),
-            ])
-            .status()?
-            .success_or_err()?;
-        Command::new("swtpm")
-            .args([
-                "socket",
-                "--daemon",
-                "--tpm2",
-                "--server",
-                format!("type=tcp,port={port},bindaddr={addr}").as_str(),
-                "--ctrl",
-                format!("type=tcp,port={ctrl_port},bindaddr={addr}").as_str(),
-                "--log",
-                format!("file={dir_path_display}/log.txt,level=5").as_str(),
-                "--flags",
-                "not-need-init,startup-clear",
-                "--tpmstate",
-                format!("dir={dir_path_display}").as_str(),
-                "--pid",
-                format!("file={}", pid_path.display()).as_str(),
-            ])
-            .status()?
-            .success_or_err()?;
-        let mut blocker = DbusBlocker::new_session(dbus_name.clone())?;
-        let tabrmd = Command::new("tpm2-abrmd")
-            .args([
-                format!("--tcti=swtpm:host=127.0.0.1,port={port}").as_str(),
-                "--dbus-name",
-                dbus_name.as_str(),
-                "--session",
-            ])
-            .spawn()?;
-        blocker.block(Duration::from_secs(5))?;
-        Ok(SwtpmHarness {
-            dir,
-            state_path,
-            pid_path,
-            tabrmd,
-            tabrmd_config: format!("bus_name={},bus_type=session", Self::dbus_name(port)),
-        })
-    }
-
-    pub fn tabrmd_config(&self) -> &str {
-        &self.tabrmd_config
-    }
-
-    pub fn context(&self) -> crypto::Result<Context> {
-        let config = TabrmdConfig::from_str(self.tabrmd_config())?;
-        Ok(Context::new(TctiNameConf::Tabrmd(config))?)
-    }
-
-    pub fn dir_path(&self) -> &std::path::Path {
-        self.dir.path()
-    }
-
-    pub fn state_path(&self) -> &std::path::Path {
-        &self.state_path
-    }
-}
-
-impl Drop for SwtpmHarness {
-    fn drop(&mut self) {
-        if let Err(err) = self.tabrmd.kill() {
-            error!("failed to kill tpm2-abrmd: {err}");
-        }
-        let pid_str = std::fs::read_to_string(&self.pid_path).unwrap();
-        let pid_int = pid_str.parse::<i32>().unwrap();
-        let pid = Pid::from_raw(pid_int);
-        signal::kill(pid, Signal::SIGKILL).unwrap();
-    }
-}
+impl<T: FromVec> MetaAccess for SectoredCursor<T> {}

+ 10 - 40
crates/btlib/src/trailered.rs

@@ -7,10 +7,9 @@ mod private {
     };
 
     use btserde::{read_from, write_to};
-    use fuse_backend_rs::file_traits::FileReadWriteVolatile;
     use serde::{de::DeserializeOwned, Serialize};
 
-    use crate::{BoxInIoErr, Decompose, MetaAccess, Result, WriteInteg};
+    use crate::{BlockMeta, BoxInIoErr, Decompose, MetaAccess, Result, WriteInteg};
 
     /// A struct which wraps a stream and which writes a trailing data structure to it when flushed.
     pub struct Trailered<T, D> {
@@ -104,7 +103,7 @@ mod private {
     }
 
     impl<T: WriteInteg + Seek, D: Serialize> Trailered<T, D> {
-        pub(crate) fn flush_integ(&mut self, trailer: &D, integrity: &[u8]) -> io::Result<()> {
+        pub fn flush_integ(&mut self, trailer: &D, integrity: &[u8]) -> io::Result<()> {
             let prev_pos = self.write_trailer(trailer)?;
             self.inner.flush_integ(integrity)?;
             self.inner.seek(SeekFrom::Start(prev_pos))?;
@@ -141,50 +140,21 @@ mod private {
         }
     }
 
-    impl<T: MetaAccess, D> MetaAccess for Trailered<T, D> {
-        fn meta(&self) -> &crate::BlockMeta {
-            self.inner.meta()
-        }
-
-        fn mut_meta(&mut self) -> &mut crate::BlockMeta {
-            self.inner.mut_meta()
+    impl<T: AsRef<BlockMeta>, D> AsRef<BlockMeta> for Trailered<T, D> {
+        fn as_ref(&self) -> &BlockMeta {
+            self.inner.as_ref()
         }
     }
 
-    impl<T: FileReadWriteVolatile, D> FileReadWriteVolatile for Trailered<T, D> {
-        fn read_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        ) -> io::Result<usize> {
-            self.inner.read_volatile(slice)
-        }
-
-        fn write_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-        ) -> io::Result<usize> {
-            self.inner.write_volatile(slice)
-        }
-
-        fn read_at_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-            offset: u64,
-        ) -> io::Result<usize> {
-            self.inner.read_at_volatile(slice, offset)
-        }
-
-        fn write_at_volatile(
-            &mut self,
-            slice: fuse_backend_rs::file_buf::FileVolatileSlice,
-            offset: u64,
-        ) -> io::Result<usize> {
-            self.inner.write_at_volatile(slice, offset)
+    impl<T: AsMut<BlockMeta>, D> AsMut<BlockMeta> for Trailered<T, D> {
+        fn as_mut(&mut self) -> &mut BlockMeta {
+            self.inner.as_mut()
         }
     }
+
+    impl<T: MetaAccess, D> MetaAccess for Trailered<T, D> {}
 }
 
-#[cfg(feature = "testing")]
 #[cfg(test)]
 mod tests {
     use crate::Decompose;

+ 0 - 4
crates/btweb/README.md

@@ -1,4 +0,0 @@
-## Overview
-This crate implements a web application which runs a node as a child process. The web
-app controls this node via commands send to the child's stdin. This allows us to serve
-an web portal for controlling a node.

+ 13 - 0
crates/swtpm-harness/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "swtpm-harness"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+dbus = { version = "0.9.6" }
+tempdir = { version = "0.3.7" }
+tss-esapi = { version = "7.1.0", features = ["generate-bindings"] }
+log = { version = "0.4.17" }
+nix = { version = "0.25.0" }

+ 258 - 0
crates/swtpm-harness/src/lib.rs

@@ -0,0 +1,258 @@
+use log::error;
+use nix::{
+    sys::signal::{self, Signal},
+    unistd::Pid,
+};
+use std::{
+    error::Error,
+    io,
+    path::PathBuf,
+    process::{Child, Command, ExitStatus, Stdio},
+    str::FromStr,
+    sync::{
+        atomic::{AtomicU16, Ordering},
+        mpsc::{channel, Receiver, TryRecvError},
+    },
+    time::{Duration, SystemTime},
+};
+use tempdir::TempDir;
+use tss_esapi::{
+    tcti_ldr::{TabrmdConfig, TctiNameConf},
+    Context,
+};
+
+pub struct SwtpmHarness {
+    dir: TempDir,
+    state_path: PathBuf,
+    pid_path: PathBuf,
+    tabrmd: Child,
+    tabrmd_config: String,
+}
+
+impl SwtpmHarness {
+    const HOST: &'static str = "127.0.0.1";
+
+    fn dbus_name(port: u16) -> String {
+        let port_str: String = port
+            .to_string()
+            .chars()
+            // Shifting each code point by 17 makes the digits into capital letters.
+            .map(|e| ((e as u8) + 17) as char)
+            .collect();
+        format!("com.intel.tss2.Tabrmd.{port_str}")
+    }
+
+    pub fn new() -> io::Result<SwtpmHarness> {
+        static PORT: AtomicU16 = AtomicU16::new(21901);
+        let port = PORT.fetch_add(2, Ordering::SeqCst);
+        let ctrl_port = port + 1;
+        let dir = TempDir::new(format!("swtpm_harness.{port}").as_str())?;
+        let dir_path = dir.path();
+        let dir_path_display = dir_path.display();
+        let conf_path = dir_path.join("swtpm_setup.conf");
+        let state_path = dir_path.join("state.bt");
+        let pid_path = dir_path.join("swtpm.pid");
+        let dbus_name = Self::dbus_name(port);
+        let addr = Self::HOST;
+        std::fs::write(
+            &conf_path,
+            r#"# Program invoked for creating certificates
+#create_certs_tool= /usr/bin/swtpm_localca
+# Comma-separated list (no spaces) of PCR banks to activate by default
+active_pcr_banks = sha256
+"#,
+        )?;
+        Command::new("swtpm_setup")
+            .stdout(Stdio::null())
+            .args([
+                "--tpm2",
+                "--config",
+                conf_path.to_str().unwrap(),
+                "--tpm-state",
+                format!("{dir_path_display}").as_str(),
+            ])
+            .status()?
+            .success_or_err()?;
+        Command::new("swtpm")
+            .args([
+                "socket",
+                "--daemon",
+                "--tpm2",
+                "--server",
+                format!("type=tcp,port={port},bindaddr={addr}").as_str(),
+                "--ctrl",
+                format!("type=tcp,port={ctrl_port},bindaddr={addr}").as_str(),
+                "--log",
+                format!("file={dir_path_display}/log.txt,level=5").as_str(),
+                "--flags",
+                "not-need-init,startup-clear",
+                "--tpmstate",
+                format!("dir={dir_path_display}").as_str(),
+                "--pid",
+                format!("file={}", pid_path.display()).as_str(),
+            ])
+            .status()?
+            .success_or_err()?;
+        let mut blocker = DbusBlocker::new_session(dbus_name.clone())?;
+        let tabrmd = Command::new("tpm2-abrmd")
+            .args([
+                format!("--tcti=swtpm:host=127.0.0.1,port={port}").as_str(),
+                "--dbus-name",
+                dbus_name.as_str(),
+                "--session",
+            ])
+            .spawn()?;
+        blocker.block(Duration::from_secs(5))?;
+        Ok(SwtpmHarness {
+            dir,
+            state_path,
+            pid_path,
+            tabrmd,
+            tabrmd_config: format!("bus_name={},bus_type=session", Self::dbus_name(port)),
+        })
+    }
+
+    pub fn tabrmd_config(&self) -> &str {
+        &self.tabrmd_config
+    }
+
+    pub fn context(&self) -> io::Result<Context> {
+        let config = TabrmdConfig::from_str(self.tabrmd_config()).box_err()?;
+        Context::new(TctiNameConf::Tabrmd(config)).box_err()
+    }
+
+    pub fn dir_path(&self) -> &std::path::Path {
+        self.dir.path()
+    }
+
+    pub fn state_path(&self) -> &std::path::Path {
+        &self.state_path
+    }
+}
+
+impl Drop for SwtpmHarness {
+    fn drop(&mut self) {
+        if let Err(err) = self.tabrmd.kill() {
+            error!("failed to kill tpm2-abrmd: {err}");
+        }
+        let pid_str = std::fs::read_to_string(&self.pid_path).unwrap();
+        let pid_int = pid_str.parse::<i32>().unwrap();
+        let pid = Pid::from_raw(pid_int);
+        signal::kill(pid, Signal::SIGKILL).unwrap();
+    }
+}
+
+trait ExitStatusExt {
+    fn success_or_err(&self) -> io::Result<()>;
+}
+
+impl ExitStatusExt for ExitStatus {
+    fn success_or_err(&self) -> io::Result<()> {
+        match self.code() {
+            Some(0) => Ok(()),
+            Some(code) => Err(io::Error::new(
+                io::ErrorKind::Other,
+                format!("ExitCode was non-zero: {code}"),
+            )),
+            None => Err(io::Error::new(io::ErrorKind::Other, "ExitCode was None")),
+        }
+    }
+}
+
+/// A DBus message which is sent when the ownership of a name changes.
+struct NameOwnerChanged {
+    name: String,
+    old_owner: String,
+    new_owner: String,
+}
+
+impl dbus::arg::AppendAll for NameOwnerChanged {
+    fn append(&self, iter: &mut dbus::arg::IterAppend) {
+        dbus::arg::RefArg::append(&self.name, iter);
+        dbus::arg::RefArg::append(&self.old_owner, iter);
+        dbus::arg::RefArg::append(&self.new_owner, iter);
+    }
+}
+
+impl dbus::arg::ReadAll for NameOwnerChanged {
+    fn read(iter: &mut dbus::arg::Iter) -> std::result::Result<Self, dbus::arg::TypeMismatchError> {
+        Ok(NameOwnerChanged {
+            name: iter.read()?,
+            old_owner: iter.read()?,
+            new_owner: iter.read()?,
+        })
+    }
+}
+
+impl dbus::message::SignalArgs for NameOwnerChanged {
+    const NAME: &'static str = "NameOwnerChanged";
+    const INTERFACE: &'static str = "org.freedesktop.DBus";
+}
+
+/// A struct used to block until a specific name appears on DBus.
+struct DbusBlocker {
+    receiver: Receiver<()>,
+    conn: dbus::blocking::Connection,
+}
+
+impl DbusBlocker {
+    fn new_session(name: String) -> io::Result<DbusBlocker> {
+        use dbus::{blocking::Connection, Message};
+        const DEST: &str = "org.freedesktop.DBus";
+
+        let (sender, receiver) = channel();
+        let conn = Connection::new_session().box_err()?;
+        let proxy = conn.with_proxy(DEST, "/org/freedesktop/DBus", Duration::from_secs(1));
+        let _ = proxy.match_signal(move |h: NameOwnerChanged, _: &Connection, _: &Message| {
+            let name_appeared = h.name == name;
+            if name_appeared {
+                if let Err(err) = sender.send(()) {
+                    error!("failed to send unblocking signal: {err}");
+                }
+            }
+            // This local variable exists to help clarify the logic.
+            #[allow(clippy::let_and_return)]
+            let remove_match = !name_appeared;
+            remove_match
+        });
+        Ok(DbusBlocker { receiver, conn })
+    }
+
+    fn block(&mut self, timeout: Duration) -> io::Result<()> {
+        let time_limit = SystemTime::now() + timeout;
+        loop {
+            self.conn.process(Duration::from_millis(100)).box_err()?;
+            match self.receiver.try_recv() {
+                Ok(_) => break,
+                Err(err) => match err {
+                    TryRecvError::Empty => (),
+                    _ => return Err(io::Error::custom(err)),
+                },
+            }
+            if SystemTime::now() > time_limit {
+                return Err(io::Error::new(
+                    io::ErrorKind::TimedOut,
+                    "timed out waiting for DBUS message",
+                ));
+            }
+        }
+        Ok(())
+    }
+}
+
+trait IoErrorExt {
+    fn custom<E: Into<Box<dyn Error + Send + Sync>>>(err: E) -> io::Error {
+        io::Error::new(io::ErrorKind::Other, err)
+    }
+}
+
+impl IoErrorExt for io::Error {}
+
+trait ResultExt<T, E> {
+    fn box_err(self) -> Result<T, io::Error>;
+}
+impl<T, E: Into<Box<dyn Error + Send + Sync>>> ResultExt<T, E> for Result<T, E> {
+    fn box_err(self) -> Result<T, io::Error> {
+        self.map_err(io::Error::custom)
+    }
+}