Parcourir la source

* Added a script for building docs for the entire workspace.
* Added more doc comments to `btlib`.

Matthew Carr il y a 1 an
Parent
commit
159cb2606d

+ 1 - 11
Cargo.toml

@@ -1,15 +1,5 @@
 [workspace]
-members = [
-    "crates/btlib",
-    "crates/btlib-tests",
-    "crates/btmsg",
-    "crates/btserde",
-    "crates/swtpm-harness",
-    "crates/btfproto",
-    "crates/btfproto-tests",
-    "crates/btfuse",
-    "crates/btfsd",
-]
+members = ["crates/*"]
 
 [profile.bench]
 debug = true

+ 47 - 3
README.md

@@ -1,9 +1,46 @@
 # Blocktree
-Blocktree is a platform for writing distributed internet services. It aims to make it easy for
-anyone to spin-up servers to host the online services they rely on.
+Blocktree is a platform for writing distributed internet services. It aims to make it easy
+to spin-up a network of servers while also providing a high level of security.
+
+The goals of the system include the following:
+- Provide a message passing IPC mechanism which is high performance and cryptographically secure.
+This interface must support fire-and-forget, remote procedure call, and pub-sub messaging patterns.
+- Enable applications to define protocols in terms of the messages they send and receive
+(e.g. [session types](https://www.doc.ic.ac.uk/~yoshida/multiparty/multiparty.pdf))
+and implement a runtime which checks for protocol adherence.
+- Implement a network file system which is highly-available, distributed, and which allows
+client-side encryption.
+- Perform the cryptographic key management necessary to implement access control.
+- Use the network file system to orchestrate OCI containers and Wasm modules.
+
+Non-goals of the systems include:
+- Writing device drivers and controlling hardware.
+- Providing a framework for building frontend apps.
+- Communication over non-IP networks.
+
+## Crates
+Blocktree is broken into the following main crates:
+* [btlib](./btlib/index.html): Contains common traits and structs. The cryptographic code
+lives in the [crypto](./btlib/crypto/index.html) module. TPM support is provided
+by the [tpm](./btlib/crypto/tpm/index.html) module.
+* [btserde](./btserde/index.html): Defines the serde compact binary serialization format used to
+store data on disk and transmit over the network.
+* [btmsg](./btmsg/index.html): Defines the message passing interface.
+* [btfproto](./btfproto/index.html): Defines the message protocol used to communicate with file
+servers.
+* [btfsd](./btfsd/index.html): Implements a file server daemon.
+* [btfuse](./btfuse/index.html): Implements a FUSE daemon which allows local or remote file systems
+to be mounted.
+
+The remaining crates contain tests and code which facilitates them.
+* [swtpm-harness](./swtpm_harness/index.html): Contains a struct for controlling a child process
+running [Stephan Berger's swtpm](https://github.com/stefanberger/swtpm) daemon, a program which
+implements the TPM 2.0 interface.
+* [btlib-tests](./btlib_tests/index.html): Tests for [btlib](./btlib/index.html).
+* [btfproto-tests](./btfproto_tests/index.html): Tests for [btfproto](./btfproto/index.html).
 
 ## Building
-Blocktree requires the nightly rust compiler to be build. The preferred way of installing this is
+Blocktree requires the nightly rust compiler. The preferred way of installing this is
 using [rustup](https://rustup.rs/). Once you have rustup installed you can install the nightly
 toolchain with `rustup toolchain install nightly`.
 
@@ -11,6 +48,13 @@ Building is as simple as executing `cargo build` in the root of this repository.
 can be run with `cargo test`. In order to run the tests `swtpm`, `libtss2` and `libfuse` need to be
 installed.
 
+## Design Principles
+- The blocktree is the network, and is used for translating blocktree paths to IP addresses.
+- IPC is performed by passing messages.
+- Messages are addressed using blocktree paths.
+- Processes are cryptographically bound to the path where their able to receive messages.
+- File system access is performed by passing messages to file servers.
+
 ## Performance Measurement with flamegraph on Linux
 In order to use the `flamegraph` crate to on Linux you need to setup unprivileged access to the
 `perf` utility by your user account. After installing `perf` (on Arch with `pacman -S perf`),

+ 1 - 1
crates/btfproto/src/msg.rs

@@ -158,7 +158,7 @@ impl BitOr<libc::mode_t> for FileType {
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
 #[repr(i32)]
-/// The generators for the group of [Flag]s. These are mostly copied from [libc], save for
+/// The generators for the group of [Flags]. These are mostly copied from [libc], save for
 /// several custom values. Note that the presence of a flag in this enum does not guarantee
 /// it's supported.
 pub enum FlagValue {

+ 1 - 0
crates/btfuse/Cargo.toml

@@ -2,6 +2,7 @@
 name = "btfuse"
 version = "0.1.0"
 edition = "2021"
+build = "build.rs"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 

+ 4 - 0
crates/btfuse/build.rs

@@ -0,0 +1,4 @@
+fn main() {
+    // This causes the build to be re-run if the README changes.
+    println!("cargo:rerun-if-changed=../../README.md");
+}

+ 11 - 1
crates/btlib/src/accessor.rs

@@ -1,4 +1,7 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
+
+//! This module contains the [Accessor] type.
+
 use positioned_io::{ReadAt, Size, WriteAt};
 use std::io::{self, Read, Seek, SeekFrom, Write};
 
@@ -20,27 +23,34 @@ mod private {
     }
 
     impl<T: ReadAt + Sectored + AsRef<BlockMeta> + Size> Accessor<T> {
+        /// Creates a new [Accessor] by wrapping the given type.
         pub fn new(inner: T) -> Result<Accessor<T>> {
             let meta: &BlockMeta = inner.as_ref();
             let key = meta.body.block_key()?.clone();
             let inner = SecretStream::new(key).try_compose(Cursor::new(inner))?;
             Ok(Self {
-                inner: SectoredBuf::new().try_compose(inner)?,
+                inner: SectoredBuf::new(inner)?,
             })
         }
     }
 
     impl<T: Size> Accessor<T> {
+        /// Returns a reference to the inner type.
         pub fn get_ref(&self) -> &T {
             self.inner.get_ref().get_ref().get_ref()
         }
 
+        /// Returns a mutable reference to the inner type.
         pub fn get_mut(&mut self) -> &mut T {
             self.inner.get_mut().get_mut().get_mut()
         }
     }
 
     impl<T: Size + ReadAt + AsRef<BlockMeta>> Accessor<T> {
+        /// Returns a reference to the internal buffer. The returned slice starts at the given
+        /// offset in the stream and is no longer than the given size, though it may be shorter in
+        /// the case where the stream's total length is shorter than `offset + size`, or if there
+        /// are fewer than `size` bytes buffered after `offset`.
         pub fn get_buf(&self, offset: u64, size: u64) -> Result<&[u8]> {
             self.inner.get_buf(offset, size)
         }

+ 15 - 2
crates/btlib/src/block_path.rs

@@ -1,4 +1,7 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
+
+//! Contains the [BlockPath] type.
+
 use crate::{
     crypto::{BtHasher, HashKind},
     Principal, Result,
@@ -7,17 +10,19 @@ use core::hash::Hash;
 use serde::{Deserialize, Serialize};
 use std::{fmt::Display, hash::Hasher};
 
-pub use private::{BlockPath, BlockPathError};
+pub use private::{BlockPath, BlockPathError, RelBlockPath};
 
 mod private {
     use super::*;
 
+    /// Represents a relative block path.
     #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default, Hash)]
     pub struct RelBlockPath {
         components: Vec<String>,
     }
 
     impl RelBlockPath {
+        /// Returns an iterator over the components of this relative path.
         pub fn components(&self) -> impl Iterator<Item = &str> {
             self.components.iter().map(|e| e.as_str())
         }
@@ -36,6 +41,7 @@ mod private {
         /// The limit, in bytes, of a path's length.
         const BYTE_LIMIT: usize = 4096;
 
+        /// Returns a path with the given root and the given components.
         pub fn new(root: Principal, components: Vec<String>) -> BlockPath {
             BlockPath { root, components }
         }
@@ -81,7 +87,7 @@ mod private {
             Ok(())
         }
 
-        /// Returns true if `other` is a subpath of this `Path`.
+        /// Returns true if `other` is a subpath of this path.
         pub fn contains(&self, other: &BlockPath) -> bool {
             if self.root != other.root {
                 return false;
@@ -100,30 +106,37 @@ mod private {
             true
         }
 
+        /// Returns a reference to the root of this path.
         pub fn root(&self) -> &Principal {
             &self.root
         }
 
+        /// Returns a mutable reference to the root of this path.
         pub fn mut_root(&mut self) -> &mut Principal {
             &mut self.root
         }
 
+        /// Returns an iterator over the components in this path.
         pub fn components(&self) -> impl Iterator<Item = &str> {
             self.components.iter().map(|e| e.as_str())
         }
 
+        /// Returns an iterator over mutable references to the components in this path.
         pub fn mut_components(&mut self) -> impl Iterator<Item = &mut String> {
             self.components.iter_mut()
         }
 
+        /// Pushes a new component onto the end of this path.
         pub fn push_component(&mut self, component: String) {
             self.components.push(component)
         }
 
+        /// Pops a component off the end of this path.
         pub fn pop_component(&mut self) -> Option<String> {
             self.components.pop()
         }
 
+        /// Calculates the [RelBlockPath] representing this path relative to `rel_to`.
         pub fn relative_to(&self, rel_to: &BlockPath) -> Result<RelBlockPath> {
             if self.root != rel_to.root || self.components.len() < rel_to.components.len() {
                 return Err(BlockPathError::NotContained.into());

+ 8 - 4
crates/btlib/src/buf_reader.rs

@@ -1,4 +1,7 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
+
+//! This module contains the [BufReader] type.
+
 use log::error;
 use positioned_io::Size;
 use std::io::{self, Cursor, Read, Seek, SeekFrom};
@@ -12,12 +15,15 @@ mod private {
 
     use super::*;
 
+    /// Nearly identical to [std::io::BufReader] but allows a buffer to be reused between
+    /// instantiations.
     pub struct BufReader<T> {
         cursor: Cursor<Vec<u8>>,
         reader: T,
     }
 
     impl<T> BufReader<T> {
+        /// Create a new [BufReader] which contains the given buffer and reader type.
         pub fn with_buf(buf: Vec<u8>, reader: T) -> Result<BufReader<T>> {
             Ok(Self {
                 cursor: Self::make_cursor(buf)?,
@@ -25,14 +31,11 @@ mod private {
             })
         }
 
+        /// Returns a reference to the inner reader.
         pub fn get_ref(&self) -> &T {
             &self.reader
         }
 
-        pub fn get_mut(&mut self) -> &mut T {
-            &mut self.reader
-        }
-
         /// Extracts the buffer from this [BufReader]. The [BufReader] which is returned contains
         /// an empty buffer.
         pub fn take_buf(mut self) -> (Self, Vec<u8>) {
@@ -60,6 +63,7 @@ mod private {
     }
 
     impl<T: Sectored> BufReader<T> {
+        /// Creates a new [BufReader] containing the given reader type and a new buffer.
         pub fn new(reader: T) -> Result<BufReader<T>> {
             let sect_sz = reader.sector_sz();
             Ok(Self {

+ 11 - 0
crates/btlib/src/collections/bijection.rs

@@ -5,28 +5,35 @@ use super::HashMapWithDefault;
 use log::error;
 use std::{borrow::Borrow, hash::Hash};
 
+/// Implements a bijection (one-to-one function) between a set of keys and a set of values.
+/// Mapping from key to value and from value to key are both O(1) operations.
 pub struct Bijection<K, V> {
     k2v: HashMapWithDefault<K, V>,
     v2k: HashMapWithDefault<V, K>,
 }
 
 impl<K, V> Bijection<K, V> {
+    /// Creates a new [Bijection] with the given default key and value.
     pub fn new(default_key: K, default_value: V) -> Self {
         let k2v = HashMapWithDefault::new(default_value);
         let v2k = HashMapWithDefault::new(default_key);
         Self { k2v, v2k }
     }
 
+    /// Returns a reference to the hash table which maps keys to values.
     pub fn k2v(&self) -> &HashMapWithDefault<K, V> {
         &self.k2v
     }
 
+    /// Returns a reference to the hash table which maps values to keys.
     pub fn v2k(&self) -> &HashMapWithDefault<V, K> {
         &self.v2k
     }
 }
 
 impl<K: Clone + Hash + Eq, V: Clone + Hash + Eq> Bijection<K, V> {
+    /// Inserts the given `(key, value)` pair. If this replaces an existing pair, then the old
+    /// pair is returned.
     pub fn insert(&mut self, key: K, value: V) -> Option<(K, V)> {
         let key_clone = key.clone();
         let value_clone = value.clone();
@@ -44,6 +51,8 @@ impl<K: Clone + Hash + Eq, V: Clone + Hash + Eq> Bijection<K, V> {
 }
 
 impl<K: Hash + Eq, V> Bijection<K, V> {
+    /// Returns the value associated with the given key. If no value is associated, then the default
+    /// value is returned.
     pub fn value<Q: ?Sized + Hash + Eq>(&self, key: &Q) -> &V
     where
         K: Borrow<Q>,
@@ -53,6 +62,8 @@ impl<K: Hash + Eq, V> Bijection<K, V> {
 }
 
 impl<K, V: Hash + Eq> Bijection<K, V> {
+    /// Returns the key associated with the given value. If no key is associated, then the default
+    /// key is returned.
     pub fn key<R: ?Sized + Hash + Eq>(&self, value: &R) -> &K
     where
         V: Borrow<R>,

+ 11 - 3
crates/btlib/src/collections/hash_map_with_default.rs

@@ -1,16 +1,18 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
-//! This module defines [HashMapWithDefault], which is a newtype wrapping a [HashMap] which allows
-//! a default value to be defined.
-//! When a key is not found in the [HashMap] the default value is returned, instead of [None].
+//! This module defines [HashMapWithDefault].
 
 use std::{borrow::Borrow, collections::HashMap, hash::Hash, ops::Index};
 
+/// A newtype wrapping a [HashMap] which allows
+/// a default value to be defined.
+/// When a key is not found in the [HashMap] the default value is returned, instead of [None].
 pub struct HashMapWithDefault<K, V> {
     hash_map: HashMap<K, V>,
     default: V,
 }
 
 impl<K, V> HashMapWithDefault<K, V> {
+    /// Create a new hash map with the given default value.
     pub fn new(default: V) -> Self {
         Self {
             hash_map: HashMap::new(),
@@ -18,24 +20,30 @@ impl<K, V> HashMapWithDefault<K, V> {
         }
     }
 
+    /// Get a reference to the inner [HashMap].
     pub fn get_ref(&self) -> &HashMap<K, V> {
         &self.hash_map
     }
 
+    /// Get a mutable reference to the inner [HashMap].
     pub fn get_mut(&mut self) -> &mut HashMap<K, V> {
         &mut self.hash_map
     }
 
+    /// Returns a reference to the default value.
     pub fn default(&self) -> &V {
         &self.default
     }
 
+    /// Returns a mutable reference to the default value.
     pub fn mut_default(&mut self) -> &mut V {
         &mut self.default
     }
 }
 
 impl<K: Hash + Eq, V> HashMapWithDefault<K, V> {
+    /// Lookup up the item with the given key in the hash table. If no value is found then the
+    /// default is returned.
     pub fn get<Q: ?Sized + Hash + Eq>(&self, index: &Q) -> &V
     where
         K: Borrow<Q>,

+ 5 - 0
crates/btlib/src/crypto/sign_stream.rs

@@ -1,4 +1,9 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
+
+//! This module contains the [SignStream] type.
+//! This is currently not being used as it's lower performance, and doesn't support all of the
+//! features of [MerkleStream].
+ 
 pub use private::SignStream;
 
 mod private {

+ 6 - 0
crates/btlib/src/drop_trigger.rs

@@ -1,14 +1,20 @@
+//! Contains the [DropTrigger] type.
+
+/// Calls a closure when it's dropped, unless it's disarmed first.
+/// This struct is useful for implementing cleanup operations.
 pub struct DropTrigger<F: FnOnce()> {
     trigger: Option<F>,
 }
 
 impl<F: FnOnce()> DropTrigger<F> {
+    /// Creates a [DropTrigger] which will call the given closure when dropped.
     pub fn new(trigger: F) -> Self {
         Self {
             trigger: Some(trigger),
         }
     }
 
+    /// Disarms this [DropTrigger] so that its closure will not be called when when it's dropped.
     pub fn disarm(&mut self) -> bool {
         self.trigger.take().is_some()
     }

+ 3 - 0
crates/btlib/src/error.rs

@@ -11,6 +11,8 @@ use std::{fmt::Display, io};
 
 use crate::Decompose;
 
+/// Creates a new [Error], which contains a stacktrace captured at the point where this macro
+/// was evaluated.
 #[macro_export]
 macro_rules! bterr {
     ($msg:literal $(,)?) => { $crate::Error::new(anyhow::anyhow!($msg)) };
@@ -18,6 +20,7 @@ macro_rules! bterr {
     ($fmt:expr, $($arg:tt)*) => { $crate::Error::new(anyhow::anyhow!($fmt, $($arg)*)) };
 }
 
+/// Ensures that an expression evaluates to true, and if it does'nt, returns an error.
 #[macro_export]
 macro_rules! btensure {
     ($cond:expr, $msg:literal $(,)?) => {

+ 71 - 36
crates/btlib/src/lib.rs

@@ -1,4 +1,10 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
+
+//! This crate contains the core structs and traits used by Blocktree.
+//! The [crypto] module contains all of the cryptographic primitives used to implement this system.
+//! Most of the functionality for accessing blocks is defined by the [Block] trait. The
+//! [BlockMeta] struct is the type which implements block metadata.
+
 pub mod accessor;
 mod block_path;
 pub mod buf_reader;
@@ -43,7 +49,7 @@ use std::{
 use strum_macros::{Display, EnumDiscriminants, FromRepr};
 
 use accessor::Accessor;
-pub use block_path::{BlockPath, BlockPathError};
+pub use block_path::{BlockPath, BlockPathError, RelBlockPath};
 use crypto::{
     AsymKeyPub, Ciphertext, ConcretePub, Creds, CredsPub, Decrypter, DecrypterExt, EncrypterExt,
     HashKind, MerkleStream, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind, VarHash,
@@ -353,28 +359,33 @@ impl AsMut<BlockMeta> for &mut BlockMeta {
     }
 }
 
+/// A wrapper around [positioned_io::SizeCursor] which implements [Split].
 #[derive(Debug)]
 pub struct Cursor<T: Size> {
     cursor: positioned_io::SizeCursor<T>,
 }
 
 impl<T: Size> Cursor<T> {
+    /// Creates a new [Cursor] containing the given inner stream.
     pub fn new(inner: T) -> Cursor<T> {
         Self {
             cursor: positioned_io::SizeCursor::new(inner),
         }
     }
 
+    /// Create a new [Cursor] containing the given inner stream and with the given position.
     pub fn new_pos(inner: T, pos: u64) -> Cursor<T> {
         Self {
             cursor: positioned_io::SizeCursor::new_pos(inner, pos),
         }
     }
 
+    /// Returns a reference to the inner stream.
     pub fn get_ref(&self) -> &T {
         self.cursor.get_ref()
     }
 
+    /// Returns a mutable reference to the inner stream.
     pub fn get_mut(&mut self) -> &mut T {
         self.cursor.get_mut()
     }
@@ -426,6 +437,7 @@ impl<T: Size> Size for Cursor<T> {
     }
 }
 
+/// A zero length slice of bytes.
 pub const EMPTY_SLICE: &[u8] = &[0u8; 0];
 
 impl<T: Size> Split<Cursor<&'static [u8]>, T> for Cursor<T> {
@@ -535,11 +547,14 @@ pub type Inode = u64;
 /// A unique identifier for a block.
 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Hash)]
 pub struct BlockId {
+    /// The identifier of the server generation the block is stored in.
     pub generation: u64,
+    /// The identifier of the inode on the server generation containing the block.
     pub inode: Inode,
 }
 
 impl BlockId {
+    /// Creates a new [BlockId] with the given values.
     pub fn new(generation: u64, inode: Inode) -> BlockId {
         BlockId { generation, inode }
     }
@@ -680,7 +695,7 @@ pub struct BlockMetaBody {
 
     #[serde(skip)]
     /// The path in the blocktree where this block is located. This is initialized in
-    /// BlockStream::new.
+    /// [BlockStream::new].
     path: BlockPath,
     #[serde(skip)]
     /// The cleartext block key.
@@ -724,6 +739,11 @@ impl BlockMetaBody {
         }
     }
 
+    /// The given closure is called with the [BlockMetaSecrets] contained in this struct.
+    /// Whatever the closure returns is returned by this method (unless an error occurs).
+    /// The secrets are decrypted, if needed, and after the closure returns they are updated.
+    /// It's best to use the [BlockMetaBody::secrets] method if all you need is read
+    /// access.
     pub fn access_secrets<T, F: FnOnce(&mut BlockMetaSecrets) -> Result<T>>(
         &mut self,
         accessor: F,
@@ -739,7 +759,11 @@ impl BlockMetaBody {
         Ok(output)
     }
 
-    pub fn decrypt_secrets(&mut self) -> Result<()> {
+    /// Decrypts the [BlockMetaSecrets] in this struct using the block key. After this method is
+    /// called the [BlockMetaBody::secrets] method can be called to cheaply access the secrets.
+    /// This method should be called by [BlockStream] in its new method, so it shouldn't need to be
+    /// called after the fact.
+    fn decrypt_secrets(&mut self) -> Result<()> {
         if self.secrets_struct.is_none() {
             let block_key = self.block_key()?;
             self.secrets_struct = Some(block_key.ser_decrypt(&self.secrets)?);
@@ -747,37 +771,42 @@ impl BlockMetaBody {
         Ok(())
     }
 
+    /// Returns a reference to the secrets in this struct. This is very cheap operation, just
+    /// copying a reference.
     pub fn secrets(&self) -> Result<&BlockMetaSecrets> {
         self.secrets_struct
             .as_ref()
             .ok_or_else(|| bterr!("secrets have not been decrypted"))
     }
 
+    /// Returns a reference to the block key.
     pub fn block_key(&self) -> Result<&SymKey> {
         self.block_key
             .as_ref()
             .ok_or_else(|| bterr!(BlockError::NoBlockKey))
     }
 
-    pub fn mut_block_key(&mut self) -> &mut Option<SymKey> {
-        &mut self.block_key
-    }
-
+    /// Returns the ID of the block this metadata is part of.
     pub fn block_id(&self) -> Result<&BlockId> {
         let secrets = self.secrets()?;
         Ok(secrets.block_id())
     }
 
+    /// Returns a reference to the integrity value stored in this struct. This value represents the
+    /// authenticated value of the block's contents.
     pub fn integrity(&self) -> Option<&[u8]> {
         self.integrity.as_ref().map(|hash| hash.as_slice())
     }
 
+    /// Decrypts the readcap for the given [Creds] and uses the resulting plaintext as the block
+    /// key.
     pub fn use_readcap_for<C: Creds>(&mut self, creds: &C) -> Result<&SymKey> {
         let block_key = self.readcaps.get(creds)?;
         self.block_key = Some(block_key);
         self.block_key()
     }
 
+    /// Adds a new readcap for the given [Creds].
     pub fn add_readcap_for<C: CredsPub>(&mut self, creds: C) -> Result<()> {
         let block_key = self
             .block_key
@@ -786,12 +815,14 @@ impl BlockMetaBody {
         self.readcaps.set(creds, block_key)
     }
 
+    /// Returns a reference to the [BlockPath] of the block this metadata is for.
     pub fn path(&self) -> &BlockPath {
         &self.path
     }
 
-    pub fn set_path(&mut self, path: BlockPath) {
-        self.path = path
+    /// Returns a mutable reference to the [BlockPath] of the block this metadata is for.
+    pub fn path_mut(&mut self) -> &mut BlockPath {
+        &mut self.path
     }
 }
 
@@ -803,16 +834,19 @@ pub struct BlockMeta {
 }
 
 impl BlockMeta {
+    /// Creates a new [BlockMeta] struct which is authenticated by the given credentials.
     pub fn new<C: Creds>(creds: &C) -> Result<BlockMeta> {
         let body = BlockMetaBody::new(creds)?;
         let sig = Signature::empty(body.signing_key.scheme());
         Ok(BlockMeta { body, sig })
     }
 
+    /// Returns a reference to the [BlockMetaBody] contained in this struct.
     pub fn body(&self) -> &BlockMetaBody {
         self.as_ref()
     }
 
+    /// Returns a mutable reference to the [BlockMetaBody] contained in this struct.
     pub fn mut_body(&mut self) -> &mut BlockMetaBody {
         self.as_mut()
     }
@@ -830,6 +864,9 @@ impl AsMut<BlockMetaBody> for BlockMeta {
     }
 }
 
+/// A stream which is responsible for managing the metadata associated with a block, including
+/// validating and authenticating it. This type shouldn't be instantiated direction, instead a
+/// [BlockOpenOptions] struct is used.
 pub struct BlockStream<T, C> {
     trailered: Trailered<T, BlockMeta>,
     meta: BlockMeta,
@@ -839,6 +876,8 @@ pub struct BlockStream<T, C> {
 }
 
 impl<T: ReadAt + Sectored + Size, C: Creds> BlockStream<T, C> {
+    /// Creates a new [BlockStream] using the given inner stream, [Creds], an optional parent
+    /// key, and [BlockPath].
     fn new(
         inner: T,
         creds: C,
@@ -972,6 +1011,8 @@ impl<T, C> Decompose<(T, C)> for BlockStream<T, C> {
     }
 }
 
+/// Describes how to open a block, including the inner stream from which the block can be read and
+/// the credentials to use to open it.
 pub struct BlockOpenOptions<T, C> {
     inner: T,
     creds: C,
@@ -981,6 +1022,7 @@ pub struct BlockOpenOptions<T, C> {
 }
 
 impl BlockOpenOptions<(), ()> {
+    /// Creates a new empty [BlockOpenOptions].
     pub fn new() -> BlockOpenOptions<(), ()> {
         BlockOpenOptions {
             inner: (),
@@ -993,6 +1035,7 @@ impl BlockOpenOptions<(), ()> {
 }
 
 impl<T, C> BlockOpenOptions<T, C> {
+    /// Configures the given stream to be used for reading and writing the block.
     pub fn with_inner<U>(self, inner: U) -> BlockOpenOptions<U, C> {
         BlockOpenOptions {
             inner,
@@ -1003,6 +1046,7 @@ impl<T, C> BlockOpenOptions<T, C> {
         }
     }
 
+    /// Configures the given [Creds] to be used to open the block.
     pub fn with_creds<D>(self, creds: D) -> BlockOpenOptions<T, D> {
         BlockOpenOptions {
             inner: self.inner,
@@ -1013,26 +1057,33 @@ impl<T, C> BlockOpenOptions<T, C> {
         }
     }
 
+    /// Specifies whether or not the block contents will be encrypted.
     pub fn with_encrypt(mut self, encrypt: bool) -> Self {
         self.encrypt = encrypt;
         self
     }
 
+    /// Specifies the path of the block. If the block is new, this will be the path it's created
+    /// at. If an existing block is being opened, then this is used for validating the block.
     pub fn with_block_path(mut self, block_path: BlockPath) -> Self {
         self.block_path = Some(block_path);
         self
     }
 
+    /// Configures the given parent key to be used for unlocking the block key.
     pub fn with_parent_key(mut self, parent_key: Option<SymKey>) -> Self {
         self.parent_key = parent_key;
         self
     }
 }
 
+/// The composition of a [BlockStream] in a [MerkleStream].
 pub type ConcreteBlock<T, C> = MerkleStream<BlockStream<T, C>>;
+/// A [ConcreteBlock] which stores its data in a file in the filesystem.
 pub type FileBlock<C> = ConcreteBlock<std::fs::File, C>;
 
 impl<T: ReadAt + WriteAt + Size + Sectored + 'static, C: Creds + 'static> BlockOpenOptions<T, C> {
+    /// Opens the block without wrapping it in an [Accessor].
     pub fn open_bare(self) -> Result<ConcreteBlock<T, C>> {
         let block_path = self.block_path.ok_or(BlockError::NoBlockPath)?;
         let stream = BlockStream::new(self.inner, self.creds, self.parent_key, block_path)?;
@@ -1041,6 +1092,7 @@ impl<T: ReadAt + WriteAt + Size + Sectored + 'static, C: Creds + 'static> BlockO
         Ok(stream)
     }
 
+    /// Opens the block and wraps it in an [Accessor] before returning it.
     pub fn open(self) -> Result<Accessor<ConcreteBlock<T, C>>> {
         let stream = self.open_bare()?;
         let stream = Accessor::new(stream)?;
@@ -1181,16 +1233,22 @@ pub struct AuthzAttrs {
     pub supp_gids: Vec<u32>,
 }
 
+/// A record which stores the authorization attributes, credentials, writecap, and IP address of a
+/// process.
 #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
 pub struct IssuedProcRec {
+    /// The last known IP address of the process.
     pub addr: IpAddr,
+    /// The public credentials of the process.
     pub pub_creds: ConcretePub,
+    /// The writecap that was issued to the process.
     pub writecap: Writecap,
+    /// The authorization attributes associated with the process.
     pub authz_attrs: AuthzAttrs,
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
 /// Structure stored in process blocks for keeping track of process credentials and location.
+#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
 pub enum ProcRec {
     Requested {
         addr: IpAddr,
@@ -1446,28 +1504,6 @@ impl Sub<Epoch> for Epoch {
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone)]
 pub struct FragmentSerial(u32);
 
-/// A struct which may contain a closure. When this struct is dropped, if it contains a closure
-/// then that closure is called. This closure can be removed by calling `disarm`.
-pub struct DropTrigger<F: FnOnce()>(Option<F>);
-
-impl<F: FnOnce()> DropTrigger<F> {
-    pub fn new(trigger: F) -> DropTrigger<F> {
-        DropTrigger(Some(trigger))
-    }
-
-    pub fn disarm(&mut self) {
-        self.0.take();
-    }
-}
-
-impl<F: FnOnce()> Drop for DropTrigger<F> {
-    fn drop(&mut self) {
-        if let Some(trigger) = self.0.take() {
-            trigger()
-        }
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use btserde::{from_vec, to_vec};
@@ -1563,7 +1599,7 @@ mod tests {
             stream.assert_root_integrity().unwrap();
             let stream = PioCursor::new(stream);
             let stream = SecretStream::new(block_key).try_compose(stream).unwrap();
-            SectoredBuf::new().try_compose(stream).unwrap()
+            SectoredBuf::new(stream).unwrap()
         }
 
         fn into_vec(stream: EncBlock) -> Vec<u8> {
@@ -1662,9 +1698,8 @@ mod tests {
                 .with_block_path(path)
                 .open()
                 .expect("failed to open block");
-            block
-                .mut_meta_body()
-                .set_path(self.node_creds.writecap().unwrap().body.path.clone());
+            *block.mut_meta_body().path_mut() =
+                self.node_creds.writecap().unwrap().body.path.clone();
             block
         }
 

+ 5 - 1
crates/btlib/src/log.rs

@@ -1,11 +1,15 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
+
+//! Helpers for configuring logging.
+
 use chrono;
 use env_logger;
 use std::io::Write;
 
+/// Extensions to [env_logger::Builder] which configure it for Blocktree crates.
 pub trait BuilderExt {
     /// Uses a standard format for log messages which includes the source file and line number
-    /// a logging statement occurred on.
+    /// a logging statement occurs on.
     fn btformat(&mut self) -> &mut Self;
 }
 

+ 72 - 124
crates/btlib/src/sectored_buf.rs

@@ -1,4 +1,7 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
+
+//! Contains the [SectoredBuf] type.
+
 use log::error;
 use positioned_io::Size;
 use safemem::write_bytes;
@@ -7,7 +10,7 @@ use std::io::{self, Read, Seek, SeekFrom, Write};
 use crate::{
     bterr, error::DisplayErr, suppress_err_if_non_zero, BlockError, BlockMeta, BoxInIoErr,
     Decompose, MetaAccess, Positioned, ReadDual, ReadExt, Result, Sectored, SeekFromExt, SizeExt,
-    Split, TryCompose, TrySeek, WriteDual, ZeroExtendable, EMPTY_SLICE,
+    Split, TrySeek, WriteDual, ZeroExtendable, EMPTY_SLICE,
 };
 
 pub use private::SectoredBuf;
@@ -29,41 +32,32 @@ mod private {
         pos: usize,
     }
 
-    impl SectoredBuf<()> {
-        pub fn new() -> SectoredBuf<()> {
-            SectoredBuf {
-                inner: (),
-                pos: 0,
+    impl<T: Sectored + Read + Seek + AsRef<BlockMeta>> SectoredBuf<T> {
+        /// Creates a new [SectoredBuf] which buffers the given stream.
+        pub fn new(inner: T) -> Result<SectoredBuf<T>> {
+            let sect_sz = inner.sector_sz();
+            let mut sectored = SectoredBuf {
+                inner,
                 buf: Vec::new(),
                 buf_start: 0,
                 dirty: false,
-            }
+                pos: 0,
+            };
+            sectored.buf.resize(sect_sz, 0);
+            sectored.inner.rewind()?;
+            sectored.fill_internal_buf()?;
+            Ok(sectored)
         }
     }
 
-    /// Returns the slice of the internal buffer which is ready to be read from.
-    /// If the buffer all the bytes in the buffer have been consumed, then the buffer is refilled.
-    /// The returned slice will be empty if and only if there are no additional bytes in the
-    /// inner stream.
-    macro_rules! readable_slice {
-        ($self:expr) => {{
-            let pos = $self.buf_pos();
-            let end = $self.buf_end();
-            if pos == end && $self.pos < $self.len() {
-                match $self.fill_internal_buf() {
-                    Ok(nread) => {
-                        if nread > 0 {
-                            Ok(&$self.buf[..$self.buf_end()])
-                        } else {
-                            Ok(&$self.buf[..0])
-                        }
-                    }
-                    Err(err) => Err(err),
-                }
-            } else {
-                Ok(&$self.buf[pos..end])
-            }
-        }};
+    impl<T: Read + Write + Seek + MetaAccess> SectoredBuf<T> {
+        /// Updates the size stored in the metadata of the block.
+        pub fn update_size(inner: &mut T, size: usize) -> Result<()> {
+            inner.mut_meta_body().access_secrets(|secrets| {
+                secrets.size = secrets.size.max(size as u64);
+                Ok(())
+            })
+        }
     }
 
     impl<T> SectoredBuf<T> {
@@ -127,12 +121,6 @@ mod private {
         }
     }
 
-    impl Default for SectoredBuf<()> {
-        fn default() -> Self {
-            Self::new()
-        }
-    }
-
     impl<T> Split<SectoredBuf<&'static [u8]>, T> for SectoredBuf<T> {
         fn split(self) -> (SectoredBuf<&'static [u8]>, T) {
             let new_self = SectoredBuf {
@@ -162,26 +150,6 @@ mod private {
         }
     }
 
-    impl<T: Sectored + Read + Seek + AsRef<BlockMeta>> TryCompose<T, SectoredBuf<T>>
-        for SectoredBuf<()>
-    {
-        type Error = crate::Error;
-        fn try_compose(self, inner: T) -> Result<SectoredBuf<T>> {
-            let sect_sz = inner.sector_sz();
-            let mut sectored = SectoredBuf {
-                inner,
-                buf: self.buf,
-                buf_start: 0,
-                dirty: false,
-                pos: 0,
-            };
-            sectored.buf.resize(sect_sz, 0);
-            sectored.inner.rewind()?;
-            sectored.fill_internal_buf()?;
-            Ok(sectored)
-        }
-    }
-
     impl<T> Sectored for SectoredBuf<T> {
         fn sector_sz(&self) -> usize {
             self.buf.len()
@@ -254,15 +222,6 @@ mod private {
         }
     }
 
-    impl<T: Read + Write + Seek + MetaAccess> SectoredBuf<T> {
-        pub fn update_size(inner: &mut T, pos: usize) -> Result<()> {
-            inner.mut_meta_body().access_secrets(|secrets| {
-                secrets.size = secrets.size.max(pos as u64);
-                Ok(())
-            })
-        }
-    }
-
     impl<T: Read + Write + Seek + MetaAccess> ZeroExtendable for SectoredBuf<T> {
         fn zero_extend(&mut self, num_zeros: u64) -> io::Result<()> {
             if num_zeros == 0 {
@@ -309,6 +268,31 @@ mod private {
         }
     }
 
+    /// Returns the slice of the internal buffer which is ready to be read from.
+    /// If the buffer all the bytes in the buffer have been consumed, then the buffer is refilled.
+    /// The returned slice will be empty if and only if there are no additional bytes in the
+    /// inner stream.
+    macro_rules! readable_slice {
+        ($self:expr) => {{
+            let pos = $self.buf_pos();
+            let end = $self.buf_end();
+            if pos == end && $self.pos < $self.len() {
+                match $self.fill_internal_buf() {
+                    Ok(nread) => {
+                        if nread > 0 {
+                            Ok(&$self.buf[..$self.buf_end()])
+                        } else {
+                            Ok(&$self.buf[..0])
+                        }
+                    }
+                    Err(err) => Err(err),
+                }
+            } else {
+                Ok(&$self.buf[pos..end])
+            }
+        }};
+    }
+
     impl<T: Read + Seek + AsRef<BlockMeta>> Read for SectoredBuf<T> {
         fn read(&mut self, mut dest: &mut [u8]) -> io::Result<usize> {
             if self.pos == self.len() {
@@ -508,8 +492,7 @@ mod tests {
     };
 
     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))
+        SectoredBuf::new(SectoredCursor::new(vec![0u8; sect_sz * sect_ct], sect_sz))
             .expect("compose for sectored buffer failed")
     }
 
@@ -553,9 +536,7 @@ mod tests {
         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 sectored = SectoredBuf::new(inner).expect("failed to compose sectored buffer");
         let mut actual = vec![0u8; expected.len()];
         sectored
             .fill_buf(actual.as_mut_slice())
@@ -659,9 +640,8 @@ mod tests {
     #[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");
+        let mut sectored =
+            SectoredBuf::new(SectoredCursor::new([0u8; LEN], LEN)).expect("compose failed");
         const BUF_LEN: usize = LEN + 1;
         sectored.write(&[1u8; BUF_LEN - 1]).expect("write failed");
         sectored.seek(SeekFrom::Start(0)).expect("seek failed");
@@ -675,9 +655,8 @@ mod tests {
     /// 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 mut sectored =
+            SectoredBuf::new(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");
@@ -690,9 +669,8 @@ mod tests {
     #[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 mut sectored =
+            SectoredBuf::new(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");
@@ -704,9 +682,7 @@ mod tests {
     #[test]
     fn read_into_count_limits_read_bytes() {
         const DATA: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), DATA.len()))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), DATA.len())).unwrap();
 
         sectored.write(&DATA).unwrap();
         sectored.rewind().unwrap();
@@ -720,9 +696,7 @@ mod tests {
     #[test]
     fn read_into_read_spans_multiple_sectors() {
         const DATA: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), DATA.len()))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), DATA.len())).unwrap();
 
         sectored.write(&DATA).unwrap();
         sectored.write(&DATA).unwrap();
@@ -740,9 +714,7 @@ mod tests {
     #[test]
     fn read_into_read_past_len() {
         const DATA: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), DATA.len()))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), DATA.len())).unwrap();
 
         sectored.write(&DATA).unwrap();
         sectored.rewind().unwrap();
@@ -757,9 +729,7 @@ mod tests {
     #[test]
     fn write_from_full_cursor() {
         const DATA: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), DATA.len()))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), DATA.len())).unwrap();
 
         let written = sectored
             .write_from(BtCursor::new(DATA), DATA.len())
@@ -773,9 +743,7 @@ mod tests {
     #[test]
     fn write_from_count_limits_bytes_read() {
         const DATA: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), DATA.len()))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), DATA.len())).unwrap();
         let mut cursor = BtCursor::new(DATA);
 
         let written = sectored.write_from(&mut cursor, DATA.len() / 2).unwrap();
@@ -795,9 +763,7 @@ mod tests {
     fn write_from_write_spans_multiple_sectors() {
         const SECT_SZ: usize = 4;
         const DATA: [u8; SECT_SZ + 1] = [0, 1, 2, 3, 4];
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), SECT_SZ))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), SECT_SZ)).unwrap();
 
         let written = sectored
             .write_from(BtCursor::new(DATA), DATA.len())
@@ -815,9 +781,7 @@ mod tests {
         const SECT_SZ: usize = 4;
         const DATA_LEN: usize = 2 * SECT_SZ;
         const DATA: [u8; DATA_LEN] = integer_array::<DATA_LEN>(0);
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), SECT_SZ))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), SECT_SZ)).unwrap();
 
         let written = sectored
             .write_from(BtCursor::new(DATA), DATA.len())
@@ -837,9 +801,7 @@ mod tests {
     #[test]
     fn seek_past_end_is_error() {
         const SECT_SZ: usize = 4;
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), SECT_SZ))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), SECT_SZ)).unwrap();
 
         let result = sectored.seek(SeekFrom::Start(1));
 
@@ -854,9 +816,7 @@ mod tests {
     #[test]
     fn seek_to_zero_when_empty() {
         const SECT_SZ: usize = 4;
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), SECT_SZ))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), SECT_SZ)).unwrap();
 
         let pos = sectored.seek(SeekFrom::Start(0)).unwrap();
 
@@ -869,9 +829,7 @@ mod tests {
         const SECT_SZ: usize = 4;
         const DATA_LEN: usize = 2 * SECT_SZ;
         const DATA: [u8; DATA_LEN] = integer_array::<DATA_LEN>(0);
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), SECT_SZ))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), SECT_SZ)).unwrap();
 
         sectored.write(&DATA).unwrap();
         const MID_FIRST: usize = SECT_SZ / 2;
@@ -896,9 +854,7 @@ mod tests {
     #[test]
     fn read_into_reads_nothing_when_at_end() {
         const SECT_SZ: usize = 8;
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), SECT_SZ))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), SECT_SZ)).unwrap();
 
         sectored.write([1u8; 6].as_slice()).unwrap();
         let mut actual = Cursor::new(Vec::new());
@@ -909,9 +865,7 @@ mod tests {
 
     #[test]
     fn zero_extend_less_than_sect_sz() {
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), 8))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), 8)).unwrap();
 
         let written = sectored.write([1u8; 4].as_slice()).unwrap();
         assert_eq!(4, written);
@@ -926,9 +880,7 @@ mod tests {
     #[test]
     fn zero_extend_multiple_sectors() {
         const SECT_SZ: usize = 8;
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), SECT_SZ))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), SECT_SZ)).unwrap();
 
         let written = sectored.write([1u8; SECT_SZ / 2].as_slice()).unwrap();
         assert_eq!(SECT_SZ / 2, written);
@@ -950,9 +902,7 @@ mod tests {
     #[test]
     fn zero_extend_multiple_sectors_with_remainder() {
         const SECT_SZ: usize = 8;
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), SECT_SZ))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), SECT_SZ)).unwrap();
 
         let written = sectored.write([1u8; SECT_SZ / 2].as_slice()).unwrap();
         assert_eq!(SECT_SZ / 2, written);
@@ -975,9 +925,7 @@ mod tests {
         const SECT_SZ: usize = crate::SECTOR_SZ_DEFAULT;
         const DIVISOR: usize = 8;
         const READ_SZ: usize = SECT_SZ / DIVISOR;
-        let mut sectored = SectoredBuf::new()
-            .try_compose(SectoredCursor::new(Vec::new(), SECT_SZ))
-            .unwrap();
+        let mut sectored = SectoredBuf::new(SectoredCursor::new(Vec::new(), SECT_SZ)).unwrap();
         let mut expected = vec![0u8; READ_SZ];
 
         for index in 0..(DIVISOR as u8 + 1) {

+ 4 - 3
crates/btlib/src/test_helpers.rs

@@ -222,9 +222,7 @@ pub fn make_block_with() -> SectoredBuf<SecretStream<PioCursor<impl Block>>> {
     let stream = SecretStream::new(block_key)
         .try_compose(PioCursor::new(stream))
         .expect("create secret stream failed");
-    SectoredBuf::new()
-        .try_compose(stream)
-        .expect("failed to compose with SectoredBuf")
+    SectoredBuf::new(stream).expect("failed to compose with SectoredBuf")
 }
 
 /// This function can be run as a test to write a new RSA key pair, as two Rust arrays,
@@ -345,6 +343,7 @@ pub fn read_check<R: Read>(mut read: R, sect_sz: usize, sect_ct: usize) {
     }
 }
 
+#[allow(dead_code)]
 pub fn write_indices<W: Write + Seek, I: Iterator<Item = usize>>(
     mut write: W,
     sect_sz: usize,
@@ -362,6 +361,7 @@ pub fn write_indices<W: Write + Seek, I: Iterator<Item = usize>>(
     write.flush().expect("flush failed");
 }
 
+#[allow(dead_code)]
 pub fn read_indices<R: Read + Seek, I: Iterator<Item = usize>>(
     mut read: R,
     sect_sz: usize,
@@ -379,6 +379,7 @@ pub fn read_indices<R: Read + Seek, I: Iterator<Item = usize>>(
     }
 }
 
+#[allow(dead_code)]
 pub fn random_indices<'a>(
     rando: &'a mut Randomizer,
     sect_ct: usize,

+ 3 - 3
crates/btmsg/src/lib.rs

@@ -17,8 +17,8 @@ use core::{
     pin::Pin,
 };
 use futures::{FutureExt, SinkExt};
-use log::{error, debug};
-use quinn::{Connection, Endpoint, RecvStream, SendStream, ConnectionError};
+use log::{debug, error};
+use quinn::{Connection, ConnectionError, Endpoint, RecvStream, SendStream};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::{
     any::Any,
@@ -524,7 +524,7 @@ impl QuicReceiver {
                         error!("error accepting stream: {err}");
                         continue;
                     }
-                }
+                },
             };
             let client_path = client_path.clone();
             let callback = callback.clone();

+ 4 - 0
doc/build-docs.sh

@@ -0,0 +1,4 @@
+#!/bin/sh
+# Builds the docs for this repo. Open "target/docs/doc/README.html" in a browser to view them.
+# NOTE: This script must be run from the "./doc" subdirectory of the repo.
+RUSTDOCFLAGS="--index-page $PWD/../README.md -Zunstable-options --markdown-css $PWD/rust.css --markdown-no-toc" cargo +nightly doc

+ 355 - 0
doc/rust.css

@@ -0,0 +1,355 @@
+/* General structure */
+
+body {
+	margin: 0 auto;
+	padding: 0 15px;
+	font-size: 18px;
+	color: #333;
+	line-height: 1.428571429;
+
+	-webkit-box-sizing: unset;
+	-moz-box-sizing: unset;
+	box-sizing: unset;
+}
+@media (min-width: 768px) {
+	body {
+		max-width: 750px;
+	}
+}
+
+h2, h3, h4, h5, h6 {
+	font-weight: 400;
+	line-height: 1.1;
+}
+h1, h2, h3 {
+	margin-top: 20px;
+	margin-bottom: 15px;
+}
+h1 {
+	margin-bottom: 20px;
+	line-height: 1.1;
+}
+h4, h5, h6 {
+	margin-top: 12px;
+	margin-bottom: 10px;
+	padding: 5px 10px;
+}
+h5, h6 {
+	color: black;
+	text-decoration: underline;
+}
+
+h1 {
+	font-size: 28px;
+	font-weight: 500;
+	padding: .1em .4em;
+	border-bottom: 2px solid #ddd;
+}
+h1.title {
+	line-height: 1.5em;
+}
+h2 {
+	font-size: 26px;
+	padding: .2em .5em;
+	border-bottom: 1px solid #ddd;
+}
+h3 {
+	font-size: 24px;
+	padding: .2em .7em;
+	border-bottom: 1px solid #DDE8FC;
+}
+h4 {
+	font-size: 22px;
+	border-bottom: none;
+}
+h5 {
+	font-size: 20px;
+}
+h6 {
+	font-size: 18px;
+}
+@media (min-width: 992px) {
+	h1 {
+		font-size: 36px;
+	}
+	h2 {
+		font-size: 30px;
+	}
+	h3 {
+		font-size: 26px;
+	}
+}
+
+nav {
+	column-count: 2;
+	-moz-column-count: 2;
+	-webkit-column-count: 2;
+	font-size: 15px;
+	margin: 0 0 1em 0;
+}
+p {
+	margin: 0 0 1em 0;
+}
+
+strong {
+	font-weight: bold;
+}
+
+em {
+	font-style: italic;
+}
+
+footer {
+	border-top: 1px solid #ddd;
+	font-size: 14px;
+	font-style: italic;
+	padding-top: 5px;
+	margin-top: 3em;
+	margin-bottom: 1em;
+}
+
+/* Links layout */
+
+a {
+	text-decoration: none;
+	color: #428BCA;
+	background: transparent;
+}
+a:hover, a:focus {
+	color: #2A6496;
+	text-decoration: underline;
+}
+a:focus {
+	outline: thin dotted #333;
+	outline: 5px auto -webkit-focus-ring-color;
+	outline-offset: -2px;
+}
+a:hover, a:active {
+	outline: 0;
+}
+
+h1 a:link, h1 a:visited, h2 a:link, h2 a:visited,
+h3 a:link, h3 a:visited, h4 a:link, h4 a:visited,
+h5 a:link, h5 a:visited {color: black;}
+
+/* Code */
+
+pre, code {
+	word-wrap: break-word;
+}
+pre {
+	border-left: 2px solid #eee;
+	white-space: pre-wrap;
+	padding-right: 0;
+	margin: 20px 0;
+	font-size: 15px;
+	word-break: break-all;
+}
+code {
+	padding: 0 2px;
+	color: #8D1A38;
+}
+pre code {
+	padding: 0;
+	font-size: inherit;
+	color: inherit;
+}
+
+a > code {
+	color: #428BCA;
+}
+
+.section-header > a > code {
+	color: #8D1A38;
+}
+
+#versioninfo {
+	text-align: center;
+	margin: 0.5em;
+	font-size: 1.1em;
+}
+@media (min-width: 992px) {
+	#versioninfo {
+		font-size: 0.8em;
+		position: fixed;
+		bottom: 0px;
+		right: 0px;
+	}
+	.white-sticker {
+		background-color: #fff;
+		margin: 2px;
+		padding: 0 2px;
+		border-radius: .2em;
+	}
+}
+#versioninfo a.hash {
+	color: gray;
+	font-size: 80%;
+}
+
+blockquote {
+	color: #000;
+	margin: 20px 0;
+	padding: 15px 20px;
+	background-color: #f2f7f9;
+	border-top: .1em solid #e5eef2;
+	border-bottom: .1em solid #e5eef2;
+}
+blockquote p {
+	font-size: 17px;
+	font-weight: 300;
+	line-height: 1.4;
+}
+blockquote p:last-child {
+	margin-bottom: 0;
+}
+
+ul ul, ol ul, ul ol, ol ol {
+	margin-bottom: 0;
+}
+dl {
+	margin-bottom: 20px;
+}
+dd {
+	margin-left: 0;
+}
+
+nav ul {
+	list-style-type: none;
+	margin: 0;
+	padding-left: 0px;
+}
+
+/* Only display one level of hierarchy in the TOC */
+nav ul ul {
+	display: none;
+}
+
+sub,
+sup {
+	font-size: 75%;
+	line-height: 0;
+	position: relative;
+}
+
+hr {
+	margin-top: 20px;
+	margin-bottom: 20px;
+	border: 0;
+	border-top: 1px solid #eeeeee;
+}
+
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+	overflow-x: auto;
+	display: block;
+}
+
+table tr.odd {
+	background: #eee;
+}
+
+table td,
+table th {
+	border: 1px solid #ddd;
+	padding: 5px;
+}
+
+/* Code snippets */
+
+a.test-arrow {
+	color: #f5f5f5
+}
+
+.unstable-feature {
+	border: 2px solid red;
+	padding: 5px;
+}
+
+@media (min-width: 1170px) {
+	pre {
+		font-size: 15px;
+	}
+}
+
+@media print {
+	* {
+		text-shadow: none !important;
+		color: #000 !important;
+		background: transparent !important;
+		box-shadow: none !important;
+	}
+	a, a:visited {
+		text-decoration: underline;
+	}
+	p a[href]:after {
+		content: " (" attr(href) ")";
+	}
+	footer a[href]:after {
+		content: "";
+	}
+	a[href^="javascript:"]:after, a[href^="#"]:after {
+		content: "";
+	}
+	pre, blockquote {
+		border: 1px solid #999;
+		page-break-inside: avoid;
+	}
+	@page {
+		margin: 2cm .5cm;
+	}
+	h1:not(.title), h2, h3 {
+		border-bottom: 0px none;
+	}
+	p, h2, h3 {
+		orphans: 3;
+		widows: 3;
+	}
+	h2, h3 {
+		page-break-after: avoid;
+	}
+	table {
+		border-collapse: collapse !important;
+	}
+	table td, table th {
+		background-color: #fff !important;
+	}
+}
+
+#keyword-table-marker + table thead { display: none; }
+#keyword-table-marker + table td { border: none; }
+#keyword-table-marker + table {
+	margin-left: 2em;
+	margin-bottom: 1em;
+}
+
+.error-described {
+	position: relative;
+}
+
+.tooltip .tooltiptext {
+	width: 120px;
+	display: none;
+	text-align: center;
+	padding: 5px 3px;
+	border-radius: 6px;
+	margin-left: 5px;
+	top: -5px;
+	left: 105%;
+	z-index: 1;
+}
+
+.tooltip:hover .tooltiptext {
+	display: inline;
+}
+
+.tooltip .tooltiptext::after {
+	content: " ";
+	position: absolute;
+	top: 50%;
+	left: 13px;
+	margin-top: -5px;
+	border-width: 5px;
+	border-style: solid;
+}