Add server reconnection tokens and anteroom local list of used nicks default tip
authoralfadur
Sat, 22 Feb 2025 19:39:31 +0300
changeset 16120 5febd2bc5372
parent 16119 278533359a93
Add server reconnection tokens and anteroom local list of used nicks
rust/hedgewars-network-protocol/src/messages.rs
rust/hedgewars-network-protocol/src/parser.rs
rust/hedgewars-network-protocol/src/tests/parser.rs
rust/hedgewars-network-protocol/src/tests/test.rs
rust/hedgewars-server/src/core.rs
rust/hedgewars-server/src/core/anteroom.rs
rust/hedgewars-server/src/core/client.rs
rust/hedgewars-server/src/core/digest.rs
rust/hedgewars-server/src/core/server.rs
rust/hedgewars-server/src/handlers.rs
rust/hedgewars-server/src/handlers/actions.rs
rust/hedgewars-server/src/handlers/common.rs
rust/hedgewars-server/src/handlers/inanteroom.rs
--- a/rust/hedgewars-network-protocol/src/messages.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-network-protocol/src/messages.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -1,6 +1,8 @@
 use crate::types::{GameCfg, ServerVar, TeamInfo, VoteType};
 use std::iter::once;
 
+//todo!("add help message")
+
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub enum HwProtocolMessage {
     // common messages
@@ -13,7 +15,7 @@
     SuperPower,
     Info(String),
     // anteroom messages
-    Nick(String),
+    Nick(String, Option<String>),
     Proto(u16),
     Password(String, String),
     Checker(u16, String, String),
@@ -52,6 +54,7 @@
     Delegate(String),
     TeamChat(String),
     MaxTeams(u8),
+    //command line messages
     Fix,
     Unfix,
     Greeting(Option<String>),
@@ -120,6 +123,7 @@
     Bye(String),
 
     Nick(String),
+    Token(String),
     Proto(u16),
     AskPassword(String),
     ServerAuth(String),
@@ -281,7 +285,8 @@
             ToggleServerRegisteredOnly => msg!["CMD", "REGISTERED_ONLY"],
             SuperPower => msg!["CMD", "SUPER_POWER"],
             Info(info) => msg!["CMD", format!("INFO {}", info)],
-            Nick(nick) => msg!("NICK", nick),
+            Nick(nick, None) => msg!["NICK", nick],
+            Nick(nick, Some(token)) => msg!["NICK", nick, token],
             Proto(version) => msg!["PROTO", version],
             Password(p, s) => msg!["PASSWORD", p, s],
             Checker(i, n, p) => msg!["CHECKER", i, n, p],
@@ -381,6 +386,7 @@
             Redirect(port) => msg!["REDIRECT", port],
             Bye(msg) => msg!["BYE", msg],
             Nick(nick) => msg!["NICK", nick],
+            Token(token) => msg!["TOKEN", token],
             Proto(proto) => msg!["PROTO", proto],
             AskPassword(salt) => msg!["ASKPASSWORD", salt],
             ServerAuth(hash) => msg!["SERVER_AUTH", hash],
--- a/rust/hedgewars-network-protocol/src/parser.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-network-protocol/src/parser.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -220,7 +220,6 @@
     }
 
     alt((
-        message("NICK\n", a_line, Nick),
         message("INFO\n", a_line, Info),
         message("CHAT\n", a_line, Chat),
         message("PART", opt_arg, Part),
@@ -496,6 +495,10 @@
             ),
             |values| CheckedOk(values.unwrap_or_default()),
         ),
+        preceded(
+            tag("NICK\n"),
+            map(pair(a_line, opt_arg), |(nick, token)| Nick(nick, token)),
+        ),
     ))(input)
 }
 
