feat: launch many services somewhat at once

This commit is contained in:
Christoph J. Scherr 2024-09-08 02:59:58 +02:00
parent f945eea904
commit ea23e93ec9
7 changed files with 99 additions and 56 deletions

View File

@ -8,7 +8,7 @@ use libpt::log::warn;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use super::{Challenge, ChallengeDesc}; use super::{ChallengeDesc, ChallengeLike};
use crate::config::Config; use crate::config::Config;
use crate::has_won; use crate::has_won;
use crate::vault::VaultRef; use crate::vault::VaultRef;
@ -21,7 +21,7 @@ pub struct C1 {
} }
#[async_trait] #[async_trait]
impl Challenge for C1 { impl ChallengeLike for C1 {
fn config(&self) -> Config { fn config(&self) -> Config {
self.config.clone() self.config.clone()
} }
@ -34,6 +34,7 @@ impl Challenge for C1 {
fn text() -> ChallengeDesc { fn text() -> ChallengeDesc {
ChallengeDesc { ChallengeDesc {
id: 1,
title: "dumb TCP".to_string(), title: "dumb TCP".to_string(),
hints: vec![String::from("TCP connect to 1337.")], hints: vec![String::from("TCP connect to 1337.")],
solution: String::from("Connect by TCP, then the secret will be sent to you."), solution: String::from("Connect by TCP, then the secret will be sent to you."),

View File

@ -10,7 +10,7 @@ use libpt::log::{info, warn};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use super::{Challenge, ChallengeDesc}; use super::{ChallengeDesc, ChallengeLike};
use crate::config::Config; use crate::config::Config;
use crate::has_won; use crate::has_won;
use crate::vault::VaultRef; use crate::vault::VaultRef;
@ -47,7 +47,7 @@ impl C2 {
} }
#[async_trait] #[async_trait]
impl Challenge for C2 { impl ChallengeLike for C2 {
fn config(&self) -> Config { fn config(&self) -> Config {
self.config.clone() self.config.clone()
} }
@ -59,6 +59,7 @@ impl Challenge for C2 {
} }
fn text() -> ChallengeDesc { fn text() -> ChallengeDesc {
ChallengeDesc { ChallengeDesc {
id: 2,
title: "TCP dialogue".to_string(), title: "TCP dialogue".to_string(),
hints: vec![String::from( hints: vec![String::from(
"TCP connect to 1337 and give me a special u16", "TCP connect to 1337 and give me a special u16",

View File

@ -29,7 +29,7 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use tokio::time::Instant; use tokio::time::Instant;
use super::{Challenge, ChallengeDesc}; use super::{ChallengeDesc, ChallengeLike};
use crate::config::Config; use crate::config::Config;
use crate::has_won; use crate::has_won;
use crate::vault::VaultRef; use crate::vault::VaultRef;
@ -207,7 +207,7 @@ impl C3 {
} }
#[async_trait] #[async_trait]
impl Challenge for C3 { impl ChallengeLike for C3 {
fn config(&self) -> Config { fn config(&self) -> Config {
self.config.clone() self.config.clone()
} }
@ -219,6 +219,7 @@ impl Challenge for C3 {
} }
fn text() -> ChallengeDesc { fn text() -> ChallengeDesc {
ChallengeDesc { ChallengeDesc {
id: 3,
title: "TCP math exam".to_string(), title: "TCP math exam".to_string(),
hints: vec![ hints: vec![
"TCP connect to 1337 and answer the questions.".to_string(), "TCP connect to 1337 and answer the questions.".to_string(),

View File

@ -16,6 +16,7 @@ pub mod c3;
#[derive(Clone, PartialEq, Eq, Hash, Debug)] #[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct ChallengeDesc { pub struct ChallengeDesc {
id: usize,
title: String, title: String,
hints: Vec<String>, hints: Vec<String>,
solution: String, solution: String,
@ -30,7 +31,7 @@ impl ChallengeDesc {
/// ///
/// # Returns /// # Returns
/// ///
/// A vector of strings containing hints for the challenge. /// A [`Vec<String>`] containing hints for the challenge.
pub fn hints(&self) -> Vec<&str> { pub fn hints(&self) -> Vec<&str> {
self.hints.iter().map(|a| a.as_ref()).collect() self.hints.iter().map(|a| a.as_ref()).collect()
} }
@ -41,7 +42,7 @@ impl ChallengeDesc {
/// ///
/// # Returns /// # Returns
/// ///
/// A string containing the solution to the challenge. /// A [`String`] containing the solution to the challenge.
pub fn solution(&self) -> &str { pub fn solution(&self) -> &str {
&self.solution &self.solution
} }
@ -52,7 +53,7 @@ impl ChallengeDesc {
/// ///
/// # Returns /// # Returns
/// ///
/// A string containing the description to the challenge. /// A [`String`] containing the description to the challenge.
pub fn description(&self) -> &str { pub fn description(&self) -> &str {
&self.description &self.description
} }
@ -62,17 +63,27 @@ impl ChallengeDesc {
/// ///
/// # Returns /// # Returns
/// ///
/// A string containing the title to the challenge. /// A [`String`] containing the title to the challenge.
pub fn title(&self) -> &str { pub fn title(&self) -> &str {
&self.title &self.title
} }
/// Returns the id to the challenge.
///
/// The id is just the index of the challenge to distinguish them easily
///
/// # Returns
///
/// A [`usize`] containing the title to the challenge.
pub fn id(&self) -> usize {
self.id
}
} }
/// Defines the behavior of a challenge. /// Defines the behavior of a challenge.
/// ///
/// Any type that implements this trait can be served as a challenge in the Wooly Vault application. /// Any type that implements this trait can be served as a challenge in the Wooly Vault application.
#[async_trait] #[async_trait]
pub trait Challenge pub trait ChallengeLike
where where
Self: Sized + 'static, Self: Sized + 'static,
Self: Send, Self: Send,
@ -127,6 +138,7 @@ where
async fn start(&self) -> anyhow::Result<()> { async fn start(&self) -> anyhow::Result<()> {
let c = self.clone(); let c = self.clone();
tokio::spawn(async move { tokio::spawn(async move {
info!("starting C{}", Self::text().id());
if let Err(e) = c.serve().await { if let Err(e) = c.serve().await {
error!("challenge {} has crashed! {e:#?}", Self::text().title()); error!("challenge {} has crashed! {e:#?}", Self::text().title());
}; };
@ -134,34 +146,3 @@ where
Ok(()) 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(())
}

View File

@ -14,6 +14,12 @@
//! Wooly Vault is programmed asynchronously with [tokio] to be able to handle many contestants at //! Wooly Vault is programmed asynchronously with [tokio] to be able to handle many contestants at
//! once if needed. //! once if needed.
use anyhow::anyhow;
use libpt::log::info;
use self::challenge::ChallengeLike;
use self::config::Config;
use self::meta::Service;
use self::vault::VaultRef; use self::vault::VaultRef;
pub mod challenge; pub mod challenge;
@ -31,3 +37,61 @@ pub(crate) async fn has_won(
vault.add_winner(contestant.clone()).await; vault.add_winner(contestant.clone()).await;
libpt::log::info!("Sending the secret to {addr}") libpt::log::info!("Sending the secret to {addr}")
} }
/// Selects a single 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_single(
index: u16,
config: Config,
vault: VaultRef,
) -> anyhow::Result<()> {
info!("select+start");
match index {
1 => challenge::c1::C1::new(config, vault).start().await?,
2 => challenge::c2::C2::new(config, vault).start().await?,
3 => challenge::c3::C3::new(config, vault).start().await?,
_ => {
return Err(anyhow!(
"no challenge with index {index} does currently exist"
))
}
}
Ok(())
}
pub async fn start_all(config: Config, vault: VaultRef) -> anyhow::Result<()> {
let c1 = challenge::c1::C1::new(config.clone(), vault.clone());
let c2 = challenge::c2::C2::new(config.clone(), vault.clone());
let c3 = challenge::c3::C3::new(config.clone(), vault.clone());
c1.start().await?;
c2.start().await?;
c3.start().await?;
meta::serve(
vec![
challenge::c1::C1::text(),
challenge::c2::C2::text(),
challenge::c3::C3::text(),
],
vault.clone(),
config.clone(),
)
.await?;
Ok(())
}

View File

@ -2,7 +2,8 @@ use anyhow::Result;
use clap::Parser; use clap::Parser;
use libpt::log::{debug, info}; use libpt::log::{debug, info};
use wooly_vault::{challenge::select_and_start, config::Config, vault::Vault}; use wooly_vault::{config::Config, vault::Vault};
use wooly_vault::{select_and_start_single, start_all};
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> { async fn main() -> Result<()> {
@ -21,7 +22,7 @@ async fn main() -> Result<()> {
let secret = Config::secret()?; let secret = Config::secret()?;
let v = Vault::new(&secret); let v = Vault::new(&secret);
select_and_start(conf.challenge, conf, v).await?; start_all(conf, v).await?;
loop { loop {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await tokio::time::sleep(tokio::time::Duration::from_secs(5)).await

View File

@ -26,27 +26,21 @@ pub struct Service<'tp> {
challenges: Vec<ChallengeDesc>, challenges: Vec<ChallengeDesc>,
} }
impl<'tp> Service<'tp> { impl<'tp> Service<'tp> {
fn new( fn build(vault: VaultRef, config: Config, challenges: Vec<ChallengeDesc>) -> Result<Arc<Self>> {
vault: VaultRef, let mut env = Environment::new();
config: Config, env.add_template("index", include_str!("../../data/www/admin.html"))?;
env: Environment<'tp>, Ok(Self {
challenges: Vec<ChallengeDesc>,
) -> Arc<Self> {
Self {
vault, vault,
config, config,
env, env,
challenges, challenges,
} }
.into() .into())
} }
} }
pub async fn serve(challenges: Vec<ChallengeDesc>, vault: VaultRef, config: Config) -> Result<()> { pub async fn serve(challenges: Vec<ChallengeDesc>, vault: VaultRef, config: Config) -> Result<()> {
let mut env = Environment::new(); let service = Service::build(vault, config, challenges)?;
env.add_template("index", include_str!("../../data/www/admin.html"))?;
let service = Service::new(vault, config, env, challenges);
let routes = Service::admin_routes(service.clone()) let routes = Service::admin_routes(service.clone())
.or(Service::ressources_routes()) .or(Service::ressources_routes())