1
// This file is part of hnefatafl-copenhagen.
2
//
3
// hnefatafl-copenhagen is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU Affero General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// hnefatafl-copenhagen is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU Affero General Public License for more details.
12
//
13
// You should have received a copy of the GNU Affero General Public License
14
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15

            
16
#![allow(clippy::unwrap_used)]
17
#![cfg(test)]
18

            
19
use argon2::{PasswordHash, PasswordVerifier};
20

            
21
use crate::accounts::{Account, Accounts};
22

            
23
use super::*;
24

            
25
use std::net::TcpStream;
26
use std::process::{Child, Stdio};
27
use std::thread;
28
use std::time::{Duration, Instant};
29

            
30
const ADDRESS: &str = "localhost:49152";
31

            
32
struct Server(Child);
33

            
34
impl Server {
35
6
    fn new(release: bool) -> anyhow::Result<Server> {
36
6
        let server = if release {
37
            std::process::Command::new("./target/release/hnefatafl-server-full")
38
                .stdout(Stdio::null())
39
                .stderr(Stdio::null())
40
                .arg("--skip-the-data-file")
41
                .arg("--skip-advertising-updates")
42
                .arg("--skip-message")
43
                .spawn()?
44
        } else {
45
6
            std::process::Command::new("./target/debug/hnefatafl-server-full")
46
6
                .stdout(Stdio::null())
47
6
                .stderr(Stdio::null())
48
6
                .arg("--skip-the-data-file")
49
6
                .arg("--skip-advertising-updates")
50
6
                .arg("--skip-message")
51
6
                .spawn()?
52
        };
53

            
54
6
        Ok(Server(server))
55
6
    }
56
}
57

            
58
impl Drop for Server {
59
6
    fn drop(&mut self) {
60
6
        self.0.kill().unwrap();
61
6
    }
62
}
63

            
64
#[test]
65
6
fn capital_letters_fail() {
66
6
    let mut accounts = Accounts::default();
67

            
68
6
    let password = "A".to_string();
69
6
    let ctx = Argon2::default();
70

            
71
6
    let salt = SaltString::generate(&mut OsRng);
72
6
    let password_hash = ctx
73
6
        .hash_password(password.as_bytes(), &salt)
74
6
        .unwrap()
75
6
        .to_string();
76

            
77
6
    let account = Account {
78
6
        password: password_hash,
79
6
        logged_in: Some(0),
80
6
        ..Default::default()
81
6
    };
82

            
83
6
    accounts.0.insert("testing".to_string(), account);
84
6
    {
85
6
        let account = accounts.0.get_mut("testing").unwrap();
86
6

            
87
6
        let salt = SaltString::generate(&mut OsRng);
88
6
        let password_hash = ctx
89
6
            .hash_password(password.as_bytes(), &salt)
90
6
            .unwrap()
91
6
            .to_string();
92
6

            
93
6
        account.password = password_hash;
94
6
    }
95

            
96
    {
97
6
        let account = accounts.0.get_mut("testing").unwrap();
98
6
        let hash = PasswordHash::try_from(account.password.as_str()).unwrap();
99

            
100
6
        assert!(
101
6
            Argon2::default()
102
6
                .verify_password(password.as_bytes(), &hash)
103
6
                .is_ok()
104
        );
105
    }
106
6
}
107

            
108
#[test]
109
6
fn server_full() -> anyhow::Result<()> {
110
6
    std::process::Command::new("cargo")
111
6
        .arg("build")
112
6
        .arg("--bin")
113
6
        .arg("hnefatafl-server-full")
114
6
        .output()?;
115

            
116
6
    let _server = Server::new(false);
117
6
    thread::sleep(Duration::from_millis(10));
118

            
119
6
    let mut buf = String::new();
120
6
    let mut socket_1 = TcpStream::connect(ADDRESS)?;
121
5
    let mut reader_1 = BufReader::new(socket_1.try_clone()?);
122

            
123
5
    socket_1.write_all(format!("{VERSION_ID} create_account player-1\n").as_bytes())?;
124
5
    reader_1.read_line(&mut buf)?;
125
5
    assert_eq!(buf, "= login\n");
126
5
    buf.clear();
127

            
128
5
    socket_1.write_all(b"change_password\n")?;
129
5
    reader_1.read_line(&mut buf)?;
130
5
    assert_eq!(buf, "= change_password\n");
131
5
    buf.clear();
132

            
133
5
    socket_1.write_all(b"new_game attacker rated fischer 900000 10 11\n")?;
134
5
    reader_1.read_line(&mut buf)?;
135
5
    assert_eq!(
136
        buf,
137
        "= new_game game 0 player-1 _ rated fischer 900000 10 11 _ false {}\n"
138
    );
139
5
    buf.clear();
140

            
141
5
    let mut socket_2 = TcpStream::connect(ADDRESS)?;
142
5
    let mut reader_2 = BufReader::new(socket_2.try_clone()?);
143

            
144
5
    socket_2.write_all(format!("{VERSION_ID} create_account player-2\n").as_bytes())?;
145
5
    reader_2.read_line(&mut buf)?;
146
5
    assert_eq!(buf, "= login\n");
147
5
    buf.clear();
148

            
149
5
    socket_2.write_all(b"join_game_pending 0\n")?;
150
5
    reader_2.read_line(&mut buf)?;
151
5
    assert_eq!(buf, "= join_game_pending 0\n");
152
5
    buf.clear();
153

            
154
5
    reader_1.read_line(&mut buf)?;
155
5
    assert_eq!(buf, "= challenge_requested 0\n");
156
5
    buf.clear();
157

            
158
    // Fixme: "join_game_pending 0\n" should not be allowed!
159
5
    socket_1.write_all(b"join_game 0\n")?;
160
5
    reader_1.read_line(&mut buf)?;
161
5
    assert_eq!(
162
        buf,
163
        "= join_game player-1 player-2 rated fischer 900000 10 11\n"
164
    );
165
5
    buf.clear();
166

            
167
5
    reader_2.read_line(&mut buf)?;
168
5
    assert_eq!(
169
        buf,
170
        "= join_game player-1 player-2 rated fischer 900000 10 11\n"
171
    );
172
5
    buf.clear();
173

            
174
5
    reader_1.read_line(&mut buf)?;
175
5
    assert_eq!(buf, "game 0 generate_move attacker\n");
176
5
    buf.clear();
177

            
178
5
    socket_1.write_all(b"game 0 play attacker resigns _\n")?;
179
5
    reader_1.read_line(&mut buf)?;
180
5
    assert_eq!(buf, "= game_over 0 defender_wins\n");
181
5
    buf.clear();
182

            
183
5
    reader_2.read_line(&mut buf)?;
184
5
    assert_eq!(buf, "game 0 play attacker resigns \n");
185
5
    buf.clear();
186

            
187
5
    reader_2.read_line(&mut buf)?;
188
5
    assert_eq!(buf, "= game_over 0 defender_wins\n");
189
5
    buf.clear();
190

            
191
5
    Ok(())
192
6
}
193

            
194
// echo "* soft nofile 1000000" >> /etc/security/limits.conf
195
// echo "* hard nofile 1000000" >> /etc/security/limits.conf
196
// fish
197
// ulimit --file-descriptor-count 1000000
198
#[ignore = "too slow, too many tcp connections"]
199
#[test]
200
fn many_clients() -> anyhow::Result<()> {
201
    std::process::Command::new("cargo")
202
        .arg("build")
203
        .arg("--release")
204
        .arg("--bin")
205
        .arg("hnefatafl-server-full")
206
        .output()?;
207

            
208
    let _server = Server::new(true);
209
    thread::sleep(Duration::from_millis(10));
210

            
211
    let t0 = Instant::now();
212

            
213
    let mut handles = Vec::new();
214
    for i in 0..1_000 {
215
        handles.push(thread::spawn(move || {
216
            let mut buf = String::new();
217
            let mut tcp = TcpStream::connect(ADDRESS).unwrap();
218
            let mut reader = BufReader::new(tcp.try_clone().unwrap());
219

            
220
            tcp.write_all(format!("{VERSION_ID} create_account q-player-{i}\n").as_bytes())
221
                .unwrap();
222

            
223
            reader.read_line(&mut buf).unwrap();
224
            buf.clear();
225
        }));
226
    }
227

            
228
    for handle in handles {
229
        handle.join().unwrap();
230
    }
231

            
232
    let t1 = Instant::now();
233
    println!("many clients: {:?}", t1 - t0);
234

            
235
    Ok(())
236
}