Skip to main content

why2_chat/network/
mod.rs

1/*
2This is part of WHY2
3Copyright (C) 2022-2026 Václav Šmejkal
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19//MODULES
20#[cfg(feature = "server")]
21pub mod server;
22
23#[cfg(feature = "client")]
24pub mod client;
25
26pub mod voice;
27
28use std::
29{
30    fs::File,
31    path::PathBuf,
32    net::TcpStream,
33    io::{ Read, Write },
34    sync::
35    {
36        Arc,
37        Mutex,
38        LazyLock,
39    },
40};
41
42use wincode::{ SchemaWrite, SchemaRead };
43
44use sha2::Sha256;
45
46use dashmap::DashMap;
47
48use why2::consts;
49
50use crate::
51{
52    crypto,
53    consts::
54    {
55        Streams,
56        self as chat_consts,
57    },
58};
59
60#[cfg(feature = "client")]
61use crate::options;
62
63#[cfg(feature = "server")]
64use std::time::{ Instant, Duration };
65
66#[cfg(feature = "server")]
67use crate::config;
68
69//STRUCTS
70#[derive(SchemaWrite, SchemaRead, PartialEq, Clone)]
71pub enum MessageCode //CONTROL CODES
72{
73    KeyExchange,        //SERVER <> CLIENT | KEY EXCHANGE
74    Rekey,              //SERVER -> CLIENT | TRIGGER KEY EXCHANGE (USED FOR RE-KEYING)
75    Welcome,            //SERVER -> CLIENT | INFORMATIONS
76    Disconnect,         //SERVER <> CLIENT | QUIT COMMUNICATION
77    Username,           //SERVER -> CLIENT | PICK USERNAME
78    PasswordL,          //SERVER -> CLIENT | LOGIN
79    PasswordR,          //SERVER -> CLIENT | REGISTER
80    Accept,             //SERVER -> CLIENT | START CHATTING
81    Join,               //SERVER -> CLIENT | CLIENT JOIN MESSAGE
82    Leave,              //SERVER -> CLIENT | CLIENT LEAVE MESSAGE
83    List,               //CLIENT <> SERVER | PRINT CONNECTED USERS
84    PrivateMessage,     //CLIENT <> SERVER | SEND MESSAGE ONLY TO ONE CLIENT
85    PrivateMessageBack, //SERVER -> CLIENT | SEND MESSAGE BACK TO SENDER
86    SpamWarning,        //SERVER -> CLIENT | TELL CLIENT TO CALM TF DOWN
87    RegisterDisabled,   //SERVER -> CLIENT | REGISTRATION IS DISABLED
88    Version,            //SERVER <> CLIENT | ASK CLIENT FOR THEIR PKG VERSION
89    Channel,            //SERVER <> CLIENT | CHANNEL CHANGE
90    Voice,              //CLIENT <> SERVER | ESTABLISH VOICE CONNECTION
91    ChannelJoin,        //SERVER -> CLIENT | CLIENT JOINED VOICE CHANNEL
92    ChannelLeave,       //SERVER -> CLIENT | CLIENT LEFT VOICE CHANNEL
93    VoiceClients,       //SERVER -> CLIENT | TELL CLIENT ALL CONNECTED VOICE CLIENTS
94    Upload,             //CLIENT <> SERVER | REQUEST FILE UPLOAD (OR APPROVAL FROM SERVER)
95    Download,           //CLIENT <> SERVER | DOWNLOAD FILE FROM SERVER
96    Uploaded,           //SERVER -> CLIENT | ANNOUNCE NEW UPLOADED FILE
97    Files,              //CLIENT <> SERVER | LIST UPLOADED FILES
98    InvalidUsage,       //SERVER -> CLIENT | INVALID PARAMETERS TO A COMMAND
99    InvalidFeature,     //SERVER -> CLIENT | CLIENT REQUESTED DISABLED FEATURE
100    KeepAlive,          //SERVER <> CLIENT | A BIT LESS STUPID KEEP-ALIVE
101}
102
103#[derive(SchemaWrite, SchemaRead, Clone)]
104pub struct MessageColors //COLORS OF MESSAGE
105{
106    pub username_color: Option<u8>, //COLOR OF USERNAME
107    pub message_color: Option<u8>,  //COLOR OF MESSAGE
108}
109
110#[derive(SchemaWrite, SchemaRead, Clone)]
111pub struct FilePayload //FILE CHUNK
112{
113    pub uid: u64,                 //UPLOAD UID
114    pub data: Option<Vec<u8>>,    //BINARY DATA
115    pub size: Option<u64>,        //FILE SIZE
116    pub filename: Option<String>, //FILE NAME
117    pub hash: Option<[u8; 32]>,   //FILE HASH
118}
119
120#[derive(SchemaWrite, SchemaRead, Clone)]
121pub struct MessagePacket //MESSAGE PACKET (WHAT IS BEING SENT)
122{
123    pub text: Option<String>,      //MESSAGE
124    pub username: Option<String>,  //USERNAME (SENT ONLY BY SERVER, AS SERVER DOESN'T ACCEPT USERNAMES FROM CLIENT)
125    pub id: Option<usize>,         //ID OF USER
126    pub code: Option<MessageCode>, //CONTROL CODE
127    pub colors: MessageColors,     //MESSAGE COLORS
128    pub seq: usize,                //SEQUENCE NUMBER
129    pub file: Option<FilePayload>, //FILE UPLOADED BY CLIENT
130}
131
132pub struct ActiveFileshare //ACTIVE FILE UPLOAD
133{
134    pub file: File,                                  //TARGET FILE (SERVER-SIDE)
135    pub size: u64,                                   //EXPECTED FILE SIZE
136    pub current_size: u64,                           //CURRENT SIZE
137    pub hash: [u8; 32],                              //SHA256 HASH OF FINAL FILE
138    pub hasher: Sha256,                              //HASHER
139    pub filename: String,                            //FILENAME
140    #[cfg(feature = "server")] pub client_id: usize, //ID OF SENDER
141}
142
143//LISTS
144pub static ACTIVE_FILESHARES: LazyLock<DashMap<u64, ActiveFileshare>> = LazyLock::new(|| DashMap::new()); //LIST FOR ACTIVE FILE UPLOADS
145
146//IMPLEMENTATIONS
147impl Default for MessagePacket //DEFAULT
148{
149    fn default() -> Self
150    {
151        Self
152        {
153            text: None,
154            username: None,
155            id: None,
156            code: None,
157            colors: MessageColors
158            {
159                username_color: None,
160                message_color: None,
161            },
162            seq: 0,
163            file: None,
164        }
165    }
166}
167
168impl Default for FilePayload
169{
170    fn default() -> Self
171    {
172        Self
173        {
174            uid: 0,
175            data: None,
176            size: None,
177            filename: None,
178            hash: None,
179        }
180    }
181}
182
183//FUNCTIONS
184//PRIVATE
185fn obfuscate_data(mut data: Vec<u8>) -> Vec<u8> //XOR BYTES (USED FOR OBFUSCATION)
186{
187    for (i, byte) in data.iter_mut().enumerate()
188    {
189        //XOR EACH BYTE WITH OBFUSCATION KEY
190        *byte ^= chat_consts::OBFUSCATION_KEY[i % chat_consts::OBFUSCATION_KEY.len()];
191    }
192
193    data
194}
195
196//PUBLIC
197pub fn send(stream: &mut TcpStream, mut packet: MessagePacket, keys: Option<&chat_consts::SharedKeys>) //SEND packet TO stream
198{
199    //ADD SEQUENCE NUMBER TO packet (FROM CLIENT)
200    #[cfg(feature = "client")]
201    {
202        packet.seq = options::get_seq() + 1;
203        options::set_seq(packet.seq);
204    }
205
206    //ADD SEQUENCE NUMBER TO packet (FROM SERVER)
207    #[cfg(feature = "server")]
208    {
209        let peer_addr = stream.peer_addr().ok();
210        if peer_addr.is_some() && let Some(mut conn) = server::CONNECTIONS.get_mut(&peer_addr.unwrap())
211        {
212            if conn.is_authenticated()
213            {
214                packet.seq = conn.server_seq().unwrap() + 1;
215                *conn.server_seq_mut().unwrap() = packet.seq;
216            }
217        }
218    }
219
220    //ENCODE THE PACKET STRUCT TO Vec<u8>
221    let packet_bytes = wincode::serialize(&packet).expect("Encoding packet failed");
222
223    let mut final_bytes = if let Some(keys) = keys
224    {
225        crypto::encrypt_packet::<{ consts::DEFAULT_GRID_WIDTH }, { consts::DEFAULT_GRID_HEIGHT }>(packet_bytes, keys)
226    } else
227    {
228        obfuscate_data(packet_bytes) //NO ENCRYPTION, OBFUSCATE
229    };
230
231    //CONVERT ENCRYPTED OUTPUT TO BYTES ([LENGTH][DATA])
232    let packet_len = final_bytes.len();
233    let mut transmission_packet = Vec::with_capacity(4 + packet_len);
234    transmission_packet.extend_from_slice(&(packet_len as u32).to_be_bytes());
235    transmission_packet.append(&mut final_bytes);
236
237    //SEND
238    let _ = stream.write_all(&transmission_packet);
239    stream.flush().expect("Flushing stream failed");
240}
241
242pub fn receive(streams: &mut Streams, keys: Option<&chat_consts::SharedKeys>) -> Option<MessagePacket>
243{
244    //SERVER SIDE PACKET SIZE LIMIT
245    #[cfg(feature = "server")]
246    let max_packet_size: usize;
247
248    //SERVER SIDE SPAM PROTECTION
249    #[cfg(feature = "server")]
250    let spam_protection = config::read_config::<bool>("spam_protection");
251
252    #[cfg(feature = "server")]
253    let peer_addr = streams.0.peer_addr().ok()?; //GET CURRENT PEER ADDRESS
254
255    //SETUP LIMITS
256    #[cfg(feature = "server")]
257    {
258        //CHECK IF CLIENT IS AUTHENTICATED
259        let authenticated = server::CONNECTIONS.get(&peer_addr)
260            .map(|conn| conn.is_authenticated())
261            .unwrap_or(false);
262
263        //ALLOW BIG MESSAGES WHEN SPAM PROTECTION IS OFF AND CLIENT IS AUTHENTICATED
264        max_packet_size = if !spam_protection && authenticated
265        {
266            usize::MAX
267        } else //SET MAX PACKET SIZE IF SPAM PROTECTION IS ENABLED
268        {
269            config::read_config("max_packet_size")
270        };
271    }
272
273    //READ MESSAGE LENGTH
274    let mut len_buf = [0u8; 4];
275    if streams.0.read_exact(&mut len_buf).is_err() //READ LENGTH
276    {
277        #[cfg(feature = "server")]
278        server::remove_connection(&peer_addr, false, Some("length"));
279        return None;
280    }
281    let len = u32::from_be_bytes(len_buf) as usize;
282
283    //CHECK PACKET SIZE
284    #[cfg(feature = "server")]
285    if len > max_packet_size
286    {
287        server::remove_connection(&peer_addr, true, Some("length"));
288        return None;
289    }
290
291    //READ REST OF PACKET
292    let mut decoded_packet = vec![0u8; len];
293    if streams.0.read_exact(&mut decoded_packet).is_err() //READ
294    {
295        #[cfg(feature = "server")]
296        server::remove_connection(&peer_addr, false, Some("length"));
297        return None;
298    }
299
300    //DECRYPT
301    if let Some(keys) = keys
302    {
303        decoded_packet = match crypto::decrypt_packet::<{ consts::DEFAULT_GRID_WIDTH }, { consts::DEFAULT_GRID_HEIGHT }>(decoded_packet, keys)
304        {
305            Some(d) => d,
306            None => //INVALID MAC
307            {
308                //LOG IF ON SERVER
309                #[cfg(feature = "server")]
310                log::warn!("HMAC verification failed: {}", peer_addr);
311
312                return None;
313            }
314        }
315    } else
316    {
317        decoded_packet = obfuscate_data(decoded_packet); //NO ENCRYPTION, REMOVE OBFUSCATION
318    }
319
320    //DESERIALIZE AND RETURN
321    match wincode::deserialize::<MessagePacket>(&decoded_packet)
322    {
323        Ok(packet) =>
324        {
325            //SPAM, SEQ & LENGTH CHECKS (SERVER)
326            #[cfg(feature = "server")]
327            {
328                //ACTIVITY TIMER
329                let mut spam_warning = false;
330                let mut shared_key = None;
331                let mut disconnect = false;
332                let mut grace = true;
333
334                if let Some(mut conn) = server::CONNECTIONS.get_mut(&peer_addr)
335                {
336                    //SPAM
337                    if packet.code != Some(MessageCode::KeepAlive) &&
338                        packet.code != Some(MessageCode::KeyExchange) &&
339                        packet.file.is_none()
340                    {
341                        //MESSAGE SIZE (ONLY FOR AUTHENTICATED)
342                        if let Some(text) = &packet.text && conn.is_authenticated()
343                        {
344                            disconnect = text.len() > config::read_config("max_message_length");
345                        }
346
347                        if !disconnect && config::read_config("spam_protection") && conn.is_authenticated() &&
348                            Instant::now().duration_since(*conn.last_activity()) <
349                            Duration::from_millis(config::read_config::<u64>("min_message_delay"))
350                        {
351                            //INCREMENT SPAM VIOLATIONS
352                            *conn.spam_violations_mut().unwrap() += 1;
353
354                            //WARN
355                            spam_warning = true;
356                            shared_key = conn.keys().cloned();
357
358                            //CHECK FOR TOO MANY VIOLATIONS
359                            disconnect = *conn.spam_violations().unwrap() > config::read_config::<usize>("max_message_delay_violations");
360                        }
361
362                        *conn.last_activity_mut() = Instant::now(); //RESET last_activity
363                    }
364
365                    //SEQ
366                    if packet.seq > *conn.seq() //VALID SEQ
367                    {
368                        //SET SEQ TO CURRENT
369                        *conn.seq_mut() = packet.seq;
370                    } else
371                    {
372                        //INVALID SEQ
373                        grace = false;
374                        disconnect = true;
375                    }
376                    drop(conn); //PREVENT DEADLOCK
377
378                    //SEND WARNING CODE
379                    if spam_warning
380                    {
381                        server::send_code(&mut streams.1.lock().unwrap(), None, MessageCode::SpamWarning, shared_key.as_ref());
382                    }
383
384                    //TOO MANY VIOLATIONS, BYE
385                    if disconnect
386                    {
387                        server::remove_connection(&peer_addr, grace, Some(if !grace { "SEQ" } else { "SPAM" }));
388                        return None;
389                    }
390                }
391            }
392
393            //VERIFY SEQUENCE NUMBER
394            #[cfg(feature = "client")] //ON CLIENT
395            {
396                if packet.seq > options::get_server_seq() || options::get_server_seq() == 0 || packet.code == Some(MessageCode::Disconnect) //VALID
397                {
398                    //SET SEQ
399                    options::set_server_seq(packet.seq);
400                } else //INVALID, DISCONNECT
401                {
402                    return None;
403                }
404            }
405
406            return Some(packet);
407        },
408
409        Err(_) =>
410        {
411            //FORCEFULLY DISCONNECT CLIENT ON INVALID PACKET
412            #[cfg(feature = "server")]
413            server::remove_connection(&peer_addr, false, Some("packet"));
414
415            return None;
416        }
417    }
418}
419
420pub fn send_file //CHUNK FILE AND SEND TO STREAM
421(
422    path: PathBuf,
423    write_stream: Arc<Mutex<TcpStream>>,
424    uid: u64,
425    code: MessageCode,
426    keys: Option<&chat_consts::SharedKeys>
427)
428{
429    let mut file = File::open(path).expect("Cannot open file for upload");
430    let mut buffer = vec![0; chat_consts::UPLOAD_CHUNK_SIZE];
431
432    //LOOP READING
433    loop
434    {
435        match file.read(&mut buffer)
436        {
437            Ok(0) => break, //EOF
438            Ok(bytes) =>
439            {
440                //SEND FILE CHUNK
441                send(&mut write_stream.lock().unwrap(), MessagePacket
442                {
443                    code: Some(code.clone()),
444                    file: Some(FilePayload
445                    {
446                        uid,
447                        data: Some(buffer[..bytes].to_vec()),
448                        ..Default::default()
449                    }),
450                    ..Default::default()
451                }, keys);
452            },
453            Err(_) => {}, //TODO: Implement
454        }
455    }
456}