Files
lanspread/crates/lanspread-compat/src/bin/lanspread-fixture-catalog.rs
T
ddidderr 60fd7ba0c2 feat(peer)!: cut over to authenticated catalog sharing
Replace address-only trust and pushed peer state with installation identities,
SPKI-pinned QUIC, candidate-only discovery, and bounded responder-owned
protocol-8 pulls. The runtime now owns each network generation and all admitted
work through shutdown.

Add exact bundled content identities, reproducible manifest publishing,
capability-confined downloads, streaming BLAKE3 verification, quarantine and
retry, and crash-recoverable download and install transactions. Ship generated
fixture catalogs and fail closed when production manifests are absent.

The Tauri backend exposes durable sharing policy, redacted identity state, and
attempt-keyed transfer snapshots. Frontend consumption follows in the next
commit. Repository-wide test certificates and protocol-7 paths are removed.

BREAKING CHANGE: peers must use protocol 8 and exact catalog content artifacts;
protocol-7 frames and shared-certificate identities are no longer accepted.

Test Plan:
- `just test` -- passed on the completed stack (708 workspace tests)
- `just clippy` -- passed on the completed stack
- `just build` -- passed with fixture catalogs on the completed stack
- `just catalog-check-production` -- failed closed because the external
  production manifest corpus is absent
- `git diff --cached --check` -- passed
2026-08-10 13:59:18 +02:00

211 lines
6.8 KiB
Rust

//! Test-only catalog generator used by the peer-CLI acceptance fixtures.
use std::{
collections::BTreeSet,
ffi::OsString,
io::{self, Write},
path::PathBuf,
};
use lanspread_compat::catalog_publisher::fixture::{
FixtureCatalogOptions,
FixturePackage,
generate_fixture_catalog,
};
const HELP: &str = "\
Usage:
lanspread-fixture-catalog --source-catalog-db PATH --output-dir PATH --unrar PATH --game-root PATH... [--no-stream-install GAME_ID...]
This test-only tool creates a reduced game.db and sibling manifests/ from the
selected fixture packages. --no-stream-install is only for synthetic transfer
fixtures whose .eti bytes intentionally are not RAR archives.";
#[derive(Debug, Eq, PartialEq)]
enum ParseOutcome {
Help,
Options(FixtureCatalogOptions),
}
#[tokio::main]
async fn main() {
if let Err(error) = run().await {
eprintln!("error: {error:#}");
std::process::exit(1);
}
}
async fn run() -> eyre::Result<()> {
match parse_args(std::env::args_os().skip(1))? {
ParseOutcome::Help => println!("{HELP}"),
ParseOutcome::Options(options) => {
let reports = generate_fixture_catalog(&options).await?;
let stdout = io::stdout();
let mut stdout = stdout.lock();
for report in &reports {
writeln!(
stdout,
"generated fixture game_id={} game_version={} content_id={}",
report.game_id, report.game_version, report.content_id
)?;
}
writeln!(stdout, "generated fixture total={}", reports.len())?;
}
}
Ok(())
}
fn parse_args(args: impl IntoIterator<Item = OsString>) -> eyre::Result<ParseOutcome> {
let mut source_catalog_db = None;
let mut output_dir = None;
let mut unrar = None;
let mut game_roots = Vec::new();
let mut no_streamed_install = BTreeSet::new();
let mut args = args.into_iter();
while let Some(argument) = args.next() {
match argument.to_str() {
Some("--help" | "-h") => return Ok(ParseOutcome::Help),
Some("--source-catalog-db") => set_once(
&mut source_catalog_db,
next_path(&mut args, "--source-catalog-db")?,
"--source-catalog-db",
)?,
Some("--output-dir") => set_once(
&mut output_dir,
next_path(&mut args, "--output-dir")?,
"--output-dir",
)?,
Some("--unrar") => set_once(&mut unrar, next_path(&mut args, "--unrar")?, "--unrar")?,
Some("--game-root") => game_roots.push(next_path(&mut args, "--game-root")?),
Some("--no-stream-install") => {
let game_id = next_utf8(&mut args, "--no-stream-install")?;
if !no_streamed_install.insert(game_id.clone()) {
eyre::bail!("duplicate --no-stream-install game ID: {game_id}");
}
}
Some(other) => eyre::bail!("unknown argument: {other}"),
None => eyre::bail!("argument is not valid UTF-8: {argument:?}"),
}
}
let mut selected_ids = BTreeSet::new();
let packages = game_roots
.into_iter()
.map(|package_root| {
let game_id = package_root
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| eyre::eyre!("--game-root needs a UTF-8 final component"))?
.to_owned();
if !selected_ids.insert(game_id.clone()) {
eyre::bail!("duplicate --game-root game ID: {game_id}");
}
Ok(FixturePackage {
streamed_install: !no_streamed_install.contains(&game_id),
game_id,
package_root,
})
})
.collect::<eyre::Result<Vec<_>>>()?;
if packages.is_empty() {
eyre::bail!("at least one --game-root is required");
}
if let Some(game_id) = no_streamed_install
.iter()
.find(|game_id| !selected_ids.contains(*game_id))
{
eyre::bail!("--no-stream-install selects missing game root: {game_id}");
}
Ok(ParseOutcome::Options(FixtureCatalogOptions {
source_catalog_db: source_catalog_db
.ok_or_else(|| eyre::eyre!("--source-catalog-db is required"))?,
output_dir: output_dir.ok_or_else(|| eyre::eyre!("--output-dir is required"))?,
unrar: unrar.ok_or_else(|| eyre::eyre!("--unrar is required"))?,
packages,
}))
}
fn next_path(args: &mut impl Iterator<Item = OsString>, option: &str) -> eyre::Result<PathBuf> {
args.next()
.map(PathBuf::from)
.ok_or_else(|| eyre::eyre!("{option} requires a value"))
}
fn next_utf8(args: &mut impl Iterator<Item = OsString>, option: &str) -> eyre::Result<String> {
args.next()
.ok_or_else(|| eyre::eyre!("{option} requires a value"))?
.into_string()
.map_err(|value| eyre::eyre!("{option} value is not valid UTF-8: {value:?}"))
}
fn set_once<T>(slot: &mut Option<T>, value: T, option: &str) -> eyre::Result<()> {
if slot.replace(value).is_some() {
eyre::bail!("{option} may be specified only once");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(arguments: &[&str]) -> eyre::Result<ParseOutcome> {
parse_args(arguments.iter().map(OsString::from))
}
#[test]
fn parses_explicit_packages_and_synthetic_streaming_opt_out() {
let parsed = parse(&[
"--source-catalog-db",
"source.db",
"--output-dir",
"catalog",
"--unrar",
"unrar",
"--game-root",
"fixtures/alienswarm",
"--game-root",
"fixtures/bf1942",
"--no-stream-install",
"bf1942",
])
.expect("fixture options should parse");
let ParseOutcome::Options(options) = parsed else {
panic!("expected parsed fixture options");
};
assert!(options.packages[0].streamed_install);
assert!(!options.packages[1].streamed_install);
}
#[test]
fn rejects_implicit_or_mismatched_fixture_authority() {
for arguments in [
vec![
"--source-catalog-db",
"source.db",
"--output-dir",
"catalog",
"--unrar",
"unrar",
],
vec![
"--source-catalog-db",
"source.db",
"--output-dir",
"catalog",
"--unrar",
"unrar",
"--game-root",
"fixtures/g",
"--no-stream-install",
"other",
],
] {
assert!(parse(&arguments).is_err(), "accepted {arguments:?}");
}
}
}