wooly-vault/src/challenge/mod.rs
PlexSheep f945eea904
All checks were successful
cargo devel CI / cargo CI (push) Successful in 2m3s
feat(meta): make datastructures ready for multiple challenges
2024-09-08 02:28:49 +02:00

167 lines
5.4 KiB
Rust

//! Challenges and their common interface.
//!
//! This module is the core of the Wooly Vault application, as it defines the interface that all
//! challenges must implement, and contains the challenge modules themselves.
use anyhow::anyhow;
use async_trait::async_trait;
use libpt::log::{error, info};
use crate::config::Config;
use crate::vault::VaultRef;
pub mod c1;
pub mod c2;
pub mod c3;
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct ChallengeDesc {
title: String,
hints: Vec<String>,
solution: String,
description: String,
}
impl ChallengeDesc {
/// Returns a list of hints for the challenge.
///
/// A hint is a short text to be given to the contestants in case the admin thinks they need
/// it. The first hint is the most vague, afterwards the hints become more and more helpful.
///
/// # Returns
///
/// A vector of strings containing hints for the challenge.
pub fn hints(&self) -> Vec<&str> {
self.hints.iter().map(|a| a.as_ref()).collect()
}
/// Returns the solution to the challenge.
///
/// A solution is a short description of what has to be done to solve the challenge and get the
/// secret of the vault.
///
/// # Returns
///
/// A string containing the solution to the challenge.
pub fn solution(&self) -> &str {
&self.solution
}
/// Returns the description to the challenge.
///
/// The description is usually a short introduction to the challenge, without giving anything
/// of the solution away.
///
/// # Returns
///
/// A string containing the description to the challenge.
pub fn description(&self) -> &str {
&self.description
}
/// Returns the title to the challenge.
///
/// The title is just a short name, usually consisting of one to 5 words.
///
/// # Returns
///
/// A string containing the title to the challenge.
pub fn title(&self) -> &str {
&self.title
}
}
/// Defines the behavior of a challenge.
///
/// Any type that implements this trait can be served as a challenge in the Wooly Vault application.
#[async_trait]
pub trait Challenge
where
Self: Sized + 'static,
Self: Send,
Self: Sync,
Self: Clone,
Self: std::fmt::Debug,
{
/// Getter for the [vault](VaultRef).
fn vault(&self) -> VaultRef;
/// Getter for the [Config].
fn config(&self) -> Config;
/// Get the various texts for this challenge.
fn text() -> ChallengeDesc;
/// Creates a new instance of the challenge with the given configuration and vault.
///
/// # Arguments
///
/// * `config` - The configuration for the challenge.
/// * `vault` - The vault that holds the secret for the challenge.
///
/// # Returns
///
/// A new instance of the challenge.
fn new(config: Config, vault: VaultRef) -> Self;
/// Starts the challenge and serves it to the contestants.
///
/// This method is asynchronous and returns a result indicating whether the challenge was
/// successful only after the challenge has ended.
///
/// # Returns
///
/// A result indicating whether the challenge was successfully served.
///
/// # Errors
///
/// Will error when the challenge errors, for example when the network adress cannot be bound.
async fn serve(&self) -> anyhow::Result<()>;
/// Serves a challenge and sets up the hint and monitoring service for the admin.
///
/// This method not only serves the challenge, but it also sets up a small webservice for the
/// admin of the challenge, where they can see the hints, the solution, and the winners.
///
/// The admin interface will only be served when [Config::addr_admin] is set by the user.
///
/// Challenges should not typically implement this themselves, if they want to customize the
/// admin interface, define [admin_interface](Self::admin_interface).
/// The admin interface is not part of the challenge and just for monitoring.
///
/// # Returns
///
/// A result indicating whether the challenge and the admin interface were successfully served.
async fn start(&self) -> anyhow::Result<()> {
let c = self.clone();
tokio::spawn(async move {
if let Err(e) = c.serve().await {
error!("challenge {} has crashed! {e:#?}", Self::text().title());
};
});
Ok(())
}
}
/// Selects a challenge by index and serves it with the given configuration and vault.
///
/// # Arguments
///
/// * `index` - The index of the challenge to select.
/// * `config` - The configuration for the challenge.
/// * `vault` - The vault that holds the secret for the challenge.
///
/// # Returns
///
/// A result indicating whether the challenge has successfully ended.
///
/// # Errors
///
/// Returns an error if no challenge with the given index exists, or if the challenge that is being
/// served errors.
pub async fn select_and_start(index: u16, config: Config, vault: VaultRef) -> anyhow::Result<()> {
info!("select+start");
match index {
1 => c1::C1::new(config, vault).start().await?,
2 => c2::C2::new(config, vault).start().await?,
3 => c3::C3::new(config, vault).start().await?,
_ => {
return Err(anyhow!(
"no challenge with index {index} does currently exist"
))
}
}
Ok(())
}