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};
35
36use wincode::{ SchemaWrite, SchemaRead };
37
38use why2::consts;
39
40use crate::
41{
42    crypto,
43    consts::
44    {
45        Streams,
46        self as chat_consts,
47    },
48};
49
50#[cfg(feature = "client")]
51use crate::options;
52
53#[cfg(feature = "server")]
54use std::time::{ Instant, Duration };
55
56#[cfg(feature = "server")]
57use crate::config;
58
59//ENUMS
60#[derive(SchemaWrite, SchemaRead, PartialEq, Clone)]
61pub enum MessageCode //CONTROL CODES
62{
63    KeyExchange,        //SERVER <> CLIENT | KEY EXCHANGE
64    Rekey,              //SERVER -> CLIENT | TRIGGER KEY EXCHANGE (USED FOR RE-KEYING)
65    Welcome,            //SERVER -> CLIENT | INFORMATIONS
66    Disconnect,         //SERVER <> CLIENT | QUIT COMMUNICATION
67    Username,           //SERVER -> CLIENT | PICK USERNAME
68    PasswordL,          //SERVER -> CLIENT | LOGIN
69    PasswordR,          //SERVER -> CLIENT | REGISTER
70    Accept,             //SERVER -> CLIENT | START CHATTING
71    Join,               //SERVER -> CLIENT | CLIENT JOIN MESSAGE
72    Leave,              //SERVER -> CLIENT | CLIENT LEAVE MESSAGE
73    List,               //CLIENT <> SERVER | PRINT CONNECTED USERS
74    PrivateMessage,     //CLIENT <> SERVER | SEND MESSAGE ONLY TO ONE CLIENT
75    PrivateMessageBack, //SERVER -> CLIENT | SEND MESSAGE BACK TO SENDER
76    SpamWarning,        //SERVER -> CLIENT | TELL CLIENT TO CALM TF DOWN
77    RegisterDisabled,   //SERVER -> CLIENT | REGISTRATION IS DISABLED
78    Version,            //SERVER <> CLIENT | ASK CLIENT FOR THEIR PKG VERSION
79    Channel,            //SERVER <> CLIENT | CHANNEL CHANGE
80    Voice,              //CLIENT <> SERVER | ESTABLISH VOICE CONNECTION
81    ChannelJoin,        //SERVER -> CLIENT | CLIENT JOINED VOICE CHANNEL
82    ChannelLeave,       //SERVER -> CLIENT | CLIENT LEFT VOICE CHANNEL
83    VoiceClients,       //SERVER -> CLIENT | TELL CLIENT ALL CONNECTED VOICE CLIENTS
84    Upload,             //CLIENT <> SERVER | REQUEST FILE UPLOAD (OR APPROVAL FROM SERVER)
85    Download,           //CLIENT <> SERVER | DOWNLOAD FILE FROM SERVER
86    Uploaded,           //SERVER -> CLIENT | ANNOUNCE NEW UPLOADED FILE
87    Files,              //CLIENT <> SERVER | LIST UPLOADED FILES
88    UploadLimit,        //SERVER -> CLIENT | MAX CONCURRENT UPLOADS REACHED
89    InvalidUsage,       //SERVER -> CLIENT | INVALID PARAMETERS TO A COMMAND
90    InvalidFeature,     //SERVER -> CLIENT | CLIENT REQUESTED DISABLED FEATURE
91    KeepAlive,          //SERVER <> CLIENT | A BIT LESS STUPID KEEP-ALIVE
92}
93
94//STRUCTS
95#[derive(SchemaWrite, SchemaRead, Clone)]
96pub struct MessageColors //COLORS OF MESSAGE
97{
98    pub username_color: Option<u8>, //COLOR OF USERNAME
99    pub message_color: Option<u8>,  //COLOR OF MESSAGE
100}
101
102#[derive(SchemaWrite, SchemaRead, Clone)]
103pub struct FilePayload //FILE CHUNK
104{
105    pub token: Option<[u8; 32]>,  //UPLOAD CHANNEL TOKEN
106    pub uid: u64,                 //UPLOAD UID
107    pub data: Option<Vec<u8>>,    //BINARY DATA
108    pub size: Option<u64>,        //FILE SIZE
109    pub filename: Option<String>, //FILE NAME
110    pub hash: Option<[u8; 32]>,   //FILE HASH
111}
112
113#[derive(SchemaWrite, SchemaRead, Clone)]
114pub struct MessagePacket //MESSAGE PACKET (WHAT IS BEING SENT)
115{
116    pub text: Option<String>,      //MESSAGE
117    pub username: Option<String>,  //USERNAME (SENT ONLY BY SERVER, AS SERVER DOESN'T ACCEPT USERNAMES FROM CLIENT)
118    pub id: Option<usize>,         //ID OF USER
119    pub code: Option<MessageCode>, //CONTROL CODE
120    pub colors: MessageColors,     //MESSAGE COLORS
121    pub seq: usize,                //SEQUENCE NUMBER
122    pub file: Option<FilePayload>, //FILE UPLOADED BY CLIENT
123}
124
125//IMPLEMENTATIONS
126impl Default for MessagePacket //DEFAULT
127{
128    fn default() -> Self
129    {
130        Self
131        {
132            text: None,
133            username: None,
134            id: None,
135            code: None,
136            colors: MessageColors
137            {
138                username_color: None,
139                message_color: None,
140            },
141            seq: 0,
142            file: None,
143        }
144    }
145}
146
147impl Default for FilePayload
148{
149    fn default() -> Self
150    {
151        Self
152        {
153            token: None,
154            uid: 0,
155            data: None,
156            size: None,
157            filename: None,
158            hash: None,
159        }
160    }
161}
162
163//FUNCTIONS
164//PRIVATE
165fn obfuscate_data(mut data: Vec<u8>, obfuscation_key: [u8; 32]) -> Vec<u8> //XOR BYTES (USED FOR OBFUSCATION)
166{
167    for (i, byte) in data.iter_mut().enumerate()
168    {
169        //XOR EACH BYTE WITH OBFUSCATION KEY
170        *byte ^= obfuscation_key[i % obfuscation_key.len()];
171    }
172
173    data
174}
175
176//PUBLIC
177pub fn send //SEND packet TO stream
178(
179    stream: &mut TcpStream,
180    mut packet: MessagePacket,
181    keys: Option<&chat_consts::SharedKeys>,
182    #[cfg(feature = "server")] seq: Option<&mut usize>, //LOCAL/GLOBAL SEQ COUNTER
183)
184{
185    //ADD SEQUENCE NUMBER TO packet (FROM CLIENT)
186    #[cfg(feature = "client")]
187    {
188        packet.seq = options::get_seq() + 1;
189        options::set_seq(packet.seq);
190    }
191
192    //ADD SEQUENCE NUMBER TO packet (FROM SERVER)
193    #[cfg(feature = "server")]
194    {
195        match seq
196        {
197            Some(local_seq) =>
198            {
199                *local_seq += 1;
200                packet.seq = *local_seq;
201            },
202
203            None =>
204            {
205                let peer_addr = stream.peer_addr().ok();
206                if peer_addr.is_some() && let Some(mut conn) = server::CONNECTIONS.get_mut(&peer_addr.unwrap())
207                {
208                    if conn.is_authenticated()
209                    {
210                        packet.seq = conn.server_seq().unwrap() + 1;
211                        *conn.server_seq_mut().unwrap() = packet.seq;
212                    }
213                }
214            }
215        }
216    }
217
218    //ENCODE THE PACKET STRUCT TO Vec<u8>
219    let packet_bytes = wincode::serialize(&packet).expect("Encoding packet failed");
220
221    let mut final_bytes = if let Some(keys) = keys
222    {
223        crypto::encrypt_packet::<{ consts::DEFAULT_GRID_WIDTH }, { consts::DEFAULT_GRID_HEIGHT }>(packet_bytes, keys)
224    } else
225    {
226        let key =
227        {
228            #[cfg(feature = "server")]
229            {
230                stream.peer_addr().ok()
231                    .and_then(|addr| server::CONNECTIONS.get(&addr))
232                    .and_then(|c| c.obfuscation_key().cloned())
233                    .unwrap_or_else(|| [0u8; 32])
234            }
235
236            #[cfg(feature = "client")]
237            {
238                options::get_obfuscation_key()
239            }
240        };
241
242        obfuscate_data(packet_bytes, key) //NO ENCRYPTION, OBFUSCATE
243    };
244
245    //CONVERT ENCRYPTED OUTPUT TO BYTES ([LENGTH][DATA])
246    let packet_len = final_bytes.len();
247    let mut transmission_packet = Vec::with_capacity(4 + packet_len);
248    transmission_packet.extend_from_slice(&(packet_len as u32).to_be_bytes());
249    transmission_packet.append(&mut final_bytes);
250
251    //SEND
252    let _ = stream.write_all(&transmission_packet);
253    stream.flush().expect("Flushing stream failed");
254}
255
256pub fn receive
257(
258    streams: &mut Streams,
259    keys: Option<&chat_consts::SharedKeys>,
260    seq: Option<&mut usize> //LOCAL/GLOBAL SEQ
261) -> Option<MessagePacket>
262{
263    //SERVER SIDE PACKET SIZE LIMIT
264    #[cfg(feature = "server")]
265    let max_packet_size: usize;
266
267    //SERVER SIDE SPAM PROTECTION
268    #[cfg(feature = "server")]
269    let spam_protection = config::read_config::<bool>("spam_protection");
270
271    #[cfg(feature = "server")]
272    let peer_addr = streams.0.peer_addr().ok()?; //GET CURRENT PEER ADDRESS
273
274    //SETUP LIMITS
275    #[cfg(feature = "server")]
276    {
277        //CHECK IF CLIENT IS AUTHENTICATED
278        let authenticated = server::CONNECTIONS.get(&peer_addr)
279            .map(|conn| conn.is_authenticated())
280            .unwrap_or(false);
281
282        //ALLOW BIG MESSAGES WHEN SPAM PROTECTION IS OFF AND CLIENT IS AUTHENTICATED
283        max_packet_size = if !spam_protection && authenticated
284        {
285            usize::MAX
286        } else //SET MAX PACKET SIZE IF SPAM PROTECTION IS ENABLED
287        {
288            config::read_config("max_packet_size")
289        };
290    }
291
292    //READ MESSAGE LENGTH
293    let mut len_buf = [0u8; 4];
294    if streams.0.read_exact(&mut len_buf).is_err() //READ LENGTH
295    {
296        #[cfg(feature = "server")]
297        server::remove_connection(&peer_addr, false, Some("length"));
298        return None;
299    }
300    let len = u32::from_be_bytes(len_buf) as usize;
301
302    //CHECK PACKET SIZE
303    #[cfg(feature = "server")]
304    if len > max_packet_size
305    {
306        server::remove_connection(&peer_addr, true, Some("length"));
307        return None;
308    }
309
310    //READ REST OF PACKET
311    let mut decoded_packet = vec![0u8; len];
312    if streams.0.read_exact(&mut decoded_packet).is_err() //READ
313    {
314        #[cfg(feature = "server")]
315        server::remove_connection(&peer_addr, false, Some("length"));
316        return None;
317    }
318
319    //DECRYPT
320    if let Some(keys) = keys
321    {
322        decoded_packet = match crypto::decrypt_packet::<{ consts::DEFAULT_GRID_WIDTH }, { consts::DEFAULT_GRID_HEIGHT }>(decoded_packet, keys)
323        {
324            Some(d) => d,
325            None => //INVALID MAC
326            {
327                //LOG IF ON SERVER
328                #[cfg(feature = "server")]
329                log::warn!("HMAC verification failed: {}", peer_addr);
330
331                return None;
332            }
333        }
334    } else
335    {
336        let key =
337        {
338            #[cfg(feature = "server")]
339            {
340                server::CONNECTIONS.get(&peer_addr)
341                    .and_then(|c| c.obfuscation_key().cloned())
342                    .unwrap_or_else(|| [0u8; 32])
343            }
344
345            #[cfg(feature = "client")]
346            {
347                options::get_obfuscation_key()
348            }
349        };
350
351        decoded_packet = obfuscate_data(decoded_packet, key); //NO ENCRYPTION, REMOVE OBFUSCATION
352    }
353
354    //DESERIALIZE AND RETURN
355    match wincode::deserialize::<MessagePacket>(&decoded_packet)
356    {
357        Ok(packet) =>
358        {
359            //SPAM, SEQ & LENGTH CHECKS (SERVER)
360            #[cfg(feature = "server")]
361            {
362                //LOCAL SEQ COUNTER
363                if let Some(seq) = seq
364                {
365                    if packet.seq > *seq //VALID SEQ
366                    {
367                        //SET SEQ TO CURRENT
368                        *seq = packet.seq;
369                    } else { return None; }
370                } else if let Some(mut conn) = server::CONNECTIONS.get_mut(&peer_addr)
371                {
372                    //ACTIVITY TIMER
373                    let mut spam_warning = false;
374                    let mut shared_key = None;
375                    let mut disconnect = false;
376                    let mut grace = true;
377
378                    //SPAM
379                    if packet.code != Some(MessageCode::KeepAlive) &&
380                        packet.code != Some(MessageCode::KeyExchange) &&
381                        packet.file.is_none()
382                    {
383                        //MESSAGE SIZE (ONLY FOR AUTHENTICATED)
384                        if let Some(text) = &packet.text && conn.is_authenticated()
385                        {
386                            disconnect = text.len() > config::read_config("max_message_length");
387                        }
388
389                        if !disconnect && config::read_config("spam_protection") && conn.is_authenticated() &&
390                            Instant::now().duration_since(*conn.last_activity()) <
391                            Duration::from_millis(config::read_config::<u64>("min_message_delay"))
392                        {
393                            //INCREMENT SPAM VIOLATIONS
394                            *conn.spam_violations_mut().unwrap() += 1;
395
396                            //WARN
397                            spam_warning = true;
398                            shared_key = conn.keys().cloned();
399
400                            //CHECK FOR TOO MANY VIOLATIONS
401                            disconnect = *conn.spam_violations().unwrap() > config::read_config::<usize>("max_message_delay_violations");
402                        }
403
404                        *conn.last_activity_mut() = Instant::now(); //RESET last_activity
405                    }
406
407                    //SEQ
408                    if packet.seq > *conn.seq() //VALID SEQ
409                    {
410                        //SET SEQ TO CURRENT
411                        *conn.seq_mut() = packet.seq;
412                    } else
413                    {
414                        //INVALID SEQ
415                        grace = false;
416                        disconnect = true;
417                    }
418                    drop(conn); //PREVENT DEADLOCK
419
420                    //SEND WARNING CODE
421                    if spam_warning
422                    {
423                        server::send_code(&mut streams.1.lock().unwrap(), None, MessageCode::SpamWarning, shared_key.as_ref());
424                    }
425
426                    //TOO MANY VIOLATIONS, BYE
427                    if disconnect
428                    {
429                        server::remove_connection(&peer_addr, grace, Some(if !grace { "SEQ" } else { "SPAM" }));
430                        return None;
431                    }
432                }
433            }
434
435            //VERIFY SEQUENCE NUMBER
436            #[cfg(feature = "client")] //ON CLIENT
437            {
438                let used_seq = match seq
439                {
440                    Some(ref s) => **s,
441                    None => options::get_server_seq(),
442                };
443
444                if packet.seq > used_seq || used_seq == 0 || packet.code == Some(MessageCode::Disconnect) //VALID
445                {
446                    //SET SEQ
447                    if let Some(seq) = seq
448                    {
449                        *seq = packet.seq;
450                    } else
451                    {
452                        options::set_server_seq(packet.seq);
453                    }
454                } else //INVALID, DISCONNECT
455                {
456                    return None;
457                }
458            }
459
460            return Some(packet);
461        },
462
463        Err(_) =>
464        {
465            //FORCEFULLY DISCONNECT CLIENT ON INVALID PACKET
466            #[cfg(feature = "server")]
467            server::remove_connection(&peer_addr, false, Some("packet"));
468
469            return None;
470        }
471    }
472}
473
474pub fn send_file //CHUNK FILE AND SEND TO STREAM
475(
476    path: PathBuf,
477    mut stream: TcpStream,
478    uid: u64,
479    code: MessageCode,
480    keys: Option<&chat_consts::SharedKeys>,
481)
482{
483    let mut file = File::open(path).expect("Cannot open file for upload");
484    let mut buffer = vec![0; chat_consts::UPLOAD_CHUNK_SIZE];
485
486    #[cfg(feature = "server")]
487    let mut seq = 0usize;
488
489    //LOOP READING
490    loop
491    {
492        match file.read(&mut buffer)
493        {
494            Ok(0) => break, //EOF
495            Ok(bytes) =>
496            {
497                //SEND FILE CHUNK
498                send(&mut stream, MessagePacket
499                {
500                    code: Some(code.clone()),
501                    file: Some(FilePayload
502                    {
503                        uid,
504                        data: Some(buffer[..bytes].to_vec()),
505                        ..Default::default()
506                    }),
507                    ..Default::default()
508                }, keys, #[cfg(feature = "server")] Some(&mut seq));
509            },
510            Err(_) => {}, //TODO: Implement
511        }
512    }
513}