keyfork/docs/src/dev-guide/index.md

95 lines
3.0 KiB
Markdown

{{#include ../links.md}}
# Writing Binaries
### Binaries - Porcelain and Plumbing
Binaries are split into two categories, porcelain (such as `keyfork`) and
plumbing (just about everything else). Porcelain binaries include what can be
called "the kitchen sink". They offer support for everything - an intuitive
interface, automatic `keyforkd` management, interconnectivity between
derivation utilities and provisioning utilities, and the ability to read from
and write to a configuration file. Plumbing binaries, on the other hand, are
often very rough around the edges and pull in as few dependencies as possible.
Usually, only cryptographic functionality (such as `sequoia-openpgp` or
`dalek-ed25519`) or hardware integration libraries (such as `openpgp-card`) are
included.
### Writing Binaries
Crates can be either a library, or a library and binary, but should never be
just a binary. When creating a crate with a binary, the `main.rs` file should
be designed to validate arguments, load any necessary system resources, and
call a separate exposed function to do the heavy lifting. The following example
was taken from `keyfork-shard` to demonstrate how a program can validate
arguments, parse input, and stream an output.
```rust
use std::{
collections::VecDeque,
env,
io::{stdin, stdout},
path::PathBuf,
process::ExitCode,
str::FromStr,
};
use keyfork_shard::openpgp::{combine, discover_certs, openpgp::Cert};
use sequoia_openpgp as openpgp;
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
fn validate(threshold: &str, key_discovery: &str) -> Result<(u8, Vec<Cert>)> {
let threshold = u8::from_str(threshold)?;
let key_discovery = PathBuf::from(key_discovery);
// Verify path exists
std::fs::metadata(&key_discovery)?;
// Load certs from path
let certs = discover_certs(key_discovery)?;
Ok((threshold, certs))
}
fn run() -> Result<()> {
let mut args = env::args();
let program_name = args.next().expect("program name");
let args = args.collect::<Vec<_>>();
let (threshold, cert_list) = match args.as_slice() {
[threshold, key_discovery] => validate(threshold, key_discovery)?,
_ => panic!("Usage: {program_name} threshold key_discovery"),
};
let encrypted_messages = parse_messages(stdin())?;
let encrypted_metadata = encrypted_messages
.pop_front()
.expect("any pgp encrypted message");
combine(
threshold,
cert_list,
encrypted_metadata,
encrypted_messages.into(),
stdout(),
)?;
Ok(())
}
fn main() -> ExitCode {
let result = run();
if let Err(e) = result {
eprintln!("Error: {e}");
return ExitCode::FAILURE;
}
ExitCode::SUCCESS
}
```
Designing binaries with this format makes it easier to load them to the Keyfork
porcelain binary, since the porcelain can call `combine()` with arguments that
it has parsed using its own configuration systems, using a `String` as a `mut
Write` as necessary.