generated from PlexSheep/rs-base
167 lines
5.4 KiB
Rust
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(())
|
|
}
|