generated from PlexSheep/rs-base
feat(vault): keep track of contestants
cargo devel CI / cargo CI (push) Successful in 1m53s
Details
cargo devel CI / cargo CI (push) Successful in 1m53s
Details
This commit is contained in:
parent
f1ef0c26ec
commit
80b9d213c3
|
@ -4,11 +4,11 @@
|
|||
//! establish a TCP connection to the server to receive the secret.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use libpt::log::{info, warn};
|
||||
use libpt::log::warn;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use super::Challenge;
|
||||
use super::{Challenge, Descriptions};
|
||||
use crate::config::Config;
|
||||
use crate::has_won;
|
||||
use crate::vault::VaultRef;
|
||||
|
@ -28,20 +28,19 @@ impl Challenge for C1 {
|
|||
self.vault.clone()
|
||||
}
|
||||
fn new(config: Config, vault: VaultRef) -> Self {
|
||||
info!("Solution: {}", Self::solution());
|
||||
Self { config, vault }
|
||||
}
|
||||
|
||||
fn hints() -> Vec<String> {
|
||||
vec![String::from("TCP connect to 1337.")]
|
||||
fn text() -> Descriptions {
|
||||
Descriptions {
|
||||
title: "dumb TCP".to_string(),
|
||||
hints: vec![String::from("TCP connect to 1337.")],
|
||||
solution: String::from("Connect by TCP, then the secret will be sent to you."),
|
||||
description: String::from("Do you know how to TCP?"),
|
||||
}
|
||||
}
|
||||
|
||||
fn solution() -> String {
|
||||
String::from("Connect by TCP, then the secret will be sent to you.")
|
||||
}
|
||||
|
||||
async fn serve(self) -> anyhow::Result<()> {
|
||||
info!("serving challenge 1");
|
||||
async fn serve(&self) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(self.config.addr).await?;
|
||||
|
||||
loop {
|
||||
|
@ -53,7 +52,8 @@ impl Challenge for C1 {
|
|||
continue;
|
||||
}
|
||||
};
|
||||
has_won(&addr);
|
||||
self.vault.add_contestant(addr.into()).await;
|
||||
has_won(&addr, &self.vault, &addr.into()).await;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = stream.write_all(vault.secret().as_bytes()).await {
|
||||
warn!("could not write to peer {addr}: {e}");
|
||||
|
|
|
@ -10,7 +10,7 @@ use libpt::log::{info, warn};
|
|||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use super::Challenge;
|
||||
use super::{Challenge, Descriptions};
|
||||
use crate::config::Config;
|
||||
use crate::has_won;
|
||||
use crate::vault::VaultRef;
|
||||
|
@ -32,7 +32,7 @@ impl C2 {
|
|||
stream: &mut TcpStream,
|
||||
addr: &std::net::SocketAddr,
|
||||
) -> Result<()> {
|
||||
has_won(addr);
|
||||
has_won(addr, vault, &addr.into()).await;
|
||||
if let Err(e) = stream.write_all(vault.secret().as_bytes()).await {
|
||||
warn!("could not write to peer {addr}: {e}");
|
||||
return Err(e.into());
|
||||
|
@ -54,22 +54,20 @@ impl Challenge for C2 {
|
|||
self.vault.clone()
|
||||
}
|
||||
fn new(config: Config, vault: VaultRef) -> Self {
|
||||
info!("Solution: {}", Self::solution());
|
||||
Self { config, vault }
|
||||
}
|
||||
|
||||
fn hints() -> Vec<String> {
|
||||
vec![String::from(
|
||||
fn text() -> Descriptions {
|
||||
Descriptions {
|
||||
title: "TCP dialogue".to_string(),
|
||||
hints: vec![String::from(
|
||||
"TCP connect to 1337 and give me a special u16",
|
||||
)]
|
||||
)],
|
||||
solution: String::from("Connect by TCP, send 1337 as bytes (not text)."),
|
||||
description: String::from("Do you know how to TCP, but slightly cooler than for C1?"),
|
||||
}
|
||||
}
|
||||
|
||||
fn solution() -> String {
|
||||
String::from("Connect by TCP, send 1337 as bytes (not text).")
|
||||
}
|
||||
|
||||
async fn serve(self) -> anyhow::Result<()> {
|
||||
info!("serving challenge 2");
|
||||
async fn serve(&self) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(self.config.addr).await?;
|
||||
|
||||
loop {
|
||||
|
@ -81,7 +79,7 @@ impl Challenge for C2 {
|
|||
continue;
|
||||
}
|
||||
};
|
||||
info!("new peer: {addr}");
|
||||
self.vault.add_contestant(addr.into()).await;
|
||||
tokio::spawn(async move {
|
||||
let mut buf: u16;
|
||||
loop {
|
||||
|
|
|
@ -29,7 +29,7 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
|||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::time::Instant;
|
||||
|
||||
use super::Challenge;
|
||||
use super::{Challenge, Descriptions};
|
||||
use crate::config::Config;
|
||||
use crate::has_won;
|
||||
use crate::vault::VaultRef;
|
||||
|
@ -189,7 +189,7 @@ impl C3 {
|
|||
stream: &mut TcpStream,
|
||||
addr: &std::net::SocketAddr,
|
||||
) -> Result<()> {
|
||||
has_won(addr);
|
||||
has_won(addr, vault, &addr.into()).await;
|
||||
if let Err(e) = stream
|
||||
.write_all(("You win: ".to_owned() + vault.secret()).as_bytes())
|
||||
.await
|
||||
|
@ -214,23 +214,22 @@ impl Challenge for C3 {
|
|||
self.vault.clone()
|
||||
}
|
||||
fn new(config: Config, vault: VaultRef) -> Self {
|
||||
info!("Solution: {}", Self::solution());
|
||||
Self { config, vault }
|
||||
}
|
||||
|
||||
fn hints() -> Vec<String> {
|
||||
vec![
|
||||
"TCP connect to 1337 and answer the questions".to_string(),
|
||||
"The questions keep chaning".to_string(),
|
||||
"You should try to solve the questions in an automated way".to_string(),
|
||||
]
|
||||
fn text() -> Descriptions {
|
||||
Descriptions {
|
||||
title: "TCP math exam".to_string(),
|
||||
hints: vec![
|
||||
"TCP connect to 1337 and answer the questions.".to_string(),
|
||||
"The questions keep chaning.".to_string(),
|
||||
"You should try to solve the questions in an automated way.".to_string(),
|
||||
],
|
||||
solution: String::from("Connect by TCP, programmatically answer the questions."),
|
||||
description: String::from("How fast can you do arithmetics?"),
|
||||
}
|
||||
}
|
||||
|
||||
fn solution() -> String {
|
||||
String::from("Connect by TCP, send 1337 as bytes (not text).")
|
||||
}
|
||||
|
||||
async fn serve(self) -> anyhow::Result<()> {
|
||||
async fn serve(&self) -> anyhow::Result<()> {
|
||||
info!("serving challenge 3");
|
||||
let listener = TcpListener::bind(self.config.addr).await?;
|
||||
|
||||
|
@ -243,7 +242,7 @@ impl Challenge for C3 {
|
|||
continue;
|
||||
}
|
||||
};
|
||||
info!("new peer: {}", peer.1);
|
||||
self.vault.add_contestant(peer.1.into()).await;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = Self::handler(peer, &vault).await {
|
||||
warn!("error while handling peer: {e}");
|
||||
|
|
|
@ -16,6 +16,60 @@ pub mod c1;
|
|||
pub mod c2;
|
||||
pub mod c3;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct Descriptions {
|
||||
title: String,
|
||||
hints: Vec<String>,
|
||||
solution: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
impl Descriptions {
|
||||
/// 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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
@ -28,6 +82,8 @@ where
|
|||
fn vault(&self) -> VaultRef;
|
||||
/// Getter for the [Config].
|
||||
fn config(&self) -> Config;
|
||||
/// Get the various texts for this challenge.
|
||||
fn text() -> Descriptions;
|
||||
/// Creates a new instance of the challenge with the given configuration and vault.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -39,24 +95,6 @@ where
|
|||
///
|
||||
/// A new instance of the challenge.
|
||||
fn new(config: Config, vault: VaultRef) -> Self;
|
||||
/// 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.
|
||||
fn hints() -> Vec<String>;
|
||||
/// 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.
|
||||
fn solution() -> String;
|
||||
/// Starts the challenge and serves it to the contestants.
|
||||
///
|
||||
/// This method is asynchronous and returns a result indicating whether the challenge was
|
||||
|
@ -69,7 +107,7 @@ where
|
|||
/// # Errors
|
||||
///
|
||||
/// Will error when the challenge errors, for example when the network adress cannot be bound.
|
||||
async fn serve(self) -> anyhow::Result<()>;
|
||||
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
|
||||
|
@ -78,8 +116,8 @@ where
|
|||
/// 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::adming_interface).
|
||||
/// The hadmin interface is not part of the challenge and just for monitoring.
|
||||
/// admin interface, define [admin_interface](Self::admin_interface).
|
||||
/// The admin interface is not part of the challenge and just for monitoring.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
|
@ -89,15 +127,19 @@ where
|
|||
if self.config().addr_admin.is_some() {
|
||||
let vault = self.vault();
|
||||
let config = self.config();
|
||||
tokio::spawn(Self::admin_interface(vault, config));
|
||||
tokio::spawn(Self::admin_interface(Self::text(), vault, config));
|
||||
}
|
||||
|
||||
let challenge_handle = self.serve();
|
||||
Ok(challenge_handle.await?)
|
||||
}
|
||||
#[cfg(feature = "admin-interface")]
|
||||
async fn admin_interface(vault: VaultRef, config: Config) -> anyhow::Result<()> {
|
||||
admin::serve(vault, config).await?;
|
||||
async fn admin_interface(
|
||||
text: Descriptions,
|
||||
vault: VaultRef,
|
||||
config: Config,
|
||||
) -> anyhow::Result<()> {
|
||||
admin::serve(text, vault, config).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,18 @@
|
|||
//! Wooly Vault is programmed asynchronously with [tokio] to be able to handle many contestants at
|
||||
//! once if needed.
|
||||
|
||||
use self::vault::VaultRef;
|
||||
|
||||
pub mod challenge;
|
||||
pub mod config;
|
||||
pub mod vault;
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn has_won(addr: &std::net::SocketAddr) {
|
||||
pub(crate) async fn has_won(
|
||||
addr: &std::net::SocketAddr,
|
||||
vault: &VaultRef,
|
||||
contestant: &vault::Contestant,
|
||||
) {
|
||||
vault.add_winner(contestant.clone()).await;
|
||||
libpt::log::info!("Sending the secret to {addr}")
|
||||
}
|
||||
|
|
59
src/vault.rs
59
src/vault.rs
|
@ -3,20 +3,57 @@
|
|||
//! This module provides a [`Vault`] struct that holds a secret and allows it to be shared safely
|
||||
//! across the application using an [`Arc`] (Atomic Reference Counted) pointer.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libpt::log::info;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// A type alias for an [`Arc`] pointer to a [`Vault`] instance.
|
||||
///
|
||||
/// This type is used to share a [`Vault`] instance across multiple parts of the application.
|
||||
pub type VaultRef = Arc<Vault>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Contestant {
|
||||
ip: IpAddr,
|
||||
}
|
||||
|
||||
/// A struct that holds a secret and provides methods for accessing it.
|
||||
///
|
||||
/// The [`Vault`] struct is designed to be shared safely across the application using an [`Arc`] pointer.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Vault {
|
||||
/// The secret stored in the vault
|
||||
secret: String,
|
||||
contestants: Arc<Mutex<HashSet<Contestant>>>,
|
||||
winners: Arc<Mutex<HashSet<Contestant>>>,
|
||||
}
|
||||
|
||||
impl From<IpAddr> for Contestant {
|
||||
fn from(value: IpAddr) -> Self {
|
||||
Self { ip: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SocketAddr> for Contestant {
|
||||
fn from(value: SocketAddr) -> Self {
|
||||
Self { ip: value.ip() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SocketAddr> for Contestant {
|
||||
fn from(value: &SocketAddr) -> Self {
|
||||
Self { ip: value.ip() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Contestant {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Vault {
|
||||
|
@ -30,6 +67,8 @@ impl Vault {
|
|||
pub fn new(secret: &str) -> VaultRef {
|
||||
let v = Self {
|
||||
secret: secret.to_string(),
|
||||
contestants: Default::default(),
|
||||
winners: Default::default(),
|
||||
};
|
||||
Arc::new(v)
|
||||
}
|
||||
|
@ -42,4 +81,22 @@ impl Vault {
|
|||
pub fn secret(&self) -> &str {
|
||||
&self.secret
|
||||
}
|
||||
|
||||
pub async fn add_contestant(&self, contestant: Contestant) -> bool {
|
||||
info!("new contestant: {contestant}");
|
||||
self.contestants.lock().await.insert(contestant)
|
||||
}
|
||||
|
||||
pub async fn add_winner(&self, contestant: Contestant) -> bool {
|
||||
info!("new winner: {contestant}");
|
||||
self.winners.lock().await.insert(contestant)
|
||||
}
|
||||
|
||||
pub async fn contestants(&self) -> HashSet<Contestant> {
|
||||
self.contestants.lock().await.clone()
|
||||
}
|
||||
|
||||
pub async fn winners(&self) -> HashSet<Contestant> {
|
||||
self.contestants.lock().await.clone()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue