Browse Source

Reorganized the repository and created a cargo workspace for all crates.

Matthew Carr 2 years ago
parent
commit
2b0b271e94

+ 18 - 7
.vscode/launch.json

@@ -5,13 +5,24 @@
     "version": "0.2.0",
     "configurations": [
         {
-            "name": "Python: Current File",
-            "type": "python",
+            "type": "lldb",
             "request": "launch",
-            "program": "${file}",
-            "console": "integratedTerminal",
-            "justMyCode": true,
-            "args": []
+            "name": "Debug binary",
+            "cargo": {
+                "args": [ "build" ]
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        },
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug unit tests",
+            "cargo": {
+                "args": [ "test" ]
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
         }
     ]
-}
+}

+ 0 - 0
crates/.vscode/settings.json → .vscode/settings.json


+ 0 - 0
crates/.vscode/tasks.json → .vscode/tasks.json


+ 94 - 52
crates/btnode/Cargo.lock → Cargo.lock

@@ -4,24 +4,24 @@ version = 3
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.18"
+version = "0.7.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "alloc-no-stdlib"
-version = "2.0.3"
+version = "2.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
 
 [[package]]
 name = "alloc-stdlib"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
 dependencies = [
  "alloc-no-stdlib",
 ]
@@ -124,11 +124,12 @@ dependencies = [
 ]
 
 [[package]]
