why2-chat 2.0.1

Lightweight, fast and secure chat application powered by WHY2 encryption.
/*
This is part of WHY2
Copyright (C) 2022-2026 Václav Šmejkal

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

//MODULES
pub mod consts;

#[cfg(feature = "client_base")]
pub mod client;

#[cfg(feature = "server")]
pub mod server;

use std::
{
    io::Error,
    net::SocketAddr,
};

use tokio::net::UdpSocket;

use wincode::{ SchemaRead, SchemaWrite };

#[cfg(not(feature = "server"))]
use std::time::Duration;

use crate::
{
    crypto,
    consts::SharedKeys,
};

#[cfg(feature = "client_base")]
use crate::network::voice::client::options;

#[cfg(not(feature = "server"))]
use crate::options as chat_options;

#[derive(SchemaRead, SchemaWrite)]
pub enum VoicePacketCode
{
    //INIT PACKET - CLAIMS THE VOICE SLOT THE TCP SESSION OPENED
    Hello
    {
        token: [u8; 32], //TOKEN HANDED OUT OVER THE AUTHENTICATED TCP CHANNEL
    },

    //Hello ACCEPTED - UDP IS LOSSY, SO THE CLIENT REPEATS Hello UNTIL THIS COMES BACK
    HelloAck,

    //AUDIO TRANSMIT
    Audio
    {
        data: Vec<u8>,            //DATA
        username: Option<String>, //CLIENT USERNAME
    },

    //Pingu without you is just Ping
    Ping
    {
        timestamp: u128, //LOCAL TIMESTAMP
    },

    //Chinese cousin of Pingu
    Pong
    {
        target_id: usize, //REMOTE CLIENT ID
        timestamp: u128,  //REMOTE TIMESTAMP
    },
}

#[derive(SchemaRead, SchemaWrite)]
pub struct VoicePacket //VOICE PACKET (WHAT IS BEING SENT)
{
    pub id: usize,               //LOCAL CLIENT ID
    pub code: VoicePacketCode,   //CODE
    pub seq: usize,              //SEQUENCE NUMBER
}

pub async fn send //SEND DATA TO UDP
(
    socket: &UdpSocket,
    id: usize,
    code: VoicePacketCode,
    #[cfg(feature = "server")] addr: &SocketAddr,
    #[cfg(feature = "server")] recipient_id: &usize,
    keys: &SharedKeys
) -> Result<usize, Error>
{
    //INIT PACKET
    let mut packet = VoicePacket
    {
        id,
        code,
        seq: 0,
    };

    //SET SERVER SEQ
    #[cfg(feature = "server")]
    {
        if let Some(mut conn) = server::CONNECTIONS.get_mut(recipient_id) &&
            let Some(conn) = conn.0.as_mut()
        {
            packet.seq = conn.server_seq() + 1;
            *conn.server_seq_mut() = packet.seq;
        }
    }

    //SET SEQ
    #[cfg(feature = "client_base")]
    {
        packet.seq = options::get_seq() + 1;
        options::set_seq(packet.seq);
    }

    //SERIALIZE PACKET
    let packet_bytes = wincode::serialize(&packet).expect("Encoding packet failed");

    //ENCRYPT PACKET
    #[cfg(feature = "server")]
    let encrypted_bytes: Vec<u8>;

    #[cfg(not(feature = "server"))]
    let mut encrypted_bytes: Vec<u8>;

    encrypted_bytes = crypto::encrypt_packet::< { consts::GRID_WIDTH }, { consts::GRID_HEIGHT } >(&packet_bytes, keys);

    //PREPEND ID TO PACKET
    #[cfg(feature = "client_base")]
    {
        encrypted_bytes.splice(0..0, id.to_be_bytes());
    }

    #[cfg(feature = "server")]
    {
        socket.send_to(&encrypted_bytes, addr).await
    }

    #[cfg(not(feature = "server"))]
    {
        socket.send(&encrypted_bytes).await
    }
}

pub async fn receive(socket: &UdpSocket) -> Option<(VoicePacket, SocketAddr)> //RECEIVE UDP PACKET & DECODE
{
    let mut buffer = [0u8; 2048];
    loop //WAIT UNTIL PACKET ARRIVES
    {
        //CHECK FOR VOICE DISABLE
        #[cfg(feature = "client_base")]
        if !options::get_use_voice() { break None; }

        let (len, addr) =
        {
            #[cfg(feature = "server")]
            {
                match socket.recv_from(&mut buffer).await
                {
                    Ok(result) => result,
                    Err(_) => continue
                }
            }

            //POLL SO THE VOICE DISABLE CHECK ABOVE STAYS RESPONSIVE
            #[cfg(not(feature = "server"))]
            {
                match tokio::time::timeout(Duration::from_millis(consts::RECV_TIMEOUT), socket.recv_from(&mut buffer)).await
                {
                    Ok(Ok(result)) => result,
                    _ => continue
                }
            }
        };

        let buffer_offset: usize;

        //ID THE KEYS WERE PICKED BY (SERVER ONLY)
        #[cfg(feature = "server")]
        let sender_id: usize;

        //GET ID ON SERVER
        let keys =
        {
            #[cfg(feature = "server")]
            {
                if len <= 8 { continue; } //INVALID PACKET

                let id = match buffer[..8].try_into()
                {
                    Ok(id_be_bytes) => usize::from_be_bytes(id_be_bytes),
                    Err(_) => continue
                };

                //REMOVE ID FROM BUFFER
                buffer_offset = 8;
                sender_id = id;

                match server::find_key(&id)
                {
                    Some(k) => k,
                    None => continue
                }
            }

            #[cfg(not(feature = "server"))]
            {
                buffer_offset = 0;
                chat_options::get_keys().unwrap()
            }
        };

        //DECRYPT
        let decrypted_bytes = match crypto::decrypt_packet::<{ consts::GRID_WIDTH }, { consts::GRID_HEIGHT }>
            (buffer[buffer_offset..len].to_vec(), &keys)
        {
            Some(d) => d,
            None => continue
        };

        //PACKET ARRIVED, DESERIALIZE
        let packet = match wincode::deserialize::<VoicePacket>(&decrypted_bytes)
        {
            Ok(packet) => packet,
            Err(_) => continue
        };

        #[cfg(feature = "server")]
        if packet.id != sender_id { continue; }

        return Some((packet, addr))
    }
}