zero_trust_rps/common/client/
bot.rs

1use std::time::Duration;
2
3use quinn::{Connection, VarInt};
4use rand::{thread_rng, Rng};
5use tokio::{
6    sync::mpsc::{channel, error::SendError},
7    task::JoinError,
8};
9
10use crate::common::{
11    message::{RoomId, RpsData},
12    rps::{move_validation::validate_move, simple_move::SimpleUserMove, state::RpsState},
13};
14
15use super::{
16    connection::{
17        conn::{run_client, RunClientError},
18        init::initialize_connection,
19    },
20    state::ClientStateView,
21};
22
23#[allow(unused)]
24const RPS_CHOICES: [RpsData; 3] = unsafe {
25    [
26        RpsData::new_unchecked([
27            240, 159, 170, 168, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
28        ]),
29        RpsData::new_unchecked([
30            240, 159, 147, 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
31        ]),
32        RpsData::new_unchecked([
33            226, 156, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
34        ]),
35    ]
36};
37
38#[derive(thiserror::Error, Debug)]
39#[allow(clippy::enum_variant_names)]
40pub enum RunBotError {
41    #[error("Could not send over channel: {}", .0)]
42    ChannelSendError(#[from] SendError<SimpleUserMove>),
43    #[error("Failed to initialize Connection: {}", .0)]
44    #[allow(unused)]
45    InitError(String),
46    #[error("Error while running: {}", .0)]
47    RunClientError(#[from] RunClientError),
48    #[error("Could not join: {}", .0)]
49    TokioError(#[from] JoinError),
50}
51
52#[allow(dead_code)]
53pub async fn run_bot(
54    id: usize,
55    connection: Connection,
56    room_id: RoomId,
57    domain: String,
58    port: u16,
59) -> Result<(), RunBotError> {
60    log::info!("Run bot {id} for room {room_id}, connect to {domain}:{port} ({connection:?})");
61    match _run_bot(connection, room_id, domain.as_str(), port).await {
62        Ok(()) => {
63            log::info!("Bot {id} finished");
64            Ok(())
65        }
66        Err(err) => {
67            log::error!("Bot {id} exited with error: {err:?}");
68            Err(err)
69        }
70    }
71}
72
73async fn _run_bot(
74    connection: Connection,
75    room_id: RoomId,
76    domain: &str,
77    port: u16,
78) -> Result<(), RunBotError> {
79    let (connection, writer, reader) = initialize_connection(connection, domain, port)
80        .await
81        .map_err(|err| RunBotError::InitError(format!("{err}")))?;
82
83    let (umove_send, umove_recv) = channel::<SimpleUserMove>(1);
84    let (state_send, mut state_recv) = channel::<ClientStateView>(1);
85
86    let jh = tokio::spawn(run_client(None, writer, reader, (), state_send, umove_recv));
87
88    let mut error = None;
89    let mut last_state = RpsState::BeforeRoom;
90    let mut requested_state_change = false;
91
92    while let Some(state) = state_recv.recv().await {
93        log::trace!("Got new state: {state:?}");
94        // TODO on error
95        if state.state != last_state || error != state.last_error {
96            requested_state_change = false;
97            last_state = state.state;
98            error = state.last_error.clone();
99        } else {
100            let duration = Duration::from_millis(thread_rng().gen_range(500..900));
101            log::trace!("Waiting for: {duration:?}");
102            tokio::time::sleep(duration).await;
103        }
104        if requested_state_change {
105            continue;
106        }
107        if state.state == RpsState::InRoom
108            && state
109                .room
110                .as_ref()
111                .map(|room| room.users.len())
112                .unwrap_or(0)
113                <= 1
114        {
115            continue; // do not do anything if we are alone in room
116        }
117        use super::super::rps::state::RpsState::*;
118        let bot_move = match state.state {
119            BeforeRoom => SimpleUserMove::JoinRoom(room_id),
120            InRoom => {
121                let choice: usize = rand::thread_rng().gen_range(0..RPS_CHOICES.len());
122                SimpleUserMove::Play(RPS_CHOICES[choice])
123            }
124            Played => SimpleUserMove::ConfirmPlay,
125            Confirmed => SimpleUserMove::BackToRoom,
126        };
127
128        if let Ok(()) = validate_move(
129            state.user,
130            &bot_move,
131            state.room.iter().flat_map(|room| room.users.iter()),
132            state.room.as_ref().and_then(|room| room.round.as_ref()),
133        ) {
134            requested_state_change = true;
135            log::trace!("{} doing move {bot_move:?}", state.user);
136            umove_send.send(bot_move).await?
137        }
138    }
139
140    let _: () = jh.await??;
141
142    connection.close(VarInt::from_u32(0), &[]);
143
144    Ok(())
145}