-name = "btnode"
+name = "btlib"
 version = "0.1.0"
 dependencies = [
  "base64-url",
  "brotli",
+ "btserde",
  "ctor",
  "env_logger",
  "foreign-types",
@@ -138,7 +139,6 @@ dependencies = [
  "openssl",
  "serde",
  "serde-big-array",
- "serde-block-tree",
  "static_assertions",
  "strum",
  "strum_macros",
@@ -148,6 +148,25 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "btnode"
+version = "0.1.0"
+dependencies = [
+ "btlib",
+ "btserde",
+ "env_logger",
+ "harness",
+ "log",
+]
+
+[[package]]
+name = "btserde"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde-big-array",
+]
+
 [[package]]
 name = "cc"
 version = "1.0.73"
@@ -233,9 +252,9 @@ dependencies = [
 
 [[package]]
 name = "env_logger"
-version = "0.9.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272"
 dependencies = [
  "atty",
  "humantime",
@@ -275,9 +294,11 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
 name = "harness"
 version = "0.0.1"
 dependencies = [
+ "btserde",
+ "ctor",
+ "env_logger",
  "log",
  "serde",
- "serde-block-tree",
 ]
 
 [[package]]
@@ -321,9 +342,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 
 [[package]]
 name = "libc"
-version = "0.2.132"
+version = "0.2.133"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
+checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
 
 [[package]]
 name = "libloading"
@@ -431,9 +452,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.13.0"
+version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
+checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
 
 [[package]]
 name = "openssl"
@@ -492,10 +513,11 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
 
 [[package]]
 name = "pest"
-version = "2.1.3"
+version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048"
 dependencies = [
+ "thiserror",
  "ucd-trie",
 ]
 
@@ -548,18 +570,18 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.40"
+version = "1.0.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
+checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.20"
+version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
 dependencies = [
  "proc-macro2",
 ]
@@ -644,9 +666,9 @@ dependencies = [
 
 [[package]]
 name = "rustversion"
-version = "1.0.7"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
+checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
 
 [[package]]
 name = "semver"
@@ -668,9 +690,9 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.139"
+version = "1.0.144"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6"
+checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
 dependencies = [
  "serde_derive",
 ]
@@ -684,28 +706,20 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "serde-block-tree"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde-big-array",
-]
-
 [[package]]
 name = "serde_bytes"
-version = "0.11.6"
+version = "0.11.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54"
+checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b"
 dependencies = [
  "serde",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.139"
+version = "1.0.144"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb"
+checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -747,9 +761,9 @@ dependencies = [
 
 [[package]]
 name = "strum_macros"
-version = "0.24.2"
+version = "0.24.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b"
+checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -760,9 +774,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.98"
+version = "1.0.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
+checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -806,6 +820,14 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "test-harness"
+version = "0.1.0"
+dependencies = [
+ "btserde",
+ "harness",
+]
+
 [[package]]
 name = "textwrap"
 version = "0.11.0"
@@ -815,6 +837,26 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "thiserror"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "tss-esapi"
 version = "7.1.0"
@@ -850,27 +892,27 @@ dependencies = [
 
 [[package]]
 name = "ucd-trie"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c"
+checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.1"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
+checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.9"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
 [[package]]
 name = "unicode-xid"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
 
 [[package]]
 name = "vcpkg"
@@ -886,13 +928,13 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
 
 [[package]]
 name = "which"
-version = "4.2.5"
+version = "4.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
+checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
 dependencies = [
  "either",
- "lazy_static",
  "libc",
+ "once_cell",
 ]
 
 [[package]]

+ 8 - 0
Cargo.toml

@@ -0,0 +1,8 @@
+[workspace]
+members = [
+    "crates/btlib",
+    "crates/btnode",
+    "crates/btserde",
+    "crates/harness",
+    "crates/test-harness"
+]

+ 0 - 28
crates/.vscode/launch.json

@@ -1,28 +0,0 @@
-{
-    // Use IntelliSense to learn about possible attributes.
-    // Hover to view descriptions of existing attributes.
-    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
-    "version": "0.2.0",
-    "configurations": [
-        {
-            "type": "lldb",
-            "request": "launch",
-            "name": "Debug binary",
-            "cargo": {
-                "args": [ "build" ]
-            },
-            "args": [],
-            "cwd": "${workspaceFolder}"
-        },
-        {
-            "type": "lldb",
-            "request": "launch",
-            "name": "Debug unit tests",
-            "cargo": {
-                "args": [ "test" ]
-            },
-            "args": [],
-            "cwd": "${workspaceFolder}"
-        }
-    ]
-}

+ 0 - 0
crates/sandbox/README.md → crates/btapp/README.md


+ 30 - 0
crates/btlib/Cargo.toml

@@ -0,0 +1,30 @@
+[package]
+name = "btlib"
+version = "0.1.0"
+authors = ["Matthew Carr <mdcarr941@gmail.com>"]
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+btserde = { path = "../btserde" }
+harness = { path = "../harness" }
+serde = { version = "^1.0.136", features = ["derive"] }
+serde-big-array = { version = "^0.4.1" }
+openssl = { version = "^0.10.38", features = ["vendored"] }
+base64-url = { version = "^1.4.13" }
+strum = { version = "^0.24.0", features = ["derive"] }
+strum_macros = { version = "^0.24.0" }
+log = "0.4.17"
+tss-esapi = { version = "7.1.0", features = ["generate-bindings"] }
+tss-esapi-sys = "0.3.0"
+foreign-types = "0.3.1"
+zeroize = { version = "1.5.7", features = ["zeroize_derive"] }
+static_assertions = "1.1.0"
+brotli = "3.3.4"
+
+[dev-dependencies]
+tempdir = "0.3.7"
+ctor = "0.1.22"
+nix = "0.25.0"
+env_logger = "0.9.0"

+ 0 - 0
crates/btnode/README.md → crates/btlib/README.md


+ 0 - 0
crates/btnode/scripts/swtpm-localca.conf → crates/btlib/scripts/swtpm-localca.conf


+ 0 - 0
crates/btnode/scripts/swtpm-localca.options → crates/btlib/scripts/swtpm-localca.options


+ 0 - 0
crates/btnode/scripts/swtpm.sh → crates/btlib/scripts/swtpm.sh


+ 0 - 0
crates/btnode/scripts/swtpm_setup.conf → crates/btlib/scripts/swtpm_setup.conf


+ 4 - 4
crates/btnode/src/crypto/mod.rs → crates/btlib/src/crypto/mod.rs

@@ -23,7 +23,7 @@ use serde::{
     de::{self, DeserializeOwned, Deserializer, SeqAccess, Visitor},
     ser::{SerializeStruct, Serializer},
 };
-use serde_block_tree::{self, from_vec, to_vec, write_to};
+use btserde::{self, from_vec, to_vec, write_to};
 use std::{
     convert::Infallible,
     io::{ErrorKind, SeekFrom},
@@ -60,7 +60,7 @@ pub enum Error {
     HashCmpFailure,
     RootHashNotVerified,
     WritecapAuthzErr(WritecapAuthzErr),
-    Serde(serde_block_tree::Error),
+    Serde(btserde::Error),
     Io(std::io::Error),
     Custom(Box<dyn std::fmt::Debug + Send + Sync>),
 }
@@ -123,8 +123,8 @@ impl From<ErrorStack> for Error {
     }
 }
 
-impl From<serde_block_tree::Error> for Error {
-    fn from(error: serde_block_tree::Error) -> Error {
+impl From<btserde::Error> for Error {
+    fn from(error: btserde::Error) -> Error {
         Error::Serde(error)
     }
 }

+ 1 - 1
crates/btnode/src/crypto/tpm.rs → crates/btlib/src/crypto/tpm.rs

@@ -8,7 +8,7 @@ use openssl::{
     pkcs5::pbkdf2_hmac,
 };
 use serde::ser;
-use serde_block_tree::read_from;
+use btserde::read_from;
 use std::{
     ffi::CStr,
     fs::OpenOptions,

+ 1212 - 0
crates/btlib/src/lib.rs

@@ -0,0 +1,1212 @@
+// The dead code warnings create too much noise during wire-framing.
+// TODO: Delete this prior to release.
+#![allow(dead_code)]
+
+#[cfg(test)]
+mod test_helpers;
+
+#[cfg(test)]
+mod serde_tests;
+
+#[macro_use]
+extern crate static_assertions;
+
+use brotli::{CompressorWriter, Decompressor};
+use btserde::{self, read_from, write_to};
+mod crypto;
+use crypto::{AsymKeyPub, Cryptotext, Hash, HashKind, Sign, Signature, SymKey};
+
+use log::error;
+use serde::{Deserialize, Serialize};
+use serde_big_array::BigArray;
+use std::{
+    collections::HashMap,
+    convert::{Infallible, TryFrom},
+    fmt::{self, Display, Formatter},
+    fs::{File, OpenOptions},
+    hash::Hash as Hashable,
+    io::{self, Read, Seek, SeekFrom, Write},
+    ops::{Add, Sub},
+    time::{Duration, SystemTime},
+};
+
+#[derive(Debug)]
+enum Error {
+    Io(std::io::Error),
+    Serde(btserde::Error),
+    Crypto(crypto::Error),
+    IncorrectSize { expected: usize, actual: usize },
+    Custom(Box<dyn std::fmt::Debug + Send + Sync>),
+}
+
+impl Error {
+    fn custom<E: std::fmt::Debug + Send + Sync + 'static>(err: E) -> Error {
+        Error::Custom(Box::new(err))
+    }
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match self {
+            Error::Io(err) => err.fmt(f),
+            Error::Serde(err) => err.fmt(f),
+            Error::Crypto(err) => err.fmt(f),
+            Error::IncorrectSize { expected, actual } => {
+                write!(f, "incorrect size {}, expected {}", actual, expected)
+            }
+            Error::Custom(err) => err.fmt(f),
+        }
+    }
+}
+
+impl std::error::Error for Error {}
+
+impl From<std::io::Error> for Error {
+    fn from(err: std::io::Error) -> Self {
+        Error::Io(err)
+    }
+}
+
+impl From<Error> for std::io::Error {
+    fn from(err: Error) -> Self {
+        io::Error::new(io::ErrorKind::Other, err)
+    }
+}
+
+impl From<btserde::Error> for Error {
+    fn from(err: btserde::Error) -> Self {
+        Error::Serde(err)
+    }
+}
+
+impl From<crypto::Error> for Error {
+    fn from(err: crypto::Error) -> Self {
+        Error::Crypto(err)
+    }
+}
+
+impl From<std::num::TryFromIntError> for Error {
+    fn from(err: std::num::TryFromIntError) -> Self {
+        Error::custom(err)
+    }
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+/// A Block tagged with its version number. When a block of a previous version is received over
+/// the network or read from the filesystem, it is upgraded to the current version before being
+/// processed.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+enum VersionedBlock<T> {
+    V0(Block<T>),
+}
+
+const SECTOR_SZ_DEFAULT: usize = 4096;
+
+// A trait for streams which only allow reads and writes in fixed sized units called sectors.
+trait Sectored {
+    // Returns the size of the sector for this stream.
+    fn sector_sz(&self) -> usize;
+
+    fn assert_sector_sz(&self, actual: usize) -> Result<()> {
+        let expected = self.sector_sz();
+        if expected == actual {
+            Ok(())
+        } else {
+            Err(Error::IncorrectSize { expected, actual })
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
+pub struct Header {
+    path: Path,
+    readcaps: HashMap<Principal, Cryptotext<SymKey>>,
+    writecap: Writecap,
+    merkle_root: Hash,
+}
+
+/// A container which binds together ciphertext along with the metadata needed to identify,
+/// verify and decrypt it.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+struct Block<T> {
+    header: Header,
+    sig: Signature,
+    body: T,
+}
+
+impl<T> Block<T> {
+    fn try_compose_body<E: Into<Error>, U: Decompose<T>, V: TryCompose<T, U, Error = E>>(
+        self,
+        new_body: V,
+    ) -> Result<Block<U>> {
+        Ok(Block {
+            header: self.header,
+            sig: self.sig,
+            body: new_body.try_compose(self.body).map_err(|err| err.into())?,
+        })
+    }
+
+    fn compose_body<U: Decompose<T>, V: Compose<T, U>>(self, new_body: V) -> Block<U> {
+        Block {
+            header: self.header,
+            sig: self.sig,
+            body: new_body.compose(self.body),
+        }
+    }
+}
+
+impl Block<File> {
+    fn new<P: AsRef<std::path::Path>>(path: P) -> Result<Block<File>> {
+        let mut file = OpenOptions::new().read(true).write(true).open(path)?;
+        let header: Header = read_from(&mut file)?;
+        let sig: Signature = read_from(&mut file)?;
+        crypto::verify_header(&header, &sig)?;
+        Ok(Block {
+            header,
+            sig,
+            body: file,
+        })
+    }
+}
+
+impl<T: Write> Write for Block<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.body.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.body.flush()
+    }
+}
+
+impl<T: Read> Read for Block<T> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.body.read(buf)
+    }
+}
+
+impl<T: Seek> Seek for Block<T> {
+    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
+        self.body.seek(pos)
+    }
+}
+
+trait Decompose<T> {
+    fn into_inner(self) -> T;
+}
+
+trait TryCompose<T, U: Decompose<T>> {
+    type Error;
+    fn try_compose(self, inner: T) -> std::result::Result<U, Self::Error>;
+}
+
+trait Compose<T, U> {
+    fn compose(self, inner: T) -> U;
+}
+
+impl<T, U: Decompose<T>, S: TryCompose<T, U, Error = Infallible>> Compose<T, U> for S {
+    fn compose(self, inner: T) -> U {
+        let result = self.try_compose(inner);
+        // Safety: Infallible has no values, so `result` must be `Ok`.
+        unsafe { result.unwrap_unchecked() }
+    }
+}
+
+/// Extensions to the `Read` trait.
+trait ReadExt: Read {
+    /// Reads repeatedly until one of the following occur:
+    ///  1. The given buffer is full.
+    ///  2. A call to `read` returns 0.
+    ///  3. A call to `read` returns an error.
+    /// The number of bytes read is returned. If an error is returned, then no bytes were read.
+    fn fill_buf(&mut self, mut dest: &mut [u8]) -> io::Result<usize> {
+        let dest_len_start = dest.len();
+        while !dest.is_empty() {
+            let byte_ct = match self.read(dest) {
+                Ok(byte_ct) => byte_ct,
+                Err(err) => {
+                    if dest_len_start == dest.len() {
+                        return Err(err);
+                    } else {
+                        // We're not allowed to return an error if we've already read from self.
+                        error!("an error occurred in fill_buf: {}", err);
+                        break;
+                    }
+                }
+            };
+            if 0 == byte_ct {
+                break;
+            }
+            dest = &mut dest[byte_ct..];
+        }
+        Ok(dest_len_start - dest.len())
+    }
+}
+
+impl<T: Read> ReadExt for T {}
+
+impl<T: Write> Decompose<T> for CompressorWriter<T> {
+    fn into_inner(self) -> T {
+        self.into_inner()
+    }
+}
+
+impl<T: Read> Decompose<T> for Decompressor<T> {
+    fn into_inner(self) -> T {
+        self.into_inner()
+    }
+}
+
+#[derive(Clone)]
+struct BrotliParams {
+    buf_sz: usize,
+    quality: u32,
+    window_sz: u32,
+}
+
+impl BrotliParams {
+    fn new(buf_sz: usize, quality: u32, window_sz: u32) -> BrotliParams {
+        BrotliParams {
+            buf_sz,
+            quality,
+            window_sz,
+        }
+    }
+}
+
+impl<T: Write> TryCompose<T, CompressorWriter<T>> for BrotliParams {
+    type Error = Error;
+    fn try_compose(self, inner: T) -> Result<CompressorWriter<T>> {
+        Ok(CompressorWriter::new(
+            inner,
+            self.buf_sz,
+            self.quality,
+            self.window_sz,
+        ))
+    }
+}
+
+impl<T: Read> TryCompose<T, Decompressor<T>> for BrotliParams {
+    type Error = Error;
+    fn try_compose(self, inner: T) -> Result<Decompressor<T>> {
+        Ok(Decompressor::new(inner, self.buf_sz))
+    }
+}
+
+/// TODO: Remove this once the error_chain crate is integrated.
+fn err_conv<T, E: std::error::Error + Send + Sync + 'static>(
+    result: std::result::Result<T, E>,
+) -> std::result::Result<T, io::Error> {
+    result.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
+}
+
+/// A stream which buffers writes and read such that the inner stream only sees reads and writes
+/// of sector length buffers.
+struct SectoredBuf<T> {
+    inner: T,
+    buf: Vec<u8>,
+    /// The offset into the inner stream which the zero offset byte in `buf` corresponds to.
+    buf_start: usize,
+    /// Indicates if the contents of `buf` have been written to, and so whether `buf` needs to be
+    /// written back to `inner` before it is refilled.
+    dirty: bool,
+    /// The total number of bytes that have been written to the inner stream, including the reserved
+    /// bytes at the beginning.
+    len: usize,
+    /// The current position of this stream, expressed as an offset into the inner stream.
+    pos: usize,
+}
+
+impl SectoredBuf<()> {
+    fn new() -> SectoredBuf<()> {
+        SectoredBuf {
+            inner: (),
+            buf: Vec::new(),
+            buf_start: 0,
+            dirty: false,
+            len: 0,
+            pos: 0,
+        }
+    }
+}
+
+impl<T> SectoredBuf<T> {
+    /// The number of bytes at the beginning of the inner stream which are reserved to store the
+    /// length of data written. All offsets into the stored data must be shifted by this amount to
+    /// be translated to an offset in the inner stream.
+    const RESERVED: usize = std::mem::size_of::<usize>();
+
+    /// Returns the position in the inner stream which the given position in this stream corresponds
+    /// to.
+    fn inner_pos(self_pos: u64) -> u64 {
+        let offset: u64 = Self::RESERVED.try_into().unwrap();
+        self_pos + offset
+    }
+
+    /// Returns the position in this stream which the given position in the inner stream corresponds
+    /// to.
+    fn self_pos(inner_pos: u64) -> u64 {
+        let offset: u64 = Self::RESERVED.try_into().unwrap();
+        inner_pos - offset
+    }
+
+    /// Returns the offset into the internal buffer that corresponds to the current position.
+    fn buf_pos(&self) -> usize {
+        self.pos - self.buf_start
+    }
+
+    /// Returns one more than the last index in the internal buffer which can be read.
+    fn buf_end(&self) -> usize {
+        let limit = self.len.min(self.buf_start + self.sector_sz());
+        limit - self.buf_start
+    }
+
+    /// Returns the index of the sector which is currently loaded into the buffer.
+    fn buf_sector_index(&self) -> usize {
+        self.pos / self.sector_sz()
+    }
+}
+
+impl<T: Read + Seek> SectoredBuf<T> {
+    /// Fills the internal buffer by reading from the inner stream at the current position
+    /// and updates `self.buf_start` with the position read from.
+    fn fill_internal_buf(&mut self) -> io::Result<usize> {
+        self.buf_start = err_conv(self.inner.stream_position()?.try_into())?;
+        let read_bytes = if self.buf_start < self.len {
+            let read_bytes = self.inner.fill_buf(&mut self.buf)?;
+            if read_bytes < self.buf.len() {
+                return Err(io::Error::new(
+                    io::ErrorKind::Other,
+                    format!(
+                        "Failed to fill SectoredBuf.buf. Expected {} bytes, got {}.",
+                        self.buf.len(),
+                        read_bytes
+                    ),
+                ));
+            }
+            read_bytes
+        } else {
+            0
+        };
+        Ok(read_bytes)
+    }
+}
+
+impl<T> Decompose<T> for SectoredBuf<T> {
+    fn into_inner(self) -> T {
+        self.inner
+    }
+}
+
+impl<T: Sectored + Read + Seek> TryCompose<T, SectoredBuf<T>> for SectoredBuf<()> {
+    type Error = Error;
+    fn try_compose(self, inner: T) -> Result<SectoredBuf<T>> {
+        let sect_sz = inner.sector_sz();
+        if sect_sz < Self::RESERVED {
+            return Err(Error::custom(format!(
+                "a sector size of at least {} is required. Got {}",
+                Self::RESERVED,
+                sect_sz,
+            )));
+        }
+        let mut sectored = SectoredBuf {
+            inner,
+            buf: self.buf,
+            buf_start: 0,
+            dirty: false,
+            len: Self::RESERVED,
+            pos: Self::RESERVED,
+        };
+        sectored.inner.seek(SeekFrom::Start(0))?;
+        sectored.buf.resize(sect_sz, 0);
+        let len_stored = match sectored.fill_internal_buf() {
+            Ok(bytes_read) => bytes_read >= Self::RESERVED,
+            Err(err) => {
+                error!("SectoredBuf::fill_internal_buf returned an error: {}", err);
+                false
+            }
+        };
+        if len_stored {
+            if let Ok(len) = read_from::<u64, _>(&mut sectored.buf.as_slice()) {
+                sectored.len = len.try_into()?;
+            }
+        } else {
+            write_to(&Self::RESERVED, &mut sectored.buf.as_mut_slice())?;
+            sectored.dirty = true;
+        }
+        Ok(sectored)
+    }
+}
+
+impl<T> Sectored for SectoredBuf<T> {
+    fn sector_sz(&self) -> usize {
+        self.buf.len()
+    }
+}
+
+impl<T: Seek + Read + Write> Write for SectoredBuf<T> {
+    fn write(&mut self, mut src: &[u8]) -> io::Result<usize> {
+        let src_len_start = src.len();
+        let mut dest = {
+            let buf_pos = self.buf_pos();
+            &mut self.buf[buf_pos..]
+        };
+        while !src.is_empty() {
+            if dest.is_empty() {
+                if let Err(err) = self.flush() {
+                    error!("A call to SectoredBuf::flush returned an error: {}", err);
+                    break;
+                }
+                dest = &mut self.buf[..];
+            }
+            let sz = src.len().min(dest.len());
+            (&mut dest[..sz]).copy_from_slice(&src[..sz]);
+            dest = &mut dest[sz..];
+            src = &src[sz..];
+            self.dirty = sz > 0;
+            self.pos += sz;
+        }
+        Ok(src_len_start - src.len())
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        if !self.dirty {
+            return Ok(());
+        }
+
+        // Write out the contents of the buffer.
+        let sect_sz: u64 = err_conv(self.sector_sz().try_into())?;
+        let inner_pos = self.inner.stream_position()?;
+        let inner_pos_usize: usize = err_conv(inner_pos.try_into())?;
+        let is_new_sector = self.pos > inner_pos_usize;
+        let is_full = (self.buf.len() - self.buf_pos()) == 0;
+        let seek_to = if is_new_sector {
+            if is_full {
+                inner_pos + sect_sz
+            } else {
+                inner_pos
+            }
+        } else {
+            // The contents of the buffer were previously read from inner, so we write the
+            // updated contents to the same offset.
+            let sect_start: u64 = err_conv(self.buf_start.try_into())?;
+            self.inner.seek(SeekFrom::Start(sect_start))?;
+            if is_full {
+                inner_pos
+            } else {
+                inner_pos - sect_sz
+            }
+        };
+        self.inner.write_all(&self.buf)?;
+
+        // Update the stored length.
+        self.len = self.len.max(self.pos);
+        self.inner.seek(SeekFrom::Start(0))?;
+        self.fill_internal_buf()?;
+        let len: u64 = err_conv(self.len.try_into())?;
+        err_conv(write_to(&len, &mut self.buf.as_mut_slice()))?;
+        self.inner.seek(SeekFrom::Start(0))?;
+        self.inner.write_all(&self.buf)?;
+
+        // Seek to the next position.
+        self.inner.seek(SeekFrom::Start(seek_to))?;
+        self.fill_internal_buf()?;
+        self.dirty = false;
+
+        Ok(())
+    }
+}
+
+impl<T: Read + Seek> Read for SectoredBuf<T> {
+    fn read(&mut self, mut dest: &mut [u8]) -> io::Result<usize> {
+        if self.pos == self.len {
+            return Ok(0);
+        }
+
+        let dest_len_start = dest.len();
+        let mut src = {
+            let start = self.buf_pos();
+            let end = self.buf_end();
+            &self.buf[start..end]
+        };
+        while !dest.is_empty() {
+            if src.is_empty() {
+                if self.pos >= self.len {
+                    break;
+                }
+                let byte_ct = match self.fill_internal_buf() {
+                    Ok(byte_ct) => byte_ct,
+                    Err(err) => {
+                        error!("SectoredBuf::full_internal_buf returned an error: {}", err);
+                        break;
+                    }
+                };
+                if 0 == byte_ct {
+                    break;
+                }
+                src = &self.buf[..byte_ct];
+            }
+            let sz = src.len().min(dest.len());
+            (&mut dest[..sz]).copy_from_slice(&src[..sz]);
+            dest = &mut dest[sz..];
+            src = &src[sz..];
+            self.pos += sz;
+        }
+        Ok(dest_len_start - dest.len())
+    }
+}
+
+impl<T: Seek + Read + Write> Seek for SectoredBuf<T> {
+    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
+        let inner_pos = self.inner.stream_position()?;
+        let inner_pos_new = match pos {
+            SeekFrom::Start(rel_start) => Self::inner_pos(rel_start),
+            SeekFrom::Current(rel_curr) => {
+                if rel_curr > 0 {
+                    inner_pos + rel_curr as u64
+                } else {
+                    inner_pos - rel_curr as u64
+                }
+            }
+            SeekFrom::End(_) => {
+                return Err(io::Error::new(
+                    io::ErrorKind::Unsupported,
+                    "seeking relative to the end of the stream is not supported",
+                ))
+            }
+        };
+        let sect_sz = self.sector_sz();
+        let sect_index = self.buf_sector_index();
+        let sect_index_new = err_conv(TryInto::<usize>::try_into(inner_pos_new))? / sect_sz;
+        let pos: u64 = err_conv(self.pos.try_into())?;
+        if sect_index != sect_index_new || pos == inner_pos {
+            self.flush()?;
+            let seek_to: u64 = err_conv((sect_index_new * sect_sz).try_into())?;
+            self.inner.seek(SeekFrom::Start(seek_to))?;
+            self.fill_internal_buf()?;
+        }
+        self.pos = err_conv(inner_pos_new.try_into())?;
+        Ok(Self::self_pos(inner_pos_new))
+    }
+}
+
+/// An envelopment of a key, which is tagged with the principal who the key is meant for.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+struct Readcap {
+    /// The principal this `Readcap` was issued to.
+    issued_to: Principal,
+    /// An encipherment of a block key using the public key of the principal.
+    key: Cryptotext<SymKey>,
+}
+
+impl Readcap {
+    fn new(issued_to: Hash, key: Cryptotext<SymKey>) -> Readcap {
+        Readcap {
+            issued_to: Principal(issued_to),
+            key,
+        }
+    }
+}
+
+/// Verifies that a principal is authorized to write blocks in a tree.
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+struct Writecap {
+    /// The principal this `Writecap` was issued to.
+    issued_to: Principal,
+    /// The path where this write caps's validity begins.
+    path: Path,
+    /// The point in time after which this write cap is no longer valid.
+    expires: Epoch,
+    /// The public key used to sign this write cap.
+    signing_key: AsymKeyPub<Sign>,
+    /// A digital signature which covers all of the fields in the write cap except for next.
+    signature: Signature,
+    /// The next write cap in the chain leading back to the root.
+    next: Option<Box<Writecap>>,
+}
+
+/// Fragments are created from blocks using Erasure Encoding and stored with other nodes in the
+/// network to provide availability and redundancy of data.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+struct Fragment {
+    /// The path to the block this fragment is from.
+    path: Path,
+    /// The serial number of this fragment.
+    serial: FragmentSerial,
+    /// The actual data.
+    body: Vec<u8>,
+}
+
+impl Fragment {
+    /// Create a new fragment with the given fields. If `path_str` cannot be parsed then a failed
+    /// `Result` is returned containing a `PathError`.
+    fn new(
+        path_str: &str,
+        serial_num: u32,
+        body: Vec<u8>,
+    ) -> std::result::Result<Fragment, PathError> {
+        let result = Path::try_from(path_str);
+        Ok(Fragment {
+            path: result?,
+            serial: FragmentSerial(serial_num),
+            body,
+        })
+    }
+}
+
+/// The body of every non-leaf node in a tree contains this data structure.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+struct Directory {
+    /// The nodes that are attached to the tree at this block.
+    nodes: Vec<Principal>,
+    /// This block's descendants.
+    children: HashMap<String, HashMap<FragmentSerial, FragmentRecord>>,
+}
+
+/// Keeps track of which principal is storing a fragment.
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+struct FragmentRecord {
+    /// The fragment serial number this record is for.
+    serial: FragmentSerial,
+    /// The principal who is storing this fragment.
+    stored_by: Principal,
+}
+
+impl FragmentRecord {
+    /// Creates a new `FragmentRecord` whose `serial` and `stored_by` fields are set to
+    /// the given values.
+    fn new(serial: u32, stored_by: Hash) -> FragmentRecord {
+        FragmentRecord {
+            serial: FragmentSerial(serial),
+            stored_by: Principal(stored_by),
+        }
+    }
+}
+
+/// An identifier for a security principal, which is any entity that can be authenticated.
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone)]
+struct Principal(Hash);
+
+impl Principal {
+    fn kind(&self) -> HashKind {
+        HashKind::from(&self.0)
+    }
+}
+
+/// Trait for types which are owned by a `Principal`.
+trait Owned {
+    /// Returns the `Principal` that owns `self`, using the given hash algorithm.
+    fn owner_of_kind(&self, kind: HashKind) -> Principal;
+
+    /// Returns the `Principal` that owns `self`, using the default hash algorithm.
+    fn owner(&self) -> Principal {
+        self.owner_of_kind(HashKind::default())
+    }
+}
+
+/// An identifier for a block in a tree.
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
+struct Path {
+    owner: Principal,
+    components: Vec<String>,
+}
+
+impl Path {
+    /// The character that is used to separate path components.
+    const SEP: char = '/';
+    /// The limit, in bytes, of a `Path`'s length.
+    const BYTE_LIMIT: usize = 4096;
+
+    /// Returns a result which, when successful, contains the index after the last character in the
+    /// current path component.
+    fn component_end<I: Iterator<Item = (usize, char)>>(
+        start: usize,
+        first: char,
+        pairs: &mut I,
+    ) -> std::result::Result<usize, PathError> {
+        if first == Path::SEP {
+            return Err(PathError::EmptyComponent);
+        }
+        let end;
+        let mut last = start;
+        loop {
+            match pairs.next() {
+                Some((index, Path::SEP)) => {
+                    end = index;
+                    break;
+                }
+                Some((index, _)) => last = index,
+                None => {
+                    end = last + 1;
+                    break;
+                }
+            }
+        }
+        if end == start {
+            Err(PathError::EmptyComponent)
+        } else {
+            Ok(end)
+        }
+    }
+
+    /// Asserts that the number of bytes in the given string is no more than `Path::BYTE_LIMIT`.
+    fn assert_not_too_long(string: &str) -> std::result::Result<(), PathError> {
+        let len = string.len();
+        if len > Path::BYTE_LIMIT {
+            return Err(PathError::PathTooLong(len));
+        }
+        Ok(())
+    }
+
+    /// Returns true if `other` is a subpath of this `Path`.
+    fn contains(&self, other: &Path) -> bool {
+        if self.owner != other.owner {
+            return false;
+        };
+        // This path must be no longer than the other path.
+        if self.components.len() > other.components.len() {
+            return false;
+        }
+        // Skip the component containing the owner.
+        let self_iter = self.components.iter().skip(1);
+        let other_iter = other.components.iter().skip(1);
+        for pair in self_iter.zip(other_iter) {
+            if pair.0 != pair.1 {
+                return false;
+            }
+        }
+        true
+    }
+}
+
+impl<'s> TryFrom<&'s str> for Path {
+    type Error = PathError;
+
+    fn try_from(string: &'s str) -> std::result::Result<Path, PathError> {
+        Path::assert_not_too_long(string)?;
+        let mut pairs = string.char_indices();
+        let mut components = Vec::new();
+        let mut last_end = 0;
+        while let Some((start, c)) = pairs.next() {
+            let end = Path::component_end(start, c, &mut pairs)?;
+            last_end = end;
+            let slice = &string[start..end];
+            components.push(slice.to_string());
+        }
+        // An empty component is added to the end to indicate if there was a trailing slash.
+        if string.len() - 1 == last_end {
+            components.push("".to_string());
+        }
+        let leading = components
+            .get(0)
+            .ok_or(PathError::InvalidLeadingComponent)?;
+        let hash =
+            Hash::try_from(leading.as_str()).map_err(|_| PathError::InvalidLeadingComponent)?;
+        Ok(Path {
+            owner: Principal(hash),
+            components,
+        })
+    }
+}
+
+impl Display for Path {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if self.components.is_empty() {
+            return write!(f, "");
+        };
+        let mut iter = self.components.iter();
+        let first = iter.next().unwrap();
+        let mut output = write!(f, "{}", first);
+        for component in iter {
+            output = write!(f, "{}{}", Path::SEP, component)
+        }
+        output
+    }
+}
+
+/// Errors which can occur when converting a string to a `Path`.
+#[derive(Debug, PartialEq)]
+enum PathError {
+    /// Occurs when the number of bytes in a string is greater than `Path::BYTE_LIMIT`.
+    PathTooLong(usize),
+    /// Indicates that a path string was empty.
+    Empty,
+    /// Occurs when a component in a path string was empty.
+    EmptyComponent,
+    /// Occurs when the leading component of a path is not in the correct format.
+    InvalidLeadingComponent,
+}
+
+impl Display for PathError {
+    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
+        match self {
+            PathError::PathTooLong(length) => formatter.write_fmt(format_args!(
+                "path contained {} bytes, which is over the {} byte limit",
+                length,
+                Path::BYTE_LIMIT
+            )),
+            PathError::Empty => formatter.write_str("path was empty"),
+            PathError::EmptyComponent => formatter.write_str("component of path was empty"),
+            PathError::InvalidLeadingComponent => {
+                formatter.write_str("invalid leading path component")
+            }
+        }
+    }
+}
+
+/// An instant in time represented by the number of seconds since January 1st 1970, 00:00:00 UTC.
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
+struct Epoch(u64);
+
+impl Epoch {
+    /// Returns the current epoch time.
+    fn now() -> Epoch {
+        let now = SystemTime::now();
+        // If the system clock is before the unix epoch, just panic.
+        let epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
+        Epoch(epoch.as_secs())
+    }
+}
+
+impl Copy for Epoch {}
+
+impl Add<Duration> for Epoch {
+    type Output = Self;
+    fn add(self, other: Duration) -> Self {
+        Epoch(self.0 + other.as_secs())
+    }
+}
+
+impl Sub<Duration> for Epoch {
+    type Output = Self;
+    fn sub(self, other: Duration) -> Self {
+        Epoch(self.0 - other.as_secs())
+    }
+}
+
+/// The serial number of a block fragment.
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable)]
+struct FragmentSerial(u32);
+
+#[cfg(test)]
+mod tests {
+    use std::io::Cursor;
+
+    use super::*;
+    use test_helpers::*;
+
+    fn path_from_str_test_case(
+        expected: std::result::Result<Path, PathError>,
+        input: &str,
+    ) -> std::result::Result<(), PathError> {
+        let result = Path::try_from(input);
+        assert_eq!(expected, result);
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_multiple_components_ok() -> std::result::Result<(), PathError> {
+        let expected = make_path(vec!["red", "green", "blue"]);
+        let input = format!("{}/red/green/blue", expected.owner.0);
+        path_from_str_test_case(Ok(expected), input.as_str())?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_one_component_ok() -> std::result::Result<(), PathError> {
+        let expected = make_path(vec![]);
+        let input = expected.owner.0.to_string();
+        path_from_str_test_case(Ok(expected), input.as_str())?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_trailing_slash_ok() -> std::result::Result<(), PathError> {
+        // Notice the empty component at the end of this path due to the trailing slash.
+        let expected = make_path(vec!["orange", "banana", "shotgun", ""]);
+        let input = format!("{}/orange/banana/shotgun/", expected.owner.0);
+        path_from_str_test_case(Ok(expected), input.as_str())?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_path_too_long_fail() -> std::result::Result<(), PathError> {
+        let principal = make_principal();
+        let input = format!("{}/{}", principal.0, "*".repeat(4097));
+        let expected = Err(PathError::PathTooLong(input.len()));
+        path_from_str_test_case(expected, input.as_str())?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_multiple_slashes_fail() -> std::result::Result<(), PathError> {
+        let expected = Err(PathError::EmptyComponent);
+        let input = format!("{}//orange", make_principal().0);
+        path_from_str_test_case(expected, input.as_str())?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_from_str_leading_slash_fail() -> std::result::Result<(), PathError> {
+        let expected = Err(PathError::EmptyComponent);
+        let input = format!("/{}/orange/banana/shotgun", make_principal().0);
+        path_from_str_test_case(expected, input.as_str())?;
+        Ok(())
+    }
+
+    #[test]
+    fn path_round_trip() -> std::result::Result<(), PathError> {
+        let expected = make_path(vec!["interstitial", "inter-related", "intersections"]);
+        let actual = Path::try_from(expected.to_string().as_str())?;
+        assert_eq!(expected, actual);
+        Ok(())
+    }
+
+    #[test]
+    fn path_contains_true() {
+        let larger = make_path(vec!["apps"]);
+        let smaller = make_path(vec!["apps", "bohdi"]);
+        assert!(larger.contains(&smaller));
+    }
+
+    #[test]
+    fn path_contains_true_only_owner() {
+        let larger = make_path(vec![]);
+        let smaller = make_path(vec![]);
+        assert!(larger.contains(&smaller));
+    }
+
+    #[test]
+    fn path_contains_false_self_is_longer() {
+        let first = make_path(vec!["apps", "bohdi"]);
+        let second = make_path(vec!["apps"]);
+        assert!(!first.contains(&second));
+    }
+
+    #[test]
+    fn path_contains_false_same_owners() {
+        let first = make_path(vec!["apps"]);
+        let second = make_path(vec!["nodes"]);
+        assert!(!first.contains(&second));
+    }
+
+    #[test]
+    fn path_contains_false_different_owners() {
+        let first = make_path(vec!["apps"]);
+        let mut second = make_path(vec!["apps"]);
+        second.owner = Principal(Hash::Sha2_256(PRINCIPAL2));
+        assert!(!first.contains(&second));
+    }
+
+    #[test]
+    fn brotli_compress_decompress() {
+        const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
+        const SECT_CT: usize = 16;
+        let params = BrotliParams::new(SECT_SZ, 8, 20);
+        let mut memory = Cursor::new([0u8; SECT_SZ * SECT_CT]);
+        {
+            let write: CompressorWriter<_> = params
+                .clone()
+                .try_compose(&mut memory)
+                .expect("compose for write failed");
+            write_fill(write, SECT_SZ, SECT_CT);
+        }
+        memory.seek(SeekFrom::Start(0)).expect("seek failed");
+        {
+            let read: Decompressor<_> = params
+                .try_compose(&mut memory)
+                .expect("compose for read failed");
+            read_check(read, SECT_SZ, SECT_CT);
+        }
+    }
+
+    fn make_sectored_buf(sect_sz: usize, sect_ct: usize) -> SectoredBuf<SectoredCursor<Vec<u8>>> {
+        SectoredBuf::new()
+            .try_compose(SectoredCursor::new(vec![0u8; sect_sz * sect_ct], sect_sz))
+            .expect("compose for sectored buffer failed")
+    }
+
+    #[test]
+    fn sectored_buf_fill_inner() {
+        const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
+        const SECT_CT: usize = 16;
+        let mut sectored = make_sectored_buf(SECT_SZ, SECT_CT);
+        let sect_sz = sectored.sector_sz();
+        assert_eq!(0, sect_sz % 16);
+        let chunk_sz = sect_sz / 16;
+        let chunk_ct = SECT_CT * 16;
+        write_fill(&mut sectored, chunk_sz, chunk_ct);
+    }
+
+    #[test]
+    fn sectored_buf_write_read_sequential() {
+        const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
+        const SECT_CT: usize = 16;
+        let mut sectored = make_sectored_buf(SECT_SZ, SECT_CT);
+        let sect_sz = sectored.sector_sz();
+        assert_eq!(0, sect_sz % 16);
+        let chunk_sz = sect_sz / 16;
+        // We subtract one here so that the underlying buffer is not completely filled. This
+        // exercises the length limiting capability of the sectored buffer.
+        let chunk_ct = SECT_CT * 16 - 1;
+        write_fill(&mut sectored, chunk_sz, chunk_ct);
+        sectored.seek(SeekFrom::Start(0)).expect("seek failed");
+        read_check(&mut sectored, chunk_sz, chunk_ct);
+    }
+
+    #[test]
+    fn sectored_buf_sect_sz_too_small_is_error() {
+        const MIN: usize = SectoredBuf::<()>::RESERVED;
+        let result = SectoredBuf::new().try_compose(SectoredCursor::new([0u8; MIN], MIN - 1));
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn sectored_buf_len_preserved() {
+        const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
+        const SECT_CT: usize = 16;
+        let mut sectored = make_sectored_buf(SECT_SZ, SECT_CT);
+        let expected = vec![42u8; 12];
+        // We need to ensure that writing expected will not fill up the buffer in sectored.
+        assert!(expected.len() < sectored.sector_sz() - SectoredBuf::<()>::RESERVED);
+
+        sectored.write_all(&expected).expect("write failed");
+        sectored.flush().expect("flush failed");
+        let inner = sectored.into_inner();
+        let mut sectored = SectoredBuf::new()
+            .try_compose(inner)
+            .expect("failed to compose sectored buffer");
+        let mut actual = vec![0u8; expected.len()];
+        sectored
+            .fill_buf(actual.as_mut_slice())
+            .expect("failed to fill actual");
+
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn sectored_buf_seek() {
+        let sect_sz = 16usize;
+        let sect_ct = 16usize;
+        let cap = sect_sz * sect_ct - std::mem::size_of::<usize>();
+        let source = {
+            let mut source = Vec::with_capacity(cap);
+            source.extend(
+                std::iter::successors(Some(0u8), |n| if *n <= 254 { Some(*n + 1) } else { None })
+                    .take(cap),
+            );
+            source
+        };
+        let mut sectored = make_sectored_buf(sect_sz, sect_ct);
+        sectored.write(&source).expect("write failed");
+        let mut buf = [0u8; 1];
+        let end = cap.try_into().expect("cap cannot fit into a u8");
+        for pos in (0..end).rev() {
+            sectored
+                .seek(SeekFrom::Start(pos as u64))
+                .expect("seek failed");
+            sectored.read(&mut buf).expect("read failed");
+            assert_eq!(pos, buf[0]);
+        }
+    }
+
+    #[test]
+    fn sectored_buf_write_read_random() {
+        const SECT_SZ: usize = 16;
+        const SECT_CT: usize = 16;
+        const CAP: usize = SECT_SZ * SECT_CT - std::mem::size_of::<usize>();
+        let source = {
+            let mut expected = Vec::with_capacity(CAP);
+            expected.extend(
+                std::iter::successors(Some(0u8), |n| if *n <= 254 { Some(*n + 1) } else { None })
+                    .take(CAP),
+            );
+            expected
+        };
+        let indices: Vec<(usize, usize)> = {
+            let rando = Randomizer::new([3u8; Randomizer::HASH.len()]);
+            let rando2 = Randomizer::new([5u8; Randomizer::HASH.len()]);
+            rando
+                .zip(rando2)
+                .take(SECT_CT)
+                .map(|(mut first, mut second)| {
+                    first %= source.len();
+                    second &= source.len();
+                    let low = first.min(second);
+                    let high = first.max(second);
+                    (low, high)
+                })
+                .collect()
+        };
+
+        let mut sectored = make_sectored_buf(SECT_SZ, SECT_CT);
+        sectored
+            .write_all(&[0u8; CAP])
+            .expect("failed to fill sectored");
+        sectored.flush().expect("flush failed");
+        for (_k, (low, high)) in indices.iter().enumerate() {
+            sectored
+                .seek(SeekFrom::Start(*low as u64))
+                .expect("seek failed");
+            let src = &source[*low..*high];
+            sectored.write(src).expect("write failed");
+        }
+        sectored.flush().expect("flush failed");
+        let mut buf = vec![0u8; CAP];
+        for (_k, (low, high)) in indices.iter().enumerate() {
+            sectored
+                .seek(SeekFrom::Start(*low as u64))
+                .expect("seek failed");
+            let actual = &mut buf[*low..*high];
+            sectored.fill_buf(actual).expect("read failed");
+            let expected = &source[*low..*high];
+            assert_eq!(expected, actual);
+        }
+    }
+
+    #[test]
+    fn sectored_buf_read_past_end() {
+        const LEN: usize = 32;
+        let mut sectored = SectoredBuf::new()
+            .try_compose(SectoredCursor::new([0u8; LEN], LEN))
+            .expect("compose failed");
+        const BUF_LEN: usize = LEN - SectoredBuf::<()>::RESERVED + 1;
+        sectored.write(&[1u8; BUF_LEN - 1]).expect("write failed");
+        sectored.seek(SeekFrom::Start(0)).expect("seek failed");
+        let mut buf = [0u8; BUF_LEN];
+        // Note that buf is one byte longer than the available capacity in the cursor.
+        sectored.read(&mut buf).expect("read failed");
+        assert_eq!(&[1u8; BUF_LEN - 1], &buf[..(BUF_LEN - 1)]);
+        assert_eq!(0u8, buf[BUF_LEN - 1]);
+    }
+
+    /// Tests that the data written in try_compose is actually written back to the underlying stream.
+    #[test]
+    fn sectored_buf_write_back() {
+        let mut sectored = SectoredBuf::new()
+            .try_compose(SectoredCursor::new(vec![0u8; 24], 16))
+            .expect("compose failed");
+        let expected = [1u8; 8];
+        sectored.write(&expected).expect("first write failed");
+        sectored.write(&[2u8; 8]).expect("second write failed");
+        sectored.seek(SeekFrom::Start(0)).expect("seek failed");
+        let mut actual = [0u8; 8];
+        sectored.read(&mut actual).expect("read failed");
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn sectored_buf_write_past_end() {
+        const LEN: usize = 8;
+        let mut sectored = SectoredBuf::new()
+            .try_compose(SectoredCursor::new(vec![0u8; 0], LEN))
+            .expect("compos failed");
+        let expected = [1u8; LEN + 1];
+        sectored.write(&expected).expect("write failed");
+        sectored.seek(SeekFrom::Start(0)).expect("seek failed");
+        let mut actual = [0u8; LEN + 1];
+        sectored.read(&mut actual).expect("read failed");
+        assert_eq!(expected, actual);
+    }
+}

+ 1 - 1
crates/btnode/src/serde_tests.rs → crates/btlib/src/serde_tests.rs

@@ -1,7 +1,7 @@
 /// Tests which ensure that the main data structures can be round-tripped faithfully.
 use super::*;
 use crypto::Hash;
-use serde_block_tree::{from_vec, to_vec, Error, Result};
+use btserde::{from_vec, to_vec, Error, Result};
 use test_helpers::*;
 
 #[test]

+ 1 - 1
crates/btnode/src/test_helpers.rs → crates/btlib/src/test_helpers.rs

@@ -1,7 +1,7 @@
 /// Test data and functions to help with testing.
 use super::*;
 use crypto::*;
-use serde_block_tree::{Error, Result};
+use btserde::{Error, Result};
 use std::{
     cell::RefCell,
     fmt::Write as FmtWrite,

+ 0 - 1
crates/btnode/.vscode

@@ -1 +0,0 @@
-../.vscode

+ 3 - 20
crates/btnode/Cargo.toml

@@ -1,30 +1,13 @@
 [package]
 name = "btnode"
 version = "0.1.0"
-authors = ["Matthew Carr <mdcarr941@gmail.com>"]
 edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-serde-block-tree = { path = "../serde-block-tree" }
+btlib = { path = "../btlib" }
+btserde = { path = "../btserde" }
 harness = { path = "../harness" }
-serde = { version = "^1.0.136", features = ["derive"] }
-serde-big-array = { version = "^0.4.1" }
-openssl = { version = "^0.10.38", features = ["vendored"] }
-base64-url = { version = "^1.4.13" }
-strum = { version = "^0.24.0", features = ["derive"] }
-strum_macros = { version = "^0.24.0" }
 env_logger = "0.9.0"
-log = "0.4.17"
-tss-esapi = { version = "7.1.0", features = ["generate-bindings"] }
-tss-esapi-sys = "0.3.0"
-foreign-types = "0.3.1"
-zeroize = { version = "1.5.7", features = ["zeroize_derive"] }
-static_assertions = "1.1.0"
-brotli = "3.3.4"
-
-[dev-dependencies]
-tempdir = "0.3.7"
-ctor = "0.1.22"
-nix = "0.25.0"
+log = "0.4.17"

+ 0 - 14
crates/btnode/notes.txt

@@ -1,14 +0,0 @@
-Convert EncryptionAlgo, DecryptionAlgo, SignAlgo, VerifyAlgo to traits.
-
-Modify SymKey and AsymKey<T> to hold the state needed to perform these operations, rather than
-just buffers of serialized data, and implement these traits.
-
-Manually implement the Serialize and Deserialize traits for these types.
-
-Split AsymKey<T> into two types, one for private and on for public keys.
-
-The public part should be an enum, AsymKeyPublic, allowing operations to be performed in software.
-
-The private part should be a trait, AsymKeyPrivate, requiring all operations to be deferred to an
-implementation, which may use a hardware device to execute them.
-AsymKeyPrivate = DecryptionAlgo + SignAlgo

+ 3 - 1211
crates/btnode/src/main.rs

@@ -1,895 +1,9 @@
-// The dead code warnings create too much noise during wire-framing.
-// TODO: Delete this prior to release.
-#![allow(dead_code)]
-
-#[cfg(test)]
-mod test_helpers;
-
-#[cfg(test)]
-mod serde_tests;
-
-#[macro_use]
-extern crate static_assertions;
-
-use brotli::{CompressorWriter, Decompressor};
+use btserde::{read_from, write_to};
 use harness::Message;
-use serde_block_tree::{self, read_from, write_to};
-mod crypto;
-use crypto::{AsymKeyPub, Cryptotext, Hash, HashKind, Sign, Signature, SymKey};
-
 use log::{error, info};
-use serde::{Deserialize, Serialize};
-use serde_big_array::BigArray;
-use std::{
-    collections::HashMap,
-    convert::{Infallible, TryFrom},
-    fmt::{self, Display, Formatter},
-    fs::{File, OpenOptions},
-    hash::Hash as Hashable,
-    io::{self, BufWriter, Read, Seek, SeekFrom, Write},
-    ops::{Add, Sub},
-    time::{Duration, SystemTime},
-};
-
-#[derive(Debug)]
-enum Error {
-    Io(std::io::Error),
-    Serde(serde_block_tree::Error),
-    Crypto(crypto::Error),
-    IncorrectSize { expected: usize, actual: usize },
-    Custom(Box<dyn std::fmt::Debug + Send + Sync>),
-}
-
-impl Error {
-    fn custom<E: std::fmt::Debug + Send + Sync + 'static>(err: E) -> Error {
-        Error::Custom(Box::new(err))
-    }
-}
-
-impl Display for Error {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        match self {
-            Error::Io(err) => err.fmt(f),
-            Error::Serde(err) => err.fmt(f),
-            Error::Crypto(err) => err.fmt(f),
-            Error::IncorrectSize { expected, actual } => {
-                write!(f, "incorrect size {}, expected {}", actual, expected)
-            }
-            Error::Custom(err) => err.fmt(f),
-        }
-    }
-}
-
-impl std::error::Error for Error {}
-
-impl From<std::io::Error> for Error {
-    fn from(err: std::io::Error) -> Self {
-        Error::Io(err)
-    }
-}
-
-impl From<Error> for std::io::Error {
-    fn from(err: Error) -> Self {
-        io::Error::new(io::ErrorKind::Other, err)
-    }
-}
-
-impl From<serde_block_tree::Error> for Error {
-    fn from(err: serde_block_tree::Error) -> Self {
-        Error::Serde(err)
-    }
-}
-
-impl From<crypto::Error> for Error {
-    fn from(err: crypto::Error) -> Self {
-        Error::Crypto(err)
-    }
-}
-
-impl From<std::num::TryFromIntError> for Error {
-    fn from(err: std::num::TryFromIntError) -> Self {
-        Error::custom(err)
-    }
-}
-
-type Result<T> = std::result::Result<T, Error>;
-
-/// A Block tagged with its version number. When a block of a previous version is received over
-/// the network or read from the filesystem, it is upgraded to the current version before being
-/// processed.
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-enum VersionedBlock<T> {
-    V0(Block<T>),
-}
-
-const SECTOR_SZ_DEFAULT: usize = 4096;
-
-// A trait for streams which only allow reads and writes in fixed sized units called sectors.
-trait Sectored {
-    // Returns the size of the sector for this stream.
-    fn sector_sz(&self) -> usize;
-
-    fn assert_sector_sz(&self, actual: usize) -> Result<()> {
-        let expected = self.sector_sz();
-        if expected == actual {
-            Ok(())
-        } else {
-            Err(Error::IncorrectSize { expected, actual })
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
-pub struct Header {
-    path: Path,
-    readcaps: HashMap<Principal, Cryptotext<SymKey>>,
-    writecap: Writecap,
-    merkle_root: Hash,
-}
-
-/// A container which binds together ciphertext along with the metadata needed to identify,
-/// verify and decrypt it.
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-struct Block<T> {
-    header: Header,
-    sig: Signature,
-    body: T,
-}
-
-impl<T> Block<T> {
-    fn try_compose_body<E: Into<Error>, U: Decompose<T>, V: TryCompose<T, U, Error = E>>(
-        self,
-        new_body: V,
-    ) -> Result<Block<U>> {
-        Ok(Block {
-            header: self.header,
-            sig: self.sig,
-            body: new_body.try_compose(self.body).map_err(|err| err.into())?,
-        })
-    }
-
-    fn compose_body<U: Decompose<T>, V: Compose<T, U>>(self, new_body: V) -> Block<U> {
-        Block {
-            header: self.header,
-            sig: self.sig,
-            body: new_body.compose(self.body),
-        }
-    }
-}
-
-impl Block<File> {
-    fn new<P: AsRef<std::path::Path>>(path: P) -> Result<Block<File>> {
-        let mut file = OpenOptions::new().read(true).write(true).open(path)?;
-        let header: Header = read_from(&mut file)?;
-        let sig: Signature = read_from(&mut file)?;
-        crypto::verify_header(&header, &sig)?;
-        Ok(Block {
-            header,
-            sig,
-            body: file,
-        })
-    }
-}
-
-impl<T: Write> Write for Block<T> {
-    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        self.body.write(buf)
-    }
-
-    fn flush(&mut self) -> io::Result<()> {
-        self.body.flush()
-    }
-}
-
-impl<T: Read> Read for Block<T> {
-    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        self.body.read(buf)
-    }
-}
-
-impl<T: Seek> Seek for Block<T> {
-    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
-        self.body.seek(pos)
-    }
-}
-
-trait Decompose<T> {
-    fn into_inner(self) -> T;
-}
-
-trait TryCompose<T, U: Decompose<T>> {
-    type Error;
-    fn try_compose(self, inner: T) -> std::result::Result<U, Self::Error>;
-}
-
-trait Compose<T, U> {
-    fn compose(self, inner: T) -> U;
-}
-
-impl<T, U: Decompose<T>, S: TryCompose<T, U, Error = Infallible>> Compose<T, U> for S {
-    fn compose(self, inner: T) -> U {
-        let result = self.try_compose(inner);
-        // Safety: Infallible has no values, so `result` must be `Ok`.
-        unsafe { result.unwrap_unchecked() }
-    }
-}
-
-/// Extensions to the `Read` trait.
-trait ReadExt: Read {
-    /// Reads repeatedly until one of the following occur:
-    ///  1. The given buffer is full.
-    ///  2. A call to `read` returns 0.
-    ///  3. A call to `read` returns an error.
-    /// The number of bytes read is returned. If an error is returned, then no bytes were read.
-    fn fill_buf(&mut self, mut dest: &mut [u8]) -> io::Result<usize> {
-        let dest_len_start = dest.len();
-        while !dest.is_empty() {
-            let byte_ct = match self.read(dest) {
-                Ok(byte_ct) => byte_ct,
-                Err(err) => {
-                    if dest_len_start == dest.len() {
-                        return Err(err);
-                    } else {
-                        // We're not allowed to return an error if we've already read from self.
-                        error!("an error occurred in fill_buf: {}", err);
-                        break;
-                    }
-                }
-            };
-            if 0 == byte_ct {
-                break;
-            }
-            dest = &mut dest[byte_ct..];
-        }
-        Ok(dest_len_start - dest.len())
-    }
-}
-
-impl<T: Read> ReadExt for T {}
-
-impl<T: Write> Decompose<T> for CompressorWriter<T> {
-    fn into_inner(self) -> T {
-        self.into_inner()
-    }
-}
-
-impl<T: Read> Decompose<T> for Decompressor<T> {
-    fn into_inner(self) -> T {
-        self.into_inner()
-    }
-}
-
-#[derive(Clone)]
-struct BrotliParams {
-    buf_sz: usize,
-    quality: u32,
-    window_sz: u32,
-}
-
-impl BrotliParams {
-    fn new(buf_sz: usize, quality: u32, window_sz: u32) -> BrotliParams {
-        BrotliParams {
-            buf_sz,
-            quality,
-            window_sz,
-        }
-    }
-}
-
-impl<T: Write> TryCompose<T, CompressorWriter<T>> for BrotliParams {
-    type Error = Error;
-    fn try_compose(self, inner: T) -> Result<CompressorWriter<T>> {
-        Ok(CompressorWriter::new(
-            inner,
-            self.buf_sz,
-            self.quality,
-            self.window_sz,
-        ))
-    }
-}
-
-impl<T: Read> TryCompose<T, Decompressor<T>> for BrotliParams {
-    type Error = Error;
-    fn try_compose(self, inner: T) -> Result<Decompressor<T>> {
-        Ok(Decompressor::new(inner, self.buf_sz))
-    }
-}
-
-/// TODO: Remove this once the error_chain crate is integrated.
-fn err_conv<T, E: std::error::Error + Send + Sync + 'static>(
-    result: std::result::Result<T, E>,
-) -> std::result::Result<T, io::Error> {
-    result.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
-}
-
-/// A stream which buffers writes and read such that the inner stream only sees reads and writes
-/// of sector length buffers.
-struct SectoredBuf<T> {
-    inner: T,
-    buf: Vec<u8>,
-    /// The offset into the inner stream which the zero offset byte in `buf` corresponds to.
-    buf_start: usize,
-    /// Indicates if the contents of `buf` have been written to, and so whether `buf` needs to be
-    /// written back to `inner` before it is refilled.
-    dirty: bool,
-    /// The total number of bytes that have been written to the inner stream, including the reserved
-    /// bytes at the beginning.
-    len: usize,
-    /// The current position of this stream, expressed as an offset into the inner stream.
-    pos: usize,
-}
-
-impl SectoredBuf<()> {
-    fn new() -> SectoredBuf<()> {
-        SectoredBuf {
-            inner: (),
-            buf: Vec::new(),
-            buf_start: 0,
-            dirty: false,
-            len: 0,
-            pos: 0,
-        }
-    }
-}
-
-impl<T> SectoredBuf<T> {
-    /// The number of bytes at the beginning of the inner stream which are reserved to store the
-    /// length of data written. All offsets into the stored data must be shifted by this amount to
-    /// be translated to an offset in the inner stream.
-    const RESERVED: usize = std::mem::size_of::<usize>();
-
-    /// Returns the position in the inner stream which the given position in this stream corresponds
-    /// to.
-    fn inner_pos(self_pos: u64) -> u64 {
-        let offset: u64 = Self::RESERVED.try_into().unwrap();
-        self_pos + offset
-    }
-
-    /// Returns the position in this stream which the given position in the inner stream corresponds
-    /// to.
-    fn self_pos(inner_pos: u64) -> u64 {
-        let offset: u64 = Self::RESERVED.try_into().unwrap();
-        inner_pos - offset
-    }
-
-    /// Returns the offset into the internal buffer that corresponds to the current position.
-    fn buf_pos(&self) -> usize {
-        self.pos - self.buf_start
-    }
-
-    /// Returns one more than the last index in the internal buffer which can be read.
-    fn buf_end(&self) -> usize {
-        let limit = self.len.min(self.buf_start + self.sector_sz());
-        limit - self.buf_start
-    }
-
-    /// Returns the index of the sector which is currently loaded into the buffer.
-    fn buf_sector_index(&self) -> usize {
-        self.pos / self.sector_sz()
-    }
-}
-
-impl<T: Read + Seek> SectoredBuf<T> {
-    /// Fills the internal buffer by reading from the inner stream at the current position
-    /// and updates `self.buf_start` with the position read from.
-    fn fill_internal_buf(&mut self) -> io::Result<usize> {
-        self.buf_start = err_conv(self.inner.stream_position()?.try_into())?;
-        let read_bytes = if self.buf_start < self.len {
-            let read_bytes = self.inner.fill_buf(&mut self.buf)?;
-            if read_bytes < self.buf.len() {
-                return Err(io::Error::new(
-                    io::ErrorKind::Other,
-                    format!(
-                        "Failed to fill SectoredBuf.buf. Expected {} bytes, got {}.",
-                        self.buf.len(),
-                        read_bytes
-                    ),
-                ));
-            }
-            read_bytes
-        } else {
-            0
-        };
-        Ok(read_bytes)
-    }
-}
-
-impl<T> Decompose<T> for SectoredBuf<T> {
-    fn into_inner(self) -> T {
-        self.inner
-    }
-}
-
-impl<T: Sectored + Read + Seek> TryCompose<T, SectoredBuf<T>> for SectoredBuf<()> {
-    type Error = Error;
-    fn try_compose(self, inner: T) -> Result<SectoredBuf<T>> {
-        let sect_sz = inner.sector_sz();
-        if sect_sz < Self::RESERVED {
-            return Err(Error::custom(format!(
-                "a sector size of at least {} is required. Got {}",
-                Self::RESERVED,
-                sect_sz,
-            )));
-        }
-        let mut sectored = SectoredBuf {
-            inner,
-            buf: self.buf,
-            buf_start: 0,
-            dirty: false,
-            len: Self::RESERVED,
-            pos: Self::RESERVED,
-        };
-        sectored.inner.seek(SeekFrom::Start(0))?;
-        sectored.buf.resize(sect_sz, 0);
-        let len_stored = match sectored.fill_internal_buf() {
-            Ok(bytes_read) => bytes_read >= Self::RESERVED,
-            Err(err) => {
-                error!("SectoredBuf::fill_internal_buf returned an error: {}", err);
-                false
-            }
-        };
-        if len_stored {
-            if let Ok(len) = read_from::<u64, _>(&mut sectored.buf.as_slice()) {
-                sectored.len = len.try_into()?;
-            }
-        } else {
-            write_to(&Self::RESERVED, &mut sectored.buf.as_mut_slice())?;
-            sectored.dirty = true;
-        }
-        Ok(sectored)
-    }
-}
-
-impl<T> Sectored for SectoredBuf<T> {
-    fn sector_sz(&self) -> usize {
-        self.buf.len()
-    }
-}
-
-impl<T: Seek + Read + Write> Write for SectoredBuf<T> {
-    fn write(&mut self, mut src: &[u8]) -> io::Result<usize> {
-        let src_len_start = src.len();
-        let mut dest = {
-            let buf_pos = self.buf_pos();
-            &mut self.buf[buf_pos..]
-        };
-        while !src.is_empty() {
-            if dest.is_empty() {
-                if let Err(err) = self.flush() {
-                    error!("A call to SectoredBuf::flush returned an error: {}", err);
-                    break;
-                }
-                dest = &mut self.buf[..];
-            }
-            let sz = src.len().min(dest.len());
-            (&mut dest[..sz]).copy_from_slice(&src[..sz]);
-            dest = &mut dest[sz..];
-            src = &src[sz..];
-            self.dirty = sz > 0;
-            self.pos += sz;
-        }
-        Ok(src_len_start - src.len())
-    }
-
-    fn flush(&mut self) -> io::Result<()> {
-        if !self.dirty {
-            return Ok(());
-        }
-
-        // Write out the contents of the buffer.
-        let sect_sz: u64 = err_conv(self.sector_sz().try_into())?;
-        let inner_pos = self.inner.stream_position()?;
-        let inner_pos_usize: usize = err_conv(inner_pos.try_into())?;
-        let is_new_sector = self.pos > inner_pos_usize;
-        let is_full = (self.buf.len() - self.buf_pos()) == 0;
-        let seek_to = if is_new_sector {
-            if is_full {
-                inner_pos + sect_sz
-            } else {
-                inner_pos
-            }
-        } else {
-            // The contents of the buffer were previously read from inner, so we write the
-            // updated contents to the same offset.
-            let sect_start: u64 = err_conv(self.buf_start.try_into())?;
-            self.inner.seek(SeekFrom::Start(sect_start))?;
-            if is_full {
-                inner_pos
-            } else {
-                inner_pos - sect_sz
-            }
-        };
-        self.inner.write_all(&self.buf)?;
-
-        // Update the stored length.
-        self.len = self.len.max(self.pos);
-        self.inner.seek(SeekFrom::Start(0))?;
-        self.fill_internal_buf()?;
-        let len: u64 = err_conv(self.len.try_into())?;
-        err_conv(write_to(&len, &mut self.buf.as_mut_slice()))?;
-        self.inner.seek(SeekFrom::Start(0))?;
-        self.inner.write_all(&self.buf)?;
-
-        // Seek to the next position.
-        self.inner.seek(SeekFrom::Start(seek_to))?;
-        self.fill_internal_buf()?;
-        self.dirty = false;
-
-        Ok(())
-    }
-}
-
-impl<T: Read + Seek> Read for SectoredBuf<T> {
-    fn read(&mut self, mut dest: &mut [u8]) -> io::Result<usize> {
-        if self.pos == self.len {
-            return Ok(0);
-        }
-
-        let dest_len_start = dest.len();
-        let mut src = {
-            let start = self.buf_pos();
-            let end = self.buf_end();
-            &self.buf[start..end]
-        };
-        while !dest.is_empty() {
-            if src.is_empty() {
-                if self.pos >= self.len {
-                    break;
-                }
-                let byte_ct = match self.fill_internal_buf() {
-                    Ok(byte_ct) => byte_ct,
-                    Err(err) => {
-                        error!("SectoredBuf::full_internal_buf returned an error: {}", err);
-                        break;
-                    }
-                };
-                if 0 == byte_ct {
-                    break;
-                }
-                src = &self.buf[..byte_ct];
-            }
-            let sz = src.len().min(dest.len());
-            (&mut dest[..sz]).copy_from_slice(&src[..sz]);
-            dest = &mut dest[sz..];
-            src = &src[sz..];
-            self.pos += sz;
-        }
-        Ok(dest_len_start - dest.len())
-    }
-}
-
-impl<T: Seek + Read + Write> Seek for SectoredBuf<T> {
-    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
-        let inner_pos = self.inner.stream_position()?;
-        let inner_pos_new = match pos {
-            SeekFrom::Start(rel_start) => Self::inner_pos(rel_start),
-            SeekFrom::Current(rel_curr) => {
-                if rel_curr > 0 {
-                    inner_pos + rel_curr as u64
-                } else {
-                    inner_pos - rel_curr as u64
-                }
-            }
-            SeekFrom::End(_) => {
-                return Err(io::Error::new(
-                    io::ErrorKind::Unsupported,
-                    "seeking relative to the end of the stream is not supported",
-                ))
-            }
-        };
-        let sect_sz = self.sector_sz();
-        let sect_index = self.buf_sector_index();
-        let sect_index_new = err_conv(TryInto::<usize>::try_into(inner_pos_new))? / sect_sz;
-        let pos: u64 = err_conv(self.pos.try_into())?;
-        if sect_index != sect_index_new || pos == inner_pos {
-            self.flush()?;
-            let seek_to: u64 = err_conv((sect_index_new * sect_sz).try_into())?;
-            self.inner.seek(SeekFrom::Start(seek_to))?;
-            self.fill_internal_buf()?;
-        }
-        self.pos = err_conv(inner_pos_new.try_into())?;
-        Ok(Self::self_pos(inner_pos_new))
-    }
-}
-
-/// An envelopment of a key, which is tagged with the principal who the key is meant for.
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-struct Readcap {
-    /// The principal this `Readcap` was issued to.
-    issued_to: Principal,
-    /// An encipherment of a block key using the public key of the principal.
-    key: Cryptotext<SymKey>,
-}
-
-impl Readcap {
-    fn new(issued_to: Hash, key: Cryptotext<SymKey>) -> Readcap {
-        Readcap {
-            issued_to: Principal(issued_to),
-            key,
-        }
-    }
-}
-
-/// Verifies that a principal is authorized to write blocks in a tree.
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-struct Writecap {
-    /// The principal this `Writecap` was issued to.
-    issued_to: Principal,
-    /// The path where this write caps's validity begins.
-    path: Path,
-    /// The point in time after which this write cap is no longer valid.
-    expires: Epoch,
-    /// The public key used to sign this write cap.
-    signing_key: AsymKeyPub<Sign>,
-    /// A digital signature which covers all of the fields in the write cap except for next.
-    signature: Signature,
-    /// The next write cap in the chain leading back to the root.
-    next: Option<Box<Writecap>>,
-}
-
-/// Fragments are created from blocks using Erasure Encoding and stored with other nodes in the
-/// network to provide availability and redundancy of data.
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-struct Fragment {
-    /// The path to the block this fragment is from.
-    path: Path,
-    /// The serial number of this fragment.
-    serial: FragmentSerial,
-    /// The actual data.
-    body: Vec<u8>,
-}
-
-impl Fragment {
-    /// Create a new fragment with the given fields. If `path_str` cannot be parsed then a failed
-    /// `Result` is returned containing a `PathError`.
-    fn new(
-        path_str: &str,
-        serial_num: u32,
-        body: Vec<u8>,
-    ) -> std::result::Result<Fragment, PathError> {
-        let result = Path::try_from(path_str);
-        Ok(Fragment {
-            path: result?,
-            serial: FragmentSerial(serial_num),
-            body,
-        })
-    }
-}
-
-/// The body of every non-leaf node in a tree contains this data structure.
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-struct Directory {
-    /// The nodes that are attached to the tree at this block.
-    nodes: Vec<Principal>,
-    /// This block's descendants.
-    children: HashMap<String, HashMap<FragmentSerial, FragmentRecord>>,
-}
-
-/// Keeps track of which principal is storing a fragment.
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-struct FragmentRecord {
-    /// The fragment serial number this record is for.
-    serial: FragmentSerial,
-    /// The principal who is storing this fragment.
-    stored_by: Principal,
-}
-
-impl FragmentRecord {
-    /// Creates a new `FragmentRecord` whose `serial` and `stored_by` fields are set to
-    /// the given values.
-    fn new(serial: u32, stored_by: Hash) -> FragmentRecord {
-        FragmentRecord {
-            serial: FragmentSerial(serial),
-            stored_by: Principal(stored_by),
-        }
-    }
-}
-
-/// An identifier for a security principal, which is any entity that can be authenticated.
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone)]
-struct Principal(Hash);
-
-impl Principal {
-    fn kind(&self) -> HashKind {
-        HashKind::from(&self.0)
-    }
-}
-
-/// Trait for types which are owned by a `Principal`.
-trait Owned {
-    /// Returns the `Principal` that owns `self`, using the given hash algorithm.
-    fn owner_of_kind(&self, kind: HashKind) -> Principal;
-
-    /// Returns the `Principal` that owns `self`, using the default hash algorithm.
-    fn owner(&self) -> Principal {
-        self.owner_of_kind(HashKind::default())
-    }
-}
-
-/// An identifier for a block in a tree.
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-struct Path {
-    owner: Principal,
-    components: Vec<String>,
-}
-
-impl Path {
-    /// The character that is used to separate path components.
-    const SEP: char = '/';
-    /// The limit, in bytes, of a `Path`'s length.
-    const BYTE_LIMIT: usize = 4096;
-
-    /// Returns a result which, when successful, contains the index after the last character in the
-    /// current path component.
-    fn component_end<I: Iterator<Item = (usize, char)>>(
-        start: usize,
-        first: char,
-        pairs: &mut I,
-    ) -> std::result::Result<usize, PathError> {
-        if first == Path::SEP {
-            return Err(PathError::EmptyComponent);
-        }
-        let end;
-        let mut last = start;
-        loop {
-            match pairs.next() {
-                Some((index, Path::SEP)) => {
-                    end = index;
-                    break;
-                }
-                Some((index, _)) => last = index,
-                None => {
-                    end = last + 1;
-                    break;
-                }
-            }
-        }
-        if end == start {
-            Err(PathError::EmptyComponent)
-        } else {
-            Ok(end)
-        }
-    }
-
-    /// Asserts that the number of bytes in the given string is no more than `Path::BYTE_LIMIT`.
-    fn assert_not_too_long(string: &str) -> std::result::Result<(), PathError> {
-        let len = string.len();
-        if len > Path::BYTE_LIMIT {
-            return Err(PathError::PathTooLong(len));
-        }
-        Ok(())
-    }
-
-    /// Returns true if `other` is a subpath of this `Path`.
-    fn contains(&self, other: &Path) -> bool {
-        if self.owner != other.owner {
-            return false;
-        };
-        // This path must be no longer than the other path.
-        if self.components.len() > other.components.len() {
-            return false;
-        }
-        // Skip the component containing the owner.
-        let self_iter = self.components.iter().skip(1);
-        let other_iter = other.components.iter().skip(1);
-        for pair in self_iter.zip(other_iter) {
-            if pair.0 != pair.1 {
-                return false;
-            }
-        }
-        true
-    }
-}
-
-impl<'s> TryFrom<&'s str> for Path {
-    type Error = PathError;
-
-    fn try_from(string: &'s str) -> std::result::Result<Path, PathError> {
-        Path::assert_not_too_long(string)?;
-        let mut pairs = string.char_indices();
-        let mut components = Vec::new();
-        let mut last_end = 0;
-        while let Some((start, c)) = pairs.next() {
-            let end = Path::component_end(start, c, &mut pairs)?;
-            last_end = end;
-            let slice = &string[start..end];
-            components.push(slice.to_string());
-        }
-        // An empty component is added to the end to indicate if there was a trailing slash.
-        if string.len() - 1 == last_end {
-            components.push("".to_string());
-        }
-        let leading = components
-            .get(0)
-            .ok_or(PathError::InvalidLeadingComponent)?;
-        let hash =
-            Hash::try_from(leading.as_str()).map_err(|_| PathError::InvalidLeadingComponent)?;
-        Ok(Path {
-            owner: Principal(hash),
-            components,
-        })
-    }
-}
-
-impl Display for Path {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if self.components.is_empty() {
-            return write!(f, "");
-        };
-        let mut iter = self.components.iter();
-        let first = iter.next().unwrap();
-        let mut output = write!(f, "{}", first);
-        for component in iter {
-            output = write!(f, "{}{}", Path::SEP, component)
-        }
-        output
-    }
-}
-
-/// Errors which can occur when converting a string to a `Path`.
-#[derive(Debug, PartialEq)]
-enum PathError {
-    /// Occurs when the number of bytes in a string is greater than `Path::BYTE_LIMIT`.
-    PathTooLong(usize),
-    /// Indicates that a path string was empty.
-    Empty,
-    /// Occurs when a component in a path string was empty.
-    EmptyComponent,
-    /// Occurs when the leading component of a path is not in the correct format.
-    InvalidLeadingComponent,
-}
-
-impl Display for PathError {
-    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
-        match self {
-            PathError::PathTooLong(length) => formatter.write_fmt(format_args!(
-                "path contained {} bytes, which is over the {} byte limit",
-                length,
-                Path::BYTE_LIMIT
-            )),
-            PathError::Empty => formatter.write_str("path was empty"),
-            PathError::EmptyComponent => formatter.write_str("component of path was empty"),
-            PathError::InvalidLeadingComponent => {
-                formatter.write_str("invalid leading path component")
-            }
-        }
-    }
-}
-
-/// An instant in time represented by the number of seconds since January 1st 1970, 00:00:00 UTC.
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
-struct Epoch(u64);
-
-impl Epoch {
-    /// Returns the current epoch time.
-    fn now() -> Epoch {
-        let now = SystemTime::now();
-        // If the system clock is before the unix epoch, just panic.
-        let epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
-        Epoch(epoch.as_secs())
-    }
-}
-
-impl Copy for Epoch {}
-
-impl Add<Duration> for Epoch {
-    type Output = Self;
-    fn add(self, other: Duration) -> Self {
-        Epoch(self.0 + other.as_secs())
-    }
-}
-
-impl Sub<Duration> for Epoch {
-    type Output = Self;
-    fn sub(self, other: Duration) -> Self {
-        Epoch(self.0 - other.as_secs())
-    }
-}
-
-/// The serial number of a block fragment.
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable)]
-struct FragmentSerial(u32);
+use std::io::{self, BufWriter, Write};
 