--- a/rust/hedgewars-network-protocol/src/tests/parser.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-network-protocol/src/tests/parser.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -22,7 +22,7 @@
     assert_eq!(message(b"START_GAME\n\n"), Ok((&b""[..], StartGame)));
     assert_eq!(
         message(b"NICK\nit's me\n\n"),
-        Ok((&b""[..], Nick("it's me".to_string())))
+        Ok((&b""[..], Nick("it's me".to_string(), None)))
     );
     assert_eq!(message(b"PROTO\n51\n\n"), Ok((&b""[..], Proto(51))));
     assert_eq!(
--- a/rust/hedgewars-network-protocol/src/tests/test.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-network-protocol/src/tests/test.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -206,7 +206,7 @@
             6 => ToggleServerRegisteredOnly(),
             7 => SuperPower(),
             8 => Info(Ascii),
-            9 => Nick(Ascii),
+            9 => Nick(Ascii, Option<Ascii>),
             10 => Proto(u16),
             11 => Password(Ascii, Ascii),
             12 => Checker(u16, Ascii, Ascii),
@@ -264,7 +264,7 @@
 pub fn gen_server_msg() -> BoxedStrategy<HwServerMessage> where {
     use HwServerMessage::*;
 
-    let res = (0..=38).no_shrink().prop_flat_map(|i| {
+    let res = (0..=39).no_shrink().prop_flat_map(|i| {
         proto_msg_match!(i, def = Ping,
                     0 => Connected(Ascii, u32),
                     1 => Redirect(u16),
@@ -304,7 +304,8 @@
                     35 => Notice(Ascii),
                     36 => Warning(Ascii),
                     37 => Error(Ascii),
-                    38 => Replay(Vec<Ascii>)
+                    38 => Replay(Vec<Ascii>),
+                    39 => Token(Ascii)
                 )
     });
     res.boxed()
--- a/rust/hedgewars-server/src/core.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-server/src/core.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -1,5 +1,6 @@
 pub mod anteroom;
 pub mod client;
+pub mod digest;
 pub mod indexslab;
 pub mod room;
 pub mod server;
--- a/rust/hedgewars-server/src/core/anteroom.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-server/src/core/anteroom.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -1,5 +1,8 @@
 use super::{indexslab::IndexSlab, types::ClientId};
+use crate::core::client::HwClient;
+use crate::core::digest::Sha1Digest;
 use chrono::{offset, DateTime};
+use std::collections::{HashMap, HashSet};
 use std::{iter::Iterator, num::NonZeroU16};
 
 pub struct HwAnteroomClient {
@@ -51,8 +54,10 @@
 }
 
 pub struct HwAnteroom {
-    pub clients: IndexSlab<HwAnteroomClient>,
+    clients: IndexSlab<HwAnteroomClient>,
     bans: BanCollection,
+    taken_nicks: HashSet<String>,
+    reconnection_tokens: HashMap<String, String>,
 }
 
 impl HwAnteroom {
@@ -61,6 +66,8 @@
         HwAnteroom {
             clients,
             bans: BanCollection::new(),
+            taken_nicks: Default::default(),
+            reconnection_tokens: Default::default(),
         }
     }
 
@@ -82,8 +89,54 @@
         self.clients.insert(client_id, client);
     }
 
+    pub fn has_client(&self, id: ClientId) -> bool {
+        self.clients.contains(id)
+    }
+
+    pub fn get_client(&mut self, id: ClientId) -> &HwAnteroomClient {
+        &self.clients[id]
+    }
+
+    pub fn get_client_mut(&mut self, id: ClientId) -> &mut HwAnteroomClient {
+        &mut self.clients[id]
+    }
+
     pub fn remove_client(&mut self, client_id: ClientId) -> Option<HwAnteroomClient> {
         let client = self.clients.remove(client_id);
+        if let Some(HwAnteroomClient {
+            nick: Some(nick), ..
+        }) = &client
+        {
+            self.taken_nicks.remove(nick);
+        }
         client
     }
+
+    pub fn nick_taken(&self, nick: &str) -> bool {
+        self.taken_nicks.contains(nick)
+    }
+
+    pub fn remember_nick(&mut self, nick: String) {
+        self.taken_nicks.insert(nick);
+    }
+
+    pub fn forget_nick(&mut self, nick: &str) {
+        self.taken_nicks.remove(nick);
+    }
+
+    #[inline]
+    pub fn get_nick_token(&self, nick: &str) -> Option<&str> {
+        self.reconnection_tokens.get(nick).map(|s| &s[..])
+    }
+
+    #[inline]
+    pub fn register_nick_token(&mut self, nick: &str) -> Option<&str> {
+        if self.reconnection_tokens.contains_key(nick) {
+            None
+        } else {
+            let token = format!("{:x}", Sha1Digest::random());
+            self.reconnection_tokens.insert(nick.to_string(), token);
+            Some(&self.reconnection_tokens[nick])
+        }
+    }
 }
--- a/rust/hedgewars-server/src/core/client.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-server/src/core/client.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -1,8 +1,9 @@
 use super::types::ClientId;
 use bitflags::*;
+use std::ops::Deref;
 
 bitflags! {
-    pub struct ClientFlags: u8 {
+    pub struct ClientFlags: u16 {
         const IS_ADMIN = 0b0000_0001;
         const IS_MASTER = 0b0000_0010;
         const IS_READY = 0b0000_0100;
@@ -11,6 +12,7 @@
         const HAS_SUPER_POWER = 0b0010_0000;
         const IS_REGISTERED = 0b0100_0000;
         const IS_MODERATOR = 0b1000_0000;
+        const IS_REJOINED = 0b1_0000_0000;
 
         const NONE = 0b0000_0000;
         const DEFAULT = Self::NONE.bits;
@@ -72,6 +74,9 @@
     pub fn is_registered(&self) -> bool {
         self.contains(ClientFlags::IS_REGISTERED)
     }
+    pub fn is_rejoined(&self) -> bool {
+        self.contains(ClientFlags::IS_REJOINED)
+    }
 
     pub fn set_is_admin(&mut self, value: bool) {
         self.set(ClientFlags::IS_ADMIN, value)
@@ -94,4 +99,7 @@
     pub fn set_is_registered(&mut self, value: bool) {
         self.set(ClientFlags::IS_REGISTERED, value)
     }
+    pub fn set_is_rejoined(&mut self, value: bool) {
+        self.set(ClientFlags::IS_REJOINED, value)
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/hedgewars-server/src/core/digest.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -0,0 +1,55 @@
+use rand::{thread_rng, RngCore};
+use std::fmt::{Formatter, LowerHex};
+
+#[derive(PartialEq, Debug)]
+pub struct Sha1Digest([u8; 20]);
+
+impl Sha1Digest {
+    pub fn new(digest: [u8; 20]) -> Self {
+        Self(digest)
+    }
+
+    pub fn random() -> Self {
+        let mut result = Sha1Digest(Default::default());
+        thread_rng().fill_bytes(&mut result.0);
+        result
+    }
+}
+
+impl LowerHex for Sha1Digest {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
+        for byte in &self.0 {
+            write!(f, "{:02x}", byte)?;
+        }
+        Ok(())
+    }
+}
+
+impl PartialEq<&str> for Sha1Digest {
+    fn eq(&self, other: &&str) -> bool {
+        if other.len() != self.0.len() * 2 {
+            false
+        } else {
+            #[inline]
+            fn convert(c: u8) -> u8 {
+                if c > b'9' {
+                    c.wrapping_sub(b'a').saturating_add(10)
+                } else {
+                    c.wrapping_sub(b'0')
+                }
+            }
+
+            other
+                .as_bytes()
+                .chunks_exact(2)
+                .zip(&self.0)
+                .all(|(chars, byte)| {
+                    if let [hi, lo] = chars {
+                        convert(*lo) == byte & 0x0f && convert(*hi) == (byte & 0xf0) >> 4
+                    } else {
+                        unreachable!()
+                    }
+                })
+        }
+    }
+}
--- a/rust/hedgewars-server/src/core/server.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-server/src/core/server.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -11,10 +11,10 @@
 use crate::server::replaystorage::ReplayStorage;
 
 use bitflags::*;
-use log::*;
-use rand::{self, seq::SliceRandom, thread_rng, Rng};
+use rand::{self, thread_rng, Rng};
 use slab::Slab;
-use std::{borrow::BorrowMut, cmp::min, collections::HashSet, iter, mem::replace};
+use std::collections::HashMap;
+use std::{cmp::min, collections::HashSet, mem::replace};
 
 #[derive(Debug)]
 pub enum CreateRoomError {
--- a/rust/hedgewars-server/src/handlers.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-server/src/handlers.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -28,6 +28,7 @@
     types::{GameCfg, TeamInfo},
 };
 
+use crate::core::digest::Sha1Digest;
 use base64::encode;
 use log::*;
 use rand::{thread_rng, RngCore};
@@ -40,53 +41,6 @@
 mod inroom;
 mod strings;
 
-#[derive(PartialEq, Debug)]
-pub struct Sha1Digest([u8; 20]);
-
-impl Sha1Digest {
-    pub fn new(digest: [u8; 20]) -> Self {
-        Self(digest)
-    }
-}
-
-impl LowerHex for Sha1Digest {
-    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
-        for byte in &self.0 {
-            write!(f, "{:02x}", byte)?;
-        }
-        Ok(())
-    }
-}
-
-impl PartialEq<&str> for Sha1Digest {
-    fn eq(&self, other: &&str) -> bool {
-        if other.len() != self.0.len() * 2 {
-            false
-        } else {
-            #[inline]
-            fn convert(c: u8) -> u8 {
-                if c > b'9' {
-                    c.wrapping_sub(b'a').saturating_add(10)
-                } else {
-                    c.wrapping_sub(b'0')
-                }
-            }
-
-            other
-                .as_bytes()
-                .chunks_exact(2)
-                .zip(&self.0)
-                .all(|(chars, byte)| {
-                    if let [hi, lo] = chars {
-                        convert(*lo) == byte & 0x0f && convert(*hi) == (byte & 0xf0) >> 4
-                    } else {
-                        unreachable!()
-                    }
-                })
-        }
-    }
-}
-
 pub struct ServerState {
     pub server: HwServer,
     pub anteroom: HwAnteroom,
@@ -270,16 +224,14 @@
         HwProtocolMessage::Ping => response.add(Pong.send_self()),
         HwProtocolMessage::Pong => (),
         _ => {
-            if state.anteroom.clients.contains(client_id) {
-                match inanteroom::handle(state, client_id, response, message) {
+            if state.anteroom.has_client(client_id) {
+                match inanteroom::handle(&mut state.anteroom, client_id, response, message) {
                     LoginResult::Unchanged => (),
-                    LoginResult::Complete => {
-                        if let Some(client) = state.anteroom.remove_client(client_id) {
-                            let is_checker = client.is_checker;
-                            state.server.add_client(client_id, client);
-                            if !is_checker {
-                                common::get_lobby_join_data(&state.server, response);
-                            }
+                    LoginResult::Complete(client) => {
+                        let is_checker = client.is_checker;
+                        state.server.add_client(client_id, client);
+                        if !is_checker {
+                            common::get_lobby_join_data(&state.server, response);
                         }
                     }
                     LoginResult::Exit => {
@@ -292,12 +244,18 @@
                     HwProtocolMessage::Quit(Some(msg)) => {
                         common::remove_client(
                             &mut state.server,
+                            &mut state.anteroom,
                             response,
                             "User quit: ".to_string() + &msg,
                         );
                     }
                     HwProtocolMessage::Quit(None) => {
-                        common::remove_client(&mut state.server, response, "User quit".to_string());
+                        common::remove_client(
+                            &mut state.server,
+                            &mut state.anteroom,
+                            response,
+                            "User quit".to_string(),
+                        );
                     }
                     HwProtocolMessage::Info(nick) => {
                         if let Some(client) = state.server.find_client(&nick) {
@@ -394,7 +352,7 @@
         .filter(|_| !is_local)
         .and_then(|a| state.anteroom.find_ip_ban(a));
     if let Some(reason) = ban_reason {
-        response.add(HwServerMessage::Bye(reason).send_self());
+        response.add(Bye(reason).send_self());
         response.remove_client(client_id);
         false
     } else {
@@ -405,17 +363,20 @@
             .anteroom
             .add_client(client_id, encode(&salt), is_local);
 
-        response.add(
-            HwServerMessage::Connected(utils::SERVER_MESSAGE.to_owned(), utils::SERVER_VERSION)
-                .send_self(),
-        );
+        response
+            .add(Connected(utils::SERVER_MESSAGE.to_owned(), utils::SERVER_VERSION).send_self());
         true
     }
 }
 
 pub fn handle_client_loss(state: &mut ServerState, client_id: ClientId, response: &mut Response) {
     if state.anteroom.remove_client(client_id).is_none() {
-        common::remove_client(&mut state.server, response, "Connection reset".to_string());
+        common::remove_client(
+            &mut state.server,
+            &mut state.anteroom,
+            response,
+            "Connection reset".to_string(),
+        );
     }
 }
 
@@ -431,7 +392,7 @@
                 response.add(Bye(REGISTRATION_REQUIRED.to_string()).send_self());
                 response.remove_client(client_id);
             } else if is_registered {
-                let client = &state.anteroom.clients[client_id];
+                let client = state.anteroom.get_client(client_id);
                 response.add(AskPassword(client.server_salt.clone()).send_self());
             } else if let Some(client) = state.anteroom.remove_client(client_id) {
                 state.server.add_client(client_id, client);
@@ -508,7 +469,7 @@
 
 #[cfg(test)]
 mod test {
-    use super::Sha1Digest;
+    use crate::core::digest::Sha1Digest;
 
     #[test]
     fn hash_cmp_test() {
--- a/rust/hedgewars-server/src/handlers/actions.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-server/src/handlers/actions.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -1,19 +1,5 @@
-use crate::{
-    core::{
-        client::HwClient,
-        room::HwRoom,
-        room::{GameInfo, RoomFlags},
-        server::HwServer,
-        types::{ClientId, RoomId},
-    },
-    utils::to_engine_msg,
-};
-use hedgewars_network_protocol::{
-    messages::{server_chat, HwProtocolMessage, HwServerMessage, HwServerMessage::*},
-    types::{GameCfg, VoteType},
-};
-use rand::{distributions::Uniform, thread_rng, Rng};
-use std::{io, io::Write, iter::once, mem::replace};
+use crate::core::types::{ClientId, RoomId};
+use hedgewars_network_protocol::messages::{HwServerMessage, HwServerMessage::*};
 
 #[derive(Clone)]
 pub enum DestinationGroup {
--- a/rust/hedgewars-server/src/handlers/common.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-server/src/handlers/common.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -2,6 +2,7 @@
     actions::{Destination, DestinationGroup},
     Response,
 };
+use crate::core::anteroom::HwAnteroom;
 use crate::core::server::HwRoomOrServer;
 use crate::handlers::actions::ToPendingMessage;
 use crate::{
@@ -354,10 +355,16 @@
     }
 }
 
-pub fn remove_client(server: &mut HwServer, response: &mut Response, msg: String) {
+pub fn remove_client(
+    server: &mut HwServer,
+    anteroom: &mut HwAnteroom,
+    response: &mut Response,
+    msg: String,
+) {
     let client_id = response.client_id();
     let client = server.client(client_id);
     let nick = client.nick.clone();
+    anteroom.forget_nick(&nick);
 
     match server.get_room_control(client_id) {
         HwRoomOrServer::Room(mut control) => {
--- a/rust/hedgewars-server/src/handlers/inanteroom.rs	Mon Feb 17 16:38:24 2025 +0100
+++ b/rust/hedgewars-server/src/handlers/inanteroom.rs	Sat Feb 22 19:39:31 2025 +0300
@@ -20,42 +20,32 @@
 
 pub enum LoginResult {
     Unchanged,
-    Complete,
+    Complete(HwAnteroomClient),
     Exit,
 }
 
-fn completion_result<'a, I>(
-    mut other_clients: I,
-    client: &mut HwAnteroomClient,
+fn get_completion_result(
+    anteroom: &mut HwAnteroom,
+    client_id: ClientId,
     response: &mut super::Response,
-) -> LoginResult
-where
-    I: Iterator<Item = &'a HwClient>,
-{
-    let has_nick_clash = other_clients.any(|c| c.nick == *client.nick.as_ref().unwrap());
-
-    if has_nick_clash {
-        client.nick = None;
-        response.add(Notice("NickAlreadyInUse".to_string()).send_self());
+) -> LoginResult {
+    #[cfg(feature = "official-server")]
+    {
+        let client = anteroom.get_client(client_id);
+        response.request_io(super::IoTask::CheckRegistered {
+            nick: client.nick.as_ref().unwrap().clone(),
+        });
         LoginResult::Unchanged
-    } else {
-        #[cfg(feature = "official-server")]
-        {
-            response.request_io(super::IoTask::CheckRegistered {
-                nick: client.nick.as_ref().unwrap().clone(),
-            });
-            LoginResult::Unchanged
-        }
+    }
 
-        #[cfg(not(feature = "official-server"))]
-        {
-            LoginResult::Complete
-        }
+    #[cfg(not(feature = "official-server"))]
+    {
+        LoginResult::Complete(anteroom.remove_client(client_id).unwrap())
     }
 }
 
 pub fn handle(
-    server_state: &mut super::ServerState,
+    anteroom: &mut HwAnteroom,
     client_id: ClientId,
     response: &mut super::Response,
     message: HwProtocolMessage,
@@ -66,8 +56,15 @@
             response.add(Bye("User quit".to_string()).send_self());
             LoginResult::Exit
         }
-        HwProtocolMessage::Nick(nick) => {
-            let client = &mut server_state.anteroom.clients[client_id];
+        HwProtocolMessage::Nick(nick, token) => {
+            if anteroom.nick_taken(&nick) {
+                response.add(Notice("NickAlreadyInUse".to_string()).send_self());
+                return LoginResult::Unchanged;
+            }
+            let reconnect = token
+                .map(|t| anteroom.get_nick_token(&nick) == Some(&t[..]))
+                .unwrap_or(false);
+            let client = anteroom.get_client_mut(client_id);
 
             if client.nick.is_some() {
                 response.error(NICKNAME_PROVIDED);
@@ -77,17 +74,23 @@
                 LoginResult::Exit
             } else {
                 client.nick = Some(nick.clone());
+                let protocol_number = client.protocol_number;
+                if reconnect {
+                    client.is_registered = reconnect;
+                } else if let Some(token) = anteroom.register_nick_token(&nick) {
+                    response.add(Token(token.to_string()).send_self());
+                }
                 response.add(Nick(nick).send_self());
 
-                if client.protocol_number.is_some() {
-                    completion_result(server_state.server.iter_clients(), client, response)
+                if protocol_number.is_some() {
+                    get_completion_result(anteroom, client_id, response)
                 } else {
                     LoginResult::Unchanged
                 }
             }
         }
         HwProtocolMessage::Proto(proto) => {
-            let client = &mut server_state.anteroom.clients[client_id];
+            let client = anteroom.get_client_mut(client_id);
             if client.protocol_number.is_some() {
                 response.error(PROTOCOL_PROVIDED);
                 LoginResult::Unchanged
@@ -99,7 +102,7 @@
                 response.add(Proto(proto).send_self());
 
                 if client.nick.is_some() {
-                    completion_result(server_state.server.iter_clients(), client, response)
+                    get_completion_result(anteroom, client_id, response)
                 } else {
                     LoginResult::Unchanged
                 }
@@ -107,7 +110,7 @@
         }
         #[cfg(feature = "official-server")]
         HwProtocolMessage::Password(hash, salt) => {
-            let client = &server_state.anteroom.clients[client_id];
+            let client = anteroom.get_client(client_id);
 
             if let (Some(nick), Some(protocol)) = (client.nick.as_ref(), client.protocol_number) {
                 response.request_io(super::IoTask::GetAccount {
@@ -123,7 +126,7 @@
         }
         #[cfg(feature = "official-server")]
         HwProtocolMessage::Checker(protocol, nick, password) => {
-            let client = &mut server_state.anteroom.clients[client_id];
+            let client = anteroom.get_client_mut(client_id);
             if protocol == 0 {
                 response.error("Bad number.");
                 LoginResult::Unchanged
@@ -142,7 +145,8 @@
                 #[cfg(feature = "official-server")]
                 {
                     response.add(LogonPassed.send_self());
-                    LoginResult::Complete
+                    anteroom.remember_nick(nick);
+                    LoginResult::Complete(anteroom.remove_client(client_id).unwrap())
                 }
             }
         }