disallow mutable clients to leave the server
authoralfadur <mail@none>
Sat, 21 Dec 2019 23:33:50 +0300
changeset 15519 b3157d218ae2
parent 15518 e705d30e0f10
child 15520 fd3a20e9d095
disallow mutable clients to leave the server
rust/hedgewars-server/src/core/server.rs
rust/hedgewars-server/src/handlers.rs
rust/hedgewars-server/src/handlers/common.rs
rust/hedgewars-server/src/handlers/inroom.rs
rust/hedgewars-server/src/handlers/strings.rs
--- a/rust/hedgewars-server/src/core/server.rs	Sat Dec 21 00:26:17 2019 +0300
+++ b/rust/hedgewars-server/src/core/server.rs	Sat Dec 21 23:33:50 2019 +0300
@@ -2,10 +2,11 @@
     client::HwClient,
     indexslab::IndexSlab,
     room::HwRoom,
-    types::{ClientId, RoomId, ServerVar},
+    types::{ClientId, RoomId, ServerVar, TeamInfo},
 };
 use crate::{protocol::messages::HwProtocolMessage::Greeting, utils};
 
+use bitflags::_core::hint::unreachable_unchecked;
 use bitflags::*;
 use chrono::{offset, DateTime};
 use log::*;
@@ -58,6 +59,21 @@
 }
 
 #[derive(Debug)]
+pub enum AddTeamError {
+    TooManyTeams,
+    TooManyHedgehogs,
+    TeamAlreadyExists,
+    GameInProgress,
+    Restricted,
+}
+
+#[derive(Debug)]
+pub enum RemoveTeamError {
+    NoTeam,
+    TeamNotOwned,
+}
+
+#[derive(Debug)]
 pub enum ModifyTeamError {
     NoTeam,
     NotMaster,
@@ -229,6 +245,16 @@
     }
 
     #[inline]
+    pub fn has_client(&self, client_id: ClientId) -> bool {
+        self.clients.contains(client_id)
+    }
+
+    #[inline]
+    pub fn iter_clients(&self) -> impl Iterator<Item = &HwClient> {
+        self.clients.iter().map(|(_, c)| c)
+    }
+
+    #[inline]
     pub fn room(&self, room_id: RoomId) -> &HwRoom {
         &self.rooms[room_id]
     }
@@ -248,8 +274,8 @@
         &mut self,
         client_id: ClientId,
         room_id: RoomId,
-    ) -> (&mut HwClient, &mut HwRoom) {
-        (&mut self.clients[client_id], &mut self.rooms[room_id])
+    ) -> (&HwClient, &mut HwRoom) {
+        (&self.clients[client_id], &mut self.rooms[room_id])
     }
 
     #[inline]
@@ -504,9 +530,44 @@
         }
     }
 
+    pub fn leave_game(&mut self, client_id: ClientId) -> Option<Vec<String>> {
+        let client = &mut self.clients[client_id];
+        let client_left = client.is_in_game();
+        if client_left {
+            client.set_is_in_game(false);
+            let room = &mut self.rooms[client.room_id.expect("Client should've been in the game")];
+
+            let team_names: Vec<_> = room
+                .client_teams(client_id)
+                .map(|t| t.name.clone())
+                .collect();
+
+            if let Some(ref mut info) = room.game_info {
+                info.teams_in_game -= team_names.len() as u8;
+
+                for team_name in &team_names {
+                    let remove_msg =
+                        utils::to_engine_msg(std::iter::once(b'F').chain(team_name.bytes()));
+                    if let Some(m) = &info.sync_msg {
+                        info.msg_log.push(m.clone());
+                    }
+                    if info.sync_msg.is_some() {
+                        info.sync_msg = None
+                    }
+                    info.msg_log.push(remove_msg);
+                }
+                Some(team_names)
+            } else {
+                unreachable!();
+            }
+        } else {
+            None
+        }
+    }
+
     pub fn end_game(&mut self, room_id: RoomId) -> EndGameResult {
         let room = &mut self.rooms[room_id];
-        room.ready_players_number = 1;
+        room.ready_players_number = room.master_id.is_some() as u8;
 
         if let Some(info) = replace(&mut room.game_info, None) {
             let joined_mid_game_clients = self
@@ -572,7 +633,58 @@
         }
     }
 