+#[allow(dead_code)]
 fn send<W: Write>(stdout: &mut BufWriter<W>, msg: &Message) {
     if let Err(err) = write_to(msg, stdout) {
         error!("Failed to serialize message {:?}", err);
@@ -929,325 +43,3 @@ fn main() {
         }
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use std::io::Cursor;
-
-    use super::*;
-    use test_helpers::*;
-
-    fn path_from_str_test_case(
-        expected: std::result::Result<Path, PathError>,
-        input: &str,
-    ) -> std::result::Result<(), PathError> {
-        let result = Path::try_from(input);
-        assert_eq!(expected, result);
-        Ok(())
-    }
-
-    #[test]
-    fn path_from_str_multiple_components_ok() -> std::result::Result<(), PathError> {
-        let expected = make_path(vec!["red", "green", "blue"]);
-        let input = format!("{}/red/green/blue", expected.owner.0);
-        path_from_str_test_case(Ok(expected), input.as_str())?;
-        Ok(())
-    }
-
-    #[test]
-    fn path_from_str_one_component_ok() -> std::result::Result<(), PathError> {
-        let expected = make_path(vec![]);
-        let input = expected.owner.0.to_string();
-        path_from_str_test_case(Ok(expected), input.as_str())?;
-        Ok(())
-    }
-
-    #[test]
-    fn path_from_str_trailing_slash_ok() -> std::result::Result<(), PathError> {
-        // Notice the empty component at the end of this path due to the trailing slash.
-        let expected = make_path(vec!["orange", "banana", "shotgun", ""]);
-        let input = format!("{}/orange/banana/shotgun/", expected.owner.0);
-        path_from_str_test_case(Ok(expected), input.as_str())?;
-        Ok(())
-    }
-
-    #[test]
-    fn path_from_str_path_too_long_fail() -> std::result::Result<(), PathError> {
-        let principal = make_principal();
-        let input = format!("{}/{}", principal.0, "*".repeat(4097));
-        let expected = Err(PathError::PathTooLong(input.len()));
-        path_from_str_test_case(expected, input.as_str())?;
-        Ok(())
-    }
-
-    #[test]
-    fn path_from_str_multiple_slashes_fail() -> std::result::Result<(), PathError> {
-        let expected = Err(PathError::EmptyComponent);
-        let input = format!("{}//orange", make_principal().0);
-        path_from_str_test_case(expected, input.as_str())?;
-        Ok(())
-    }
-
-    #[test]
-    fn path_from_str_leading_slash_fail() -> std::result::Result<(), PathError> {
-        let expected = Err(PathError::EmptyComponent);
-        let input = format!("/{}/orange/banana/shotgun", make_principal().0);
-        path_from_str_test_case(expected, input.as_str())?;
-        Ok(())
-    }
-
-    #[test]
-    fn path_round_trip() -> std::result::Result<(), PathError> {
-        let expected = make_path(vec!["interstitial", "inter-related", "intersections"]);
-        let actual = Path::try_from(expected.to_string().as_str())?;
-        assert_eq!(expected, actual);
-        Ok(())
-    }
-
-    #[test]
-    fn path_contains_true() {
-        let larger = make_path(vec!["apps"]);
-        let smaller = make_path(vec!["apps", "bohdi"]);
-        assert!(larger.contains(&smaller));
-    }
-
-    #[test]
-    fn path_contains_true_only_owner() {
-        let larger = make_path(vec![]);
-        let smaller = make_path(vec![]);
-        assert!(larger.contains(&smaller));
-    }
-
-    #[test]
-    fn path_contains_false_self_is_longer() {
-        let first = make_path(vec!["apps", "bohdi"]);
-        let second = make_path(vec!["apps"]);
-        assert!(!first.contains(&second));
-    }
-
-    #[test]
-    fn path_contains_false_same_owners() {
-        let first = make_path(vec!["apps"]);
-        let second = make_path(vec!["nodes"]);
-        assert!(!first.contains(&second));
-    }
-
-    #[test]
-    fn path_contains_false_different_owners() {
-        let first = make_path(vec!["apps"]);
-        let mut second = make_path(vec!["apps"]);
-        second.owner = Principal(Hash::Sha2_256(PRINCIPAL2));
-        assert!(!first.contains(&second));
-    }
-
-    #[test]
-    fn brotli_compress_decompress() {
-        const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
-        const SECT_CT: usize = 16;
-        let params = BrotliParams::new(SECT_SZ, 8, 20);
-        let mut memory = Cursor::new([0u8; SECT_SZ * SECT_CT]);
-        {
-            let write: CompressorWriter<_> = params
-                .clone()
-                .try_compose(&mut memory)
-                .expect("compose for write failed");
-            write_fill(write, SECT_SZ, SECT_CT);
-        }
-        memory.seek(SeekFrom::Start(0)).expect("seek failed");
-        {
-            let read: Decompressor<_> = params
-                .try_compose(&mut memory)
-                .expect("compose for read failed");
-            read_check(read, SECT_SZ, SECT_CT);
-        }
-    }
-
-    fn make_sectored_buf(sect_sz: usize, sect_ct: usize) -> SectoredBuf<SectoredCursor<Vec<u8>>> {
-        SectoredBuf::new()
-            .try_compose(SectoredCursor::new(vec![0u8; sect_sz * sect_ct], sect_sz))
-            .expect("compose for sectored buffer failed")
-    }
-
-    #[test]
-    fn sectored_buf_fill_inner() {
-        const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
-        const SECT_CT: usize = 16;
-        let mut sectored = make_sectored_buf(SECT_SZ, SECT_CT);
-        let sect_sz = sectored.sector_sz();
-        assert_eq!(0, sect_sz % 16);
-        let chunk_sz = sect_sz / 16;
-        let chunk_ct = SECT_CT * 16;
-        write_fill(&mut sectored, chunk_sz, chunk_ct);
-    }
-
-    #[test]
-    fn sectored_buf_write_read_sequential() {
-        const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
-        const SECT_CT: usize = 16;
-        let mut sectored = make_sectored_buf(SECT_SZ, SECT_CT);
-        let sect_sz = sectored.sector_sz();
-        assert_eq!(0, sect_sz % 16);
-        let chunk_sz = sect_sz / 16;
-        // We subtract one here so that the underlying buffer is not completely filled. This
-        // exercises the length limiting capability of the sectored buffer.
-        let chunk_ct = SECT_CT * 16 - 1;
-        write_fill(&mut sectored, chunk_sz, chunk_ct);
-        sectored.seek(SeekFrom::Start(0)).expect("seek failed");
-        read_check(&mut sectored, chunk_sz, chunk_ct);
-    }
-
-    #[test]
-    fn sectored_buf_sect_sz_too_small_is_error() {
-        const MIN: usize = SectoredBuf::<()>::RESERVED;
-        let result = SectoredBuf::new().try_compose(SectoredCursor::new([0u8; MIN], MIN - 1));
-        assert!(result.is_err());
-    }
-
-    #[test]
-    fn sectored_buf_len_preserved() {
-        const SECT_SZ: usize = SECTOR_SZ_DEFAULT;
-        const SECT_CT: usize = 16;
-        let mut sectored = make_sectored_buf(SECT_SZ, SECT_CT);
-        let expected = vec![42u8; 12];
-        // We need to ensure that writing expected will not fill up the buffer in sectored.
-        assert!(expected.len() < sectored.sector_sz() - SectoredBuf::<()>::RESERVED);
-
-        sectored.write_all(&expected).expect("write failed");
-        sectored.flush().expect("flush failed");
-        let inner = sectored.into_inner();
-        let mut sectored = SectoredBuf::new()
-            .try_compose(inner)
-            .expect("failed to compose sectored buffer");
-        let mut actual = vec![0u8; expected.len()];
-        sectored
-            .fill_buf(actual.as_mut_slice())
-            .expect("failed to fill actual");
-
-        assert_eq!(expected, actual);
-    }
-
-    #[test]
-    fn sectored_buf_seek() {
-        let sect_sz = 16usize;
-        let sect_ct = 16usize;
-        let cap = sect_sz * sect_ct - std::mem::size_of::<usize>();
-        let source = {
-            let mut source = Vec::with_capacity(cap);
-            source.extend(
-                std::iter::successors(Some(0u8), |n| if *n <= 254 { Some(*n + 1) } else { None })
-                    .take(cap),
-            );
-            source
-        };
-        let mut sectored = make_sectored_buf(sect_sz, sect_ct);
-        sectored.write(&source).expect("write failed");
-        let mut buf = [0u8; 1];
-        let end = cap.try_into().expect("cap cannot fit into a u8");
-        for pos in (0..end).rev() {
-            sectored
-                .seek(SeekFrom::Start(pos as u64))
-                .expect("seek failed");
-            sectored.read(&mut buf).expect("read failed");
-            assert_eq!(pos, buf[0]);
-        }
-    }
-
-    #[test]
-    fn sectored_buf_write_read_random() {
-        const SECT_SZ: usize = 16;
-        const SECT_CT: usize = 16;
-        const CAP: usize = SECT_SZ * SECT_CT - std::mem::size_of::<usize>();
-        let source = {
-            let mut expected = Vec::with_capacity(CAP);
-            expected.extend(
-                std::iter::successors(Some(0u8), |n| if *n <= 254 { Some(*n + 1) } else { None })
-                    .take(CAP),
-            );
-            expected
-        };
-        let indices: Vec<(usize, usize)> = {
-            let rando = Randomizer::new([3u8; Randomizer::HASH.len()]);
-            let rando2 = Randomizer::new([5u8; Randomizer::HASH.len()]);
-            rando
-                .zip(rando2)
-                .take(SECT_CT)
-                .map(|(mut first, mut second)| {
-                    first %= source.len();
-                    second &= source.len();
-                    let low = first.min(second);
-                    let high = first.max(second);
-                    (low, high)
-                })
-                .collect()
-        };
-
-        let mut sectored = make_sectored_buf(SECT_SZ, SECT_CT);
-        sectored
-            .write_all(&[0u8; CAP])
-            .expect("failed to fill sectored");
-        sectored.flush().expect("flush failed");
-        for (_k, (low, high)) in indices.iter().enumerate() {
-            sectored
-                .seek(SeekFrom::Start(*low as u64))
-                .expect("seek failed");
-            let src = &source[*low..*high];
-            sectored.write(src).expect("write failed");
-        }
-        sectored.flush().expect("flush failed");
-        let mut buf = vec![0u8; CAP];
-        for (_k, (low, high)) in indices.iter().enumerate() {
-            sectored
-                .seek(SeekFrom::Start(*low as u64))
-                .expect("seek failed");
-            let actual = &mut buf[*low..*high];
-            sectored.fill_buf(actual).expect("read failed");
-            let expected = &source[*low..*high];
-            assert_eq!(expected, actual);
-        }
-    }
-
-    #[test]
-    fn sectored_buf_read_past_end() {
-        const LEN: usize = 32;
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new([0u8; LEN], LEN))
-            .expect("compose failed");
-        const BUF_LEN: usize = LEN - SectoredBuf::<()>::RESERVED + 1;
-        sectored.write(&[1u8; BUF_LEN - 1]).expect("write failed");
-        sectored.seek(SeekFrom::Start(0)).expect("seek failed");
-        let mut buf = [0u8; BUF_LEN];
-        // Note that buf is one byte longer than the available capacity in the cursor.
-        sectored.read(&mut buf).expect("read failed");
-        assert_eq!(&[1u8; BUF_LEN - 1], &buf[..(BUF_LEN - 1)]);
-        assert_eq!(0u8, buf[BUF_LEN - 1]);
-    }
-
-    /// Tests that the data written in try_compose is actually written back to the underlying stream.
-    #[test]
-    fn sectored_buf_write_back() {
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(vec![0u8; 24], 16))
-            .expect("compose failed");
-        let expected = [1u8; 8];
-        sectored.write(&expected).expect("first write failed");
-        sectored.write(&[2u8; 8]).expect("second write failed");
-        sectored.seek(SeekFrom::Start(0)).expect("seek failed");
-        let mut actual = [0u8; 8];
-        sectored.read(&mut actual).expect("read failed");
-        assert_eq!(expected, actual);
-    }
-
-    #[test]
-    fn sectored_buf_write_past_end() {
-        const LEN: usize = 8;
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(vec![0u8; 0], LEN))
-            .expect("compos failed");
-        let expected = [1u8; LEN + 1];
-        sectored.write(&expected).expect("write failed");
-        sectored.seek(SeekFrom::Start(0)).expect("seek failed");
-        let mut actual = [0u8; LEN + 1];
-        sectored.read(&mut actual).expect("read failed");
-        assert_eq!(expected, actual);
-    }
-}