-    pub fn add_team(&mut self, client_id: ClientId) {}
+    pub fn add_team(
+        &mut self,
+        client_id: ClientId,
+        mut info: Box<TeamInfo>,
+    ) -> Result<&TeamInfo, AddTeamError> {
+        let client = &mut self.clients[client_id];
+        if let Some(room_id) = client.room_id {
+            let room = &mut self.rooms[room_id];
+            if room.teams.len() >= room.max_teams as usize {
+                Err(AddTeamError::TooManyTeams)
+            } else if room.addable_hedgehogs() == 0 {
+                Err(AddTeamError::TooManyHedgehogs)
+            } else if room.find_team(|t| t.name == info.name) != None {
+                Err(AddTeamError::TeamAlreadyExists)
+            } else if room.game_info.is_some() {
+                Err(AddTeamError::GameInProgress)
+            } else if room.is_team_add_restricted() {
+                Err(AddTeamError::Restricted)
+            } else {
+                info.owner = client.nick.clone();
+                let team = room.add_team(client.id, *info, client.protocol_number < 42);
+                client.teams_in_game += 1;
+                client.clan = Some(team.color);
+                Ok(team)
+            }
+        } else {
+            unreachable!()
+        }
+    }
+
+    pub fn remove_team(
+        &mut self,
+        client_id: ClientId,
+        team_name: &str,
+    ) -> Result<(), RemoveTeamError> {
+        let client = &mut self.clients[client_id];
+        if let Some(room_id) = client.room_id {
+            let room = &mut self.rooms[room_id];
+            match room.find_team_owner(team_name) {
+                None => Err(RemoveTeamError::NoTeam),
+                Some((id, _)) if id != client_id => Err(RemoveTeamError::TeamNotOwned),
+                Some(_) => {
+                    client.teams_in_game -= 1;
+                    client.clan = room.find_team_color(client.id);
+                    room.remove_team(team_name);
+                    Ok(())
+                }
+            }
+        } else {
+            unreachable!();
+        }
+    }
 
     pub fn set_team_color(
         &mut self,
--- a/rust/hedgewars-server/src/handlers.rs	Sat Dec 21 00:26:17 2019 +0300
+++ b/rust/hedgewars-server/src/handlers.rs	Sat Dec 21 23:33:50 2019 +0300
@@ -259,7 +259,7 @@
                         response.remove_client(client_id);
                     }
                 }
-            } else if server.clients.contains(client_id) {
+            } else if server.has_client(client_id) {
                 match message {
                     HwProtocolMessage::Quit(Some(msg)) => {
                         common::remove_client(server, response, "User quit: ".to_string() + &msg);
--- a/rust/hedgewars-server/src/handlers/common.rs	Sat Dec 21 00:26:17 2019 +0300
+++ b/rust/hedgewars-server/src/handlers/common.rs	Sat Dec 21 23:33:50 2019 +0300
@@ -392,7 +392,7 @@
                 };
                 get_room_update(None, room, room_master, response);
 
-                for (_, client) in server.clients.iter() {
+                for client in server.iter_clients() {
                     if client.room_id == Some(room_id) {
                         super::common::get_room_config(&server.rooms[room_id], client.id, response);
                     }
--- a/rust/hedgewars-server/src/handlers/inroom.rs	Sat Dec 21 00:26:17 2019 +0300
+++ b/rust/hedgewars-server/src/handlers/inroom.rs	Sat Dec 21 23:33:50 2019 +0300
@@ -1,4 +1,6 @@
 use super::{common::rnd_reply, strings::*};
+use crate::core::room::GameInfo;
+use crate::core::server::AddTeamError;
 use crate::{
     core::{
         room::{HwRoom, RoomFlags, MAX_TEAMS_IN_ROOM},
@@ -206,76 +208,69 @@
                 super::common::get_start_game_data(server, room_id, result, response);
             }
         }
-        AddTeam(mut info) => {
-            if room.teams.len() >= room.max_teams as usize {
-                response.warn("Too many teams!");
-            } else if room.addable_hedgehogs() == 0 {
-                response.warn("Too many hedgehogs!");
-            } else if room.find_team(|t| t.name == info.name) != None {
-                response.warn("There's already a team with same name in the list.");
-            } else if room.game_info.is_some() {
-                response.warn("Joining not possible: Round is in progress.");
-            } else if room.is_team_add_restricted() {
-                response.warn("This room currently does not allow adding new teams.");
-            } else {
-                info.owner = client.nick.clone();
-                let team = room.add_team(client.id, *info, client.protocol_number < 42);
-                client.teams_in_game += 1;
-                client.clan = Some(team.color);
-                response.add(TeamAccepted(team.name.clone()).send_self());
-                response.add(
-                    TeamAdd(team.to_protocol())
-                        .send_all()
-                        .in_room(room_id)
-                        .but_self(),
-                );
-                response.add(
-                    TeamColor(team.name.clone(), team.color)
-                        .send_all()
-                        .in_room(room_id),
-                );
-                response.add(
-                    HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
-                        .send_all()
-                        .in_room(room_id),
-                );
+        AddTeam(info) => {
+            use crate::core::server::AddTeamError;
+            match server.add_team(client_id, info) {
+                Ok(team) => {
+                    response.add(TeamAccepted(team.name.clone()).send_self());
+                    response.add(
+                        TeamAdd(team.to_protocol())
+                            .send_all()
+                            .in_room(room_id)
+                            .but_self(),
+                    );
+                    response.add(
+                        TeamColor(team.name.clone(), team.color)
+                            .send_all()
+                            .in_room(room_id),
+                    );
+                    response.add(
+                        HedgehogsNumber(team.name.clone(), team.hedgehogs_number)
+                            .send_all()
+                            .in_room(room_id),
+                    );
 
-                let room = server.room(room_id);
-                let room_master = if let Some(id) = room.master_id {
-                    Some(server.client(id))
-                } else {
-                    None
-                };
-                super::common::get_room_update(None, room, room_master, response);
+                    let room = server.room(room_id);
+                    let room_master = if let Some(id) = room.master_id {
+                        Some(server.client(id))
+                    } else {
+                        None
+                    };
+                    super::common::get_room_update(None, room, room_master, response);
+                }
+                Err(AddTeamError::TooManyTeams) => response.warn(TOO_MANY_TEAMS),
+                Err(AddTeamError::TooManyHedgehogs) => response.warn(TOO_MANY_HEDGEHOGS),
+                Err(AddTeamError::TeamAlreadyExists) => response.warn(TEAM_EXISTS),
+                Err(AddTeamError::GameInProgress) => response.warn(ROUND_IN_PROGRESS),
+                Err(AddTeamError::Restricted) => response.warn(TEAM_ADD_RESTRICTED),
             }
         }
-        RemoveTeam(name) => match room.find_team_owner(&name) {
-            None => response.warn("Error: The team you tried to remove does not exist."),
-            Some((id, _)) if id != client_id => {
-                response.warn("You can't remove a team you don't own.")
+        RemoveTeam(name) => {
+            use crate::core::server::RemoveTeamError;
+            match server.remove_team(client_id, &name) {
+                Ok(()) => {
+                    let (client, room) = server.client_and_room(client_id, room_id);
+
+                    let removed_teams = vec![name];
+                    super::common::get_remove_teams_data(
+                        room_id,
+                        client.is_in_game(),
+                        removed_teams,
+                        response,
+                    );
+
+                    match room.game_info {
+                        Some(ref info) if info.teams_in_game == 0 => {
+                            let result = server.end_game(room_id);
+                            super::common::get_end_game_result(server, room_id, result, response);
+                        }
+                        _ => (),
+                    }
+                }
+                Err(RemoveTeamError::NoTeam) => response.warn(NO_TEAM_TO_REMOVE),
+                Err(RemoveTeamError::TeamNotOwned) => response.warn(TEAM_NOT_OWNED),
             }
-            Some((_, name)) => {
-                let name = name.to_string();
-                client.teams_in_game -= 1;
-                client.clan = room.find_team_color(client.id);
-                room.remove_team(&name);
-                let removed_teams = vec![name];
-                super::common::get_remove_teams_data(
-                    room_id,
-                    client.is_in_game(),
-                    removed_teams,
-                    response,
-                );
-
-                match room.game_info {
-                    Some(ref info) if info.teams_in_game == 0 => {
-                        let result = server.end_game(room_id);
-                        super::common::get_end_game_result(server, room_id, result, response);
-                    }
-                    _ => (),
-                }
-            }
-        },
+        }
         SetHedgehogsNumber(team_name, number) => {
             let addable_hedgehogs = room.addable_hedgehogs();
             if let Some((_, team)) = room.find_team_and_owner_mut(|t| t.name == team_name) {
@@ -510,54 +505,31 @@
             }
         }
         RoundFinished => {
-            let mut game_ended = false;
-            if client.is_in_game() {
-                client.set_is_in_game(false);
+            if let Some(team_names) = server.leave_game(client_id) {
+                let (client, room) = server.client_and_room(client_id, room_id);
                 response.add(
                     ClientFlags(remove_flags(&[Flags::InGame]), vec![client.nick.clone()])
                         .send_all()
                         .in_room(room.id),
                 );
-                let team_names: Vec<_> = room
-                    .client_teams(client_id)
-                    .map(|t| t.name.clone())
-                    .collect();
-
-                if let Some(ref mut info) = room.game_info {
-                    info.teams_in_game -= team_names.len() as u8;
-                    if info.teams_in_game == 0 {
-                        game_ended = true;
-                    }
-
-                    for team_name in team_names {
-                        let msg = once(b'F').chain(team_name.bytes());
-                        response.add(
-                            ForwardEngineMessage(vec![to_engine_msg(msg)])
-                                .send_all()
-                                .in_room(room_id)
-                                .but_self(),
-                        );
 
-                        let remove_msg = to_engine_msg(once(b'F').chain(team_name.bytes()));
-                        if let Some(m) = &info.sync_msg {
-                            info.msg_log.push(m.clone());
-                        }
-                        if info.sync_msg.is_some() {
-                            info.sync_msg = None
-                        }
-                        info.msg_log.push(remove_msg.clone());
-                        response.add(
-                            ForwardEngineMessage(vec![remove_msg])
-                                .send_all()
-                                .in_room(room_id)
-                                .but_self(),
-                        );
-                    }
+                for team_name in team_names {
+                    let msg = once(b'F').chain(team_name.bytes());
+                    response.add(
+                        ForwardEngineMessage(vec![to_engine_msg(msg)])
+                            .send_all()
+                            .in_room(room_id)
+                            .but_self(),
+                    );
                 }
-            }
-            if game_ended {
-                let result = server.end_game(room_id);
-                super::common::get_end_game_result(server, room_id, result, response);
+
+                if let Some(GameInfo {
+                    teams_in_game: 0, ..
+                }) = room.game_info
+                {
+                    let result = server.end_game(room_id);
+                    super::common::get_end_game_result(server, room_id, result, response);
+                }
             }
         }
         Rnd(v) => {
--- a/rust/hedgewars-server/src/handlers/strings.rs	Sat Dec 21 00:26:17 2019 +0300
+++ b/rust/hedgewars-server/src/handlers/strings.rs	Sat Dec 21 23:33:50 2019 +0300
@@ -3,6 +3,7 @@
 pub const ILLEGAL_ROOM_NAME: &str = "Illegal room name! A room name must be between 1-40 characters long, must not have a trailing or leading space and must not have any of these characters: $()*+?[]^{|}";
 pub const NO_ROOM: &str = "No such room.";
 pub const NO_TEAM: &str = "No such team.";
+pub const NO_TEAM_TO_REMOVE: &str = "Error: The team you tried to remove does not exist.";
 pub const NO_USER: &str = "No such user.";
 pub const NOT_MASTER: &str = "You're not the room master!";
 pub const REPLAY_LOAD_FAILED: &str = "Could't load the replay";
@@ -19,7 +20,13 @@
 pub const ROOM_EXISTS: &str = "A room with the same name already exists.";
 pub const ROOM_FULL: &str = "This room is already full.";
 pub const ROOM_JOIN_RESTRICTED: &str = "Access denied. This room currently doesn't allow joining.";
+pub const ROUND_IN_PROGRESS: &str = "Joining not possible: Round is in progress.";
 pub const SUPER_POWER: &str = "Super power activated.";
+pub const TEAM_EXISTS: &str = "There's already a team with same name in the list.";
+pub const TEAM_NOT_OWNED: &str = "You can't remove a team you don't own.";
+pub const TEAM_ADD_RESTRICTED: &str = "This room currently does not allow adding new teams.";
+pub const TOO_MANY_HEDGEHOGS: &str = "Too many hedgehogs!";
+pub const TOO_MANY_TEAMS: &str = "Too many teams!";
 pub const USER_OFFLINE: &str = "Player is not online.";
 pub const VARIABLE_UPDATED: &str = "Server variable has been updated.";
 pub const WRONG_PROTOCOL: &str = "Room version incompatible to your Hedgewars version!";