+ 1 - 1
crates/serde-block-tree/Cargo.toml → crates/btserde/Cargo.toml

@@ -1,5 +1,5 @@
 [package]
-name = "serde-block-tree"
+name = "btserde"
 version = "0.1.0"
 authors = ["Matthew Carr <mdcarr941@gmail.com>"]
 edition = "2021"

+ 1 - 1
crates/serde-block-tree/src/de.rs → crates/btserde/src/de.rs

@@ -380,7 +380,7 @@ impl<'de, T: Read> de::VariantAccess<'de> for &mut Deserializer<'de, T> {
     }
 }
 
-impl<'a, 'de, T: Read> de::EnumAccess<'de> for &mut Deserializer<'de, T> {
+impl<'de, T: Read> de::EnumAccess<'de> for &mut Deserializer<'de, T> {
     type Error = Error;
     type Variant = Self;
     

+ 0 - 0
crates/serde-block-tree/src/dependency_tests.rs → crates/btserde/src/dependency_tests.rs


+ 0 - 0
crates/serde-block-tree/src/error.rs → crates/btserde/src/error.rs


+ 0 - 0
crates/serde-block-tree/src/lib.rs → crates/btserde/src/lib.rs


+ 0 - 0
crates/serde-block-tree/src/ser.rs → crates/btserde/src/ser.rs


+ 0 - 0
crates/web-host/README.md → crates/btweb/README.md


+ 0 - 1
crates/harness/.vscode

@@ -1 +0,0 @@
-../.vscode

+ 0 - 228
crates/harness/Cargo.lock

@@ -1,228 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "aho-corasick"
-version = "0.7.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "atty"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-dependencies = [
- "hermit-abi",
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "ctor"
-version = "0.1.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
-dependencies = [
- "quote",
- "syn",
-]
-
-[[package]]
-name = "env_logger"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
-dependencies = [
- "atty",
- "humantime",
- "log",
- "regex",
- "termcolor",
-]
-
-[[package]]
-name = "harness"
-version = "0.0.1"
-dependencies = [
- "ctor",
- "env_logger",
- "log",
- "serde",
- "serde-block-tree",
-]
-
-[[package]]
-name = "hermit-abi"
-version = "0.1.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "humantime"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
-
-[[package]]
-name = "libc"
-version = "0.2.126"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
-
-[[package]]
-name = "log"
-version = "0.4.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "memchr"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "regex"
-version = "1.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-syntax"
-version = "0.6.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
-
-[[package]]
-name = "serde"
-version = "1.0.138"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde-big-array"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3323f09a748af288c3dc2474ea6803ee81f118321775bffa3ac8f7e65c5e90e7"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "serde-block-tree"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde-big-array",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.138"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.98"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "termcolor"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
-dependencies = [
- "winapi-util",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-util"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

+ 1 - 1
crates/harness/Cargo.toml

@@ -6,7 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-serde-block-tree = { path = "../serde-block-tree" }
+btserde = { path = "../btserde" }
 serde = { version = "^1.0.136", features = ["derive"] }
 log = "0.4.17"
 

+ 5 - 5
crates/harness/src/lib.rs

@@ -5,7 +5,7 @@ use std::{
     sync::mpsc::{channel, Receiver},
     time::{Duration},
 };
-use serde_block_tree::{self, write_to, read_from};
+use btserde::{self, write_to, read_from};
 use log::{error};
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -42,8 +42,8 @@ impl Node {
                     }
                 },
                 // Break if the child is has exited.
-                Err(serde_block_tree::Error::Eof) => break,
-                Err(serde_block_tree::Error::Io(io_err)) => {
+                Err(btserde::Error::Eof) => break,
+                Err(btserde::Error::Io(io_err)) => {
                     match io_err.kind() {
                         std::io::ErrorKind::UnexpectedEof => break,
                         _ => error!("IO error ocurred: {:?}", io_err),
@@ -55,9 +55,9 @@ impl Node {
         Ok(Node { child, stdin, message_rx })
     }
 
-    pub fn send(&mut self, msg: &Message) -> serde_block_tree::Result<()> {
+    pub fn send(&mut self, msg: &Message) -> btserde::Result<()> {
         write_to(&msg, &mut self.stdin)?;
-        self.stdin.flush().map_err(serde_block_tree::Error::Io)?;
+        self.stdin.flush().map_err(btserde::Error::Io)?;
         Ok(())
     }
 

+ 0 - 1
crates/sandbox/.vscode

@@ -1 +0,0 @@
-../.vscode

+ 0 - 1
crates/serde-block-tree/.vscode

@@ -1 +0,0 @@
-../.vscode

+ 0 - 75
crates/serde-block-tree/Cargo.lock

@@ -1,75 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
-dependencies = [
- "unicode-xid",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "serde"
-version = "1.0.136"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde-big-array"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3323f09a748af288c3dc2474ea6803ee81f118321775bffa3ac8f7e65c5e90e7"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "serde-block-tree"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde-big-array",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.136"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.90"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"

+ 0 - 1
crates/test-harness/.vscode

@@ -1 +0,0 @@
-../.vscode

+ 0 - 107
crates/test-harness/Cargo.lock

@@ -1,107 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "harness"
-version = "0.0.1"
-dependencies = [
- "log",
- "serde",
- "serde-block-tree",
-]
-
-[[package]]
-name = "log"
-version = "0.4.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "serde"
-version = "1.0.138"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde-big-array"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3323f09a748af288c3dc2474ea6803ee81f118321775bffa3ac8f7e65c5e90e7"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "serde-block-tree"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde-big-array",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.138"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.98"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "test-harness"
-version = "0.1.0"
-dependencies = [
- "harness",
- "serde-block-tree",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"

+ 1 - 1
crates/test-harness/Cargo.toml

@@ -6,5 +6,5 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-serde-block-tree = { path = "../serde-block-tree" }
+btserde = { path = "../btserde" }
 harness = { path = "../harness" }

+ 0 - 12
crates/test-harness/src/lib.rs

@@ -1,12 +0,0 @@
-#[cfg(test)]
-use std::{
-    io::{self, Write}
-};
-use harness::Message;
-use serde_block_tree::{write_to, read_from};
-
-#[test]
-fn echo() {
-    unimplemented!();
-}
-

+ 0 - 1
crates/web-host/.vscode

@@ -1 +0,0 @@
-../.vscode