├── .gitignore ├── src ├── shared │ ├── channels │ │ ├── unreliable │ │ │ ├── mod.rs │ │ │ ├── recv.rs │ │ │ └── send.rs │ │ ├── reliable │ │ │ ├── mod.rs │ │ │ ├── recv.rs │ │ │ ├── codec.rs │ │ │ └── send.rs │ │ └── tasks.rs │ ├── certificate.rs │ ├── error.rs │ ├── peer_connection │ │ └── recv_channels.rs │ ├── channels.rs │ └── peer_connection.rs ├── lib.rs ├── server │ ├── client_id.rs │ ├── connection.rs │ ├── error.rs │ ├── certificate.rs │ └── messages.rs ├── client │ ├── connection │ │ ├── client_id.rs │ │ └── messages.rs │ └── error.rs └── shared.rs ├── assets ├── fonts │ ├── FiraSans-Bold.ttf │ ├── FiraMono-Medium.ttf │ └── FiraMono-LICENSE ├── sounds │ └── breakout_collision.ogg └── tests │ ├── key.pem.test │ └── cert.pem.test ├── examples ├── chat │ ├── protocol.rs │ ├── server.rs │ └── client.rs └── breakout │ ├── protocol.rs │ └── breakout.rs ├── LICENSE-MIT ├── Cargo.toml ├── docs └── Certificates.md ├── tests ├── connection.rs ├── channels.rs ├── utils │ └── mod.rs └── certificates.rs ├── LICENSE-APACHE ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.pem 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /src/shared/channels/unreliable/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod recv; 2 | pub(crate) mod send; 3 | -------------------------------------------------------------------------------- /assets/fonts/FiraSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Henauxg/bevy_quinnet/HEAD/assets/fonts/FiraSans-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/FiraMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Henauxg/bevy_quinnet/HEAD/assets/fonts/FiraMono-Medium.ttf -------------------------------------------------------------------------------- /assets/sounds/breakout_collision.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Henauxg/bevy_quinnet/HEAD/assets/sounds/breakout_collision.ogg -------------------------------------------------------------------------------- /assets/tests/key.pem.test: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgXCUVK397YCoNA2mk 3 | tEq+WEg9PxXWXDNoJYo0MB4/ML2hRANCAATASoVqZIx8Umd8ff67alLqRWmARDun 4 | OWvej2nby87zBCM+CWOGqeFg8t0R7qmMXsF8EY8agYGKPlbjWKsKs082 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | 3 | //! A Client/Server game networking plugin using QUIC, for the Bevy game engine. 4 | //! See the repository at 5 | 6 | /// Client features 7 | #[cfg(feature = "client")] 8 | pub mod client; 9 | /// Server features 10 | #[cfg(feature = "server")] 11 | pub mod server; 12 | /// Shared features between client & server 13 | pub mod shared; 14 | -------------------------------------------------------------------------------- /src/shared/channels/reliable/mod.rs: -------------------------------------------------------------------------------- 1 | use super::PROTOCOL_HEADER_LEN; 2 | 3 | pub(crate) mod codec; 4 | pub(crate) mod recv; 5 | pub(crate) mod send; 6 | 7 | /// Default max frame length for payloads sent on reliable channels, in bytes 8 | pub const DEFAULT_MAX_RELIABLE_FRAME_LEN: usize = 8 * 1_024 * 1_024; 9 | 10 | // PAYLOAD LENGTH | CHANNEL ID | PAYLOAD 11 | pub(crate) const RELIABLE_FRAME_LENGTH_FIELD_LEN: usize = 4; 12 | pub(crate) const RELIABLE_FRAME_TOTAL_HEADER_LEN: usize = 13 | RELIABLE_FRAME_LENGTH_FIELD_LEN + PROTOCOL_HEADER_LEN; 14 | -------------------------------------------------------------------------------- /assets/tests/cert.pem.test: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBUTCB+KADAgECAggRsbo6nHSkozAKBggqhkjOPQQDAjAhMR8wHQYDVQQDDBZy 3 | Y2dlbiBzZWxmIHNpZ25lZCBjZXJ0MCAXDTc1MDEwMTAwMDAwMFoYDzQwOTYwMTAx 4 | MDAwMDAwWjAhMR8wHQYDVQQDDBZyY2dlbiBzZWxmIHNpZ25lZCBjZXJ0MFkwEwYH 5 | KoZIzj0CAQYIKoZIzj0DAQcDQgAEwEqFamSMfFJnfH3+u2pS6kVpgEQ7pzlr3o9p 6 | 28vO8wQjPgljhqnhYPLdEe6pjF7BfBGPGoGBij5W41irCrNPNqMYMBYwFAYDVR0R 7 | BA0wC4IJMTI3LjAuMC4xMAoGCCqGSM49BAMCA0gAMEUCIHdWXNcGlifqr+yWpF8u 8 | li0+uq56A+bBdHQ1Df14nSiuAiEArc1tyMX6furJW5q+7LUdtZK03v3i4gKtQGsM 9 | XXm98DE= 10 | -----END CERTIFICATE----- 11 | -------------------------------------------------------------------------------- /examples/chat/protocol.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use bevy_quinnet::shared::ClientId; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | // Messages from clients 7 | #[derive(Debug, Clone, Serialize, Deserialize)] 8 | pub enum ClientMessage { 9 | Join { name: String }, 10 | Disconnect {}, 11 | ChatMessage { message: String }, 12 | } 13 | 14 | // Messages from the server 15 | #[derive(Debug, Clone, Serialize, Deserialize)] 16 | pub enum ServerMessage { 17 | ClientConnected { 18 | client_id: ClientId, 19 | username: String, 20 | }, 21 | ClientDisconnected { 22 | client_id: ClientId, 23 | }, 24 | ChatMessage { 25 | client_id: ClientId, 26 | message: String, 27 | }, 28 | InitClient { 29 | client_id: ClientId, 30 | usernames: HashMap, 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /src/shared/certificate.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// SHA-256 hash of the certificate data in DER form 4 | #[derive(Debug, Clone, Eq, PartialEq)] 5 | pub struct CertificateFingerprint([u8; 32]); 6 | 7 | impl CertificateFingerprint { 8 | /// Wraps a buffer into a [`CertificateFingerprint`] 9 | pub fn new(buf: [u8; 32]) -> Self { 10 | CertificateFingerprint(buf) 11 | } 12 | 13 | /// Encodes the wrapped buffer content to base64 14 | pub fn to_base64(&self) -> String { 15 | base64::encode(self.0) 16 | } 17 | } 18 | 19 | impl From<&rustls::pki_types::CertificateDer<'_>> for CertificateFingerprint { 20 | fn from(cert: &rustls::pki_types::CertificateDer<'_>) -> CertificateFingerprint { 21 | let hash = ring::digest::digest(&ring::digest::SHA256, cert); 22 | let fingerprint_bytes = hash.as_ref().try_into().unwrap(); 23 | CertificateFingerprint(fingerprint_bytes) 24 | } 25 | } 26 | 27 | impl fmt::Display for CertificateFingerprint { 28 | #[inline] 29 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 | fmt::Display::fmt(&self.to_base64(), f) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/shared/channels/unreliable/recv.rs: -------------------------------------------------------------------------------- 1 | use bevy::log::trace; 2 | use bytes::Bytes; 3 | use std::fmt::Display; 4 | use tokio::sync::mpsc::{self}; 5 | 6 | use crate::shared::channels::{ChannelId, CloseRecv, CHANNEL_ID_LEN}; 7 | 8 | pub(crate) async fn unreliable_channel_receiver_task( 9 | task_id: T, 10 | connection: quinn::Connection, 11 | mut close_recv: CloseRecv, 12 | bytes_incoming_send: mpsc::Sender<(ChannelId, Bytes)>, 13 | ) { 14 | tokio::select! { 15 | _ = close_recv.recv() => { 16 | trace!("Listener for unreliable datagrams with id {} received a close signal", task_id) 17 | } 18 | _ = async { 19 | while let Ok(mut msg_bytes) = connection.read_datagram().await { 20 | if msg_bytes.len() <= CHANNEL_ID_LEN { 21 | continue; 22 | } 23 | let payload = msg_bytes.split_off(1); 24 | let channel_id = msg_bytes[0]; 25 | // TODO Clean: error handling 26 | bytes_incoming_send.send((channel_id, payload)).await.unwrap(); 27 | } 28 | } => { 29 | trace!("Listener for unreliable datagrams with id {} ended", task_id) 30 | } 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/server/client_id.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bytes::{BufMut, BytesMut}; 3 | use futures::SinkExt; 4 | use tokio::sync::mpsc::{self}; 5 | use tokio_util::codec::{FramedWrite, LengthDelimitedCodec}; 6 | 7 | use crate::shared::{channels::ChannelAsyncMessage, ClientId, CLIENT_ID_LEN}; 8 | 9 | pub(crate) fn spawn_client_id_sender( 10 | connection_handle: quinn::Connection, 11 | client_id: ClientId, 12 | from_channels_send: mpsc::Sender, 13 | ) { 14 | tokio::spawn(async move { 15 | let (stream_send, _) = connection_handle 16 | .open_bi() 17 | .await 18 | .expect("Failed to open send stream"); 19 | let mut frame_sender = FramedWrite::new(stream_send, LengthDelimitedCodec::new()); 20 | 21 | let mut msg_bytes = BytesMut::with_capacity(CLIENT_ID_LEN); 22 | msg_bytes.put_uint(client_id, CLIENT_ID_LEN); 23 | if let Err(err) = frame_sender.send(msg_bytes.into()).await { 24 | error!( 25 | "Error while sending client Id {} on Quinnet Protocol Channel, {}", 26 | client_id, err 27 | ); 28 | from_channels_send 29 | .send(ChannelAsyncMessage::LostConnection) 30 | .await 31 | .expect("Failed to signal connection lost on Quinnet Protocol Channel"); 32 | } 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/server/connection.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::mpsc::{self, error::TrySendError}; 2 | 3 | use crate::{ 4 | server::ServerSyncMessage, 5 | shared::{peer_connection::PeerConnection, InternalConnectionRef}, 6 | }; 7 | 8 | /// A connection to a client from the server's perspective. 9 | pub type ServerSideConnection = PeerConnection; 10 | 11 | /// Specific data for a server-side connection 12 | pub struct ServerConnection { 13 | connection_handle: InternalConnectionRef, 14 | to_connection_send: mpsc::Sender, 15 | } 16 | impl ServerConnection { 17 | pub(crate) fn new( 18 | connection_handle: InternalConnectionRef, 19 | to_connection_send: mpsc::Sender, 20 | ) -> Self { 21 | Self { 22 | connection_handle, 23 | to_connection_send, 24 | } 25 | } 26 | } 27 | 28 | impl ServerSideConnection { 29 | /// See [quinn::Connection::max_datagram_size] 30 | #[inline(always)] 31 | pub fn max_datagram_size(&self) -> Option { 32 | self.specific.connection_handle.max_datagram_size() 33 | } 34 | 35 | /// Returns statistics about a client connection 36 | #[inline(always)] 37 | pub fn quinn_connection_stats(&self) -> quinn::ConnectionStats { 38 | self.specific.connection_handle.stats() 39 | } 40 | 41 | #[inline(always)] 42 | pub(crate) fn try_send_to_async_connection( 43 | &self, 44 | msg: ServerSyncMessage, 45 | ) -> Result<(), TrySendError> { 46 | self.specific.to_connection_send.try_send(msg) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/client/connection/client_id.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bytes::Buf; 3 | use futures::StreamExt; 4 | use tokio_util::codec::{FramedRead, LengthDelimitedCodec}; 5 | 6 | use crate::{ 7 | client::QuinnetConnectionError, 8 | shared::{ClientId, CLIENT_ID_LEN}, 9 | }; 10 | 11 | use super::CloseRecv; 12 | 13 | pub(crate) enum ClientIdReception { 14 | Interrupted, 15 | Retrieved(ClientId), 16 | Failed(QuinnetConnectionError), 17 | } 18 | 19 | pub(crate) async fn receive_client_id( 20 | connection_handle: quinn::Connection, 21 | mut close_recv: CloseRecv, 22 | ) -> ClientIdReception { 23 | tokio::select! { 24 | _ = close_recv.recv() => { 25 | trace!("Client id receiver received a close signal"); 26 | ClientIdReception::Interrupted 27 | } 28 | client_id_res = async { 29 | let (_, recv) = connection_handle.accept_bi().await?; 30 | let mut frame_recv = FramedRead::new(recv, LengthDelimitedCodec::new()); 31 | let mut msg_bytes = frame_recv.next().await.ok_or(QuinnetConnectionError::ClientIdNotReceived)?.map_err(|_| QuinnetConnectionError::ClientIdNotReceived)?; 32 | if msg_bytes.len() >= CLIENT_ID_LEN { 33 | Ok(msg_bytes.get_uint(CLIENT_ID_LEN)) 34 | } else { 35 | Err(QuinnetConnectionError::InvalidClientId) 36 | } 37 | } => { 38 | trace!("Client id receiver ended"); 39 | match client_id_res{ 40 | Ok(client_id) => ClientIdReception::Retrieved(client_id), 41 | Err(err) => ClientIdReception::Failed(err), 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/shared.rs: -------------------------------------------------------------------------------- 1 | use std::{mem::size_of, time::Duration}; 2 | 3 | use bevy::{ 4 | ecs::schedule::SystemSet, 5 | prelude::{Deref, DerefMut, Resource}, 6 | }; 7 | use channels::MAX_CHANNEL_COUNT; 8 | use tokio::runtime::Runtime; 9 | 10 | /// Certificate functionalities shared by client & server 11 | pub mod certificate; 12 | /// Channel functionalities shared by client & server 13 | pub mod channels; 14 | /// Shared error types 15 | pub mod error; 16 | /// Connection functionalities shared by client & server 17 | pub mod peer_connection; 18 | 19 | /// Default max size of async channels used to hold network messages. 1 async channel per connection. 20 | pub const DEFAULT_MESSAGE_QUEUE_SIZE: usize = 150; 21 | /// Default period of inactivity before sending a keep-alive packet 22 | /// 23 | /// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out. 24 | pub const DEFAULT_KEEP_ALIVE_INTERVAL_S: Duration = Duration::from_secs(4); 25 | 26 | /// Default max size for quinnet internal message channels 27 | pub const DEFAULT_INTERNAL_MESSAGES_CHANNEL_SIZE: usize = 100; 28 | 29 | /// Default max size for Quinnet Channels messages 30 | /// 31 | /// At least MAX_CHANNEL_COUNT capacity if all available channel slots are requested to open 32 | pub const DEFAULT_QCHANNEL_MESSAGES_CHANNEL_SIZE: usize = 2 * MAX_CHANNEL_COUNT; 33 | 34 | /// Default max size of the queues used to transmit close messages for async tasks 35 | pub(crate) const DEFAULT_KILL_MESSAGE_QUEUE_SIZE: usize = 10; 36 | 37 | /// Represents the id of a client on the server. 38 | pub type ClientId = u64; 39 | pub(crate) const CLIENT_ID_LEN: usize = size_of::(); 40 | 41 | /// Async runtime newtype wrapping the tokio runtime handle. used by both quinnet client and server's async back-ends. 42 | #[derive(Resource, Deref, DerefMut)] 43 | pub struct AsyncRuntime(pub(crate) Runtime); 44 | pub(crate) type InternalConnectionRef = quinn::Connection; 45 | 46 | /// System set used to update the sync client & server from updates coming from the async quinnet back-end. 47 | /// 48 | /// This is where client & server events are raised. 49 | /// 50 | /// This system set runs in [bevy::prelude::PreUpdate]. 51 | #[derive(Debug, SystemSet, Clone, Copy, PartialEq, Eq, Hash)] 52 | pub struct QuinnetSyncPreUpdate; 53 | 54 | /// System set used to perform end-of-frame clean-up tasks. 55 | /// 56 | /// May also be used to send buffered payloads at the end of a frame in the future. 57 | /// 58 | /// This system set runs in [bevy::prelude::Last]. 59 | #[derive(Debug, SystemSet, Clone, Copy, PartialEq, Eq, Hash)] 60 | pub struct QuinnetSyncLast; 61 | -------------------------------------------------------------------------------- /src/shared/channels/reliable/recv.rs: -------------------------------------------------------------------------------- 1 | use bevy::log::trace; 2 | use bytes::{Buf, Bytes, BytesMut}; 3 | use futures::StreamExt; 4 | use quinn::RecvStream; 5 | use std::{fmt::Display, io::Cursor}; 6 | use tokio::sync::mpsc::{self}; 7 | use tokio_util::codec::FramedRead; 8 | 9 | use crate::shared::channels::{ 10 | reliable::{codec::QuinnetProtocolCodecDecoder, DEFAULT_MAX_RELIABLE_FRAME_LEN}, 11 | ChannelId, CloseRecv, CHANNEL_ID_LEN, 12 | }; 13 | 14 | pub(crate) async fn reliable_channels_receiver_task( 15 | task_id: T, 16 | connection: quinn::Connection, 17 | mut close_recv: CloseRecv, 18 | bytes_incoming_send: mpsc::Sender<(ChannelId, Bytes)>, 19 | ) { 20 | let close_recv_clone = close_recv.resubscribe(); 21 | tokio::select! { 22 | _ = close_recv.recv() => { 23 | trace!("Listener for new Unidirectional Receiving Streams with id {} received a close signal", task_id) 24 | } 25 | _ = async { 26 | while let Ok(recv) = connection.accept_uni().await { 27 | let bytes_incoming_send_clone = bytes_incoming_send.clone(); 28 | let close_recv_clone = close_recv_clone.resubscribe(); 29 | tokio::spawn(async move { 30 | reliable_stream_receiver_task( 31 | recv, 32 | close_recv_clone, 33 | bytes_incoming_send_clone 34 | ).await; 35 | }); 36 | } 37 | } => { 38 | trace!("Listener for new Unidirectional Receiving Streams with id {} ended", task_id) 39 | } 40 | }; 41 | } 42 | 43 | async fn reliable_stream_receiver_task( 44 | recv: RecvStream, 45 | mut close_recv: CloseRecv, 46 | bytes_incoming_send: mpsc::Sender<(ChannelId, Bytes)>, 47 | ) { 48 | tokio::select! { 49 | _ = close_recv.recv() => {} 50 | _ = async { 51 | let mut frame_recv = FramedRead::new(recv, QuinnetProtocolCodecDecoder::new(DEFAULT_MAX_RELIABLE_FRAME_LEN)); 52 | while let Some(Ok(msg_bytes)) = frame_recv.next().await { 53 | // TODO Clean: error handling 54 | bytes_incoming_send 55 | .send(decode_incoming_reliable_message(msg_bytes)) 56 | .await 57 | .unwrap(); 58 | } 59 | } => {} 60 | }; 61 | } 62 | 63 | fn decode_incoming_reliable_message(mut msg_bytes: BytesMut) -> (ChannelId, Bytes) { 64 | let mut msg = Cursor::new(&msg_bytes); 65 | let channel_id = msg.get_u8(); 66 | let payload = msg_bytes.split_off(CHANNEL_ID_LEN).into(); 67 | (channel_id, payload) 68 | } 69 | -------------------------------------------------------------------------------- /src/client/error.rs: -------------------------------------------------------------------------------- 1 | use std::sync::PoisonError; 2 | 3 | use crate::shared::error::{AsyncChannelError, ConnectionAlreadyClosed, ConnectionSendError}; 4 | 5 | use super::connection::ConnectionLocalId; 6 | 7 | /// Error when sending data from the client 8 | #[derive(thiserror::Error, Debug)] 9 | pub enum ClientSendError { 10 | /// A connection is closed 11 | #[error("Connection is 'disconnected'")] 12 | ConnectionClosed, 13 | /// Error when sending data on the connection 14 | #[error("Error when sending data on the connection")] 15 | ConnectionSendError(#[from] ConnectionSendError), 16 | } 17 | 18 | /// Error when sending a payload from the client 19 | #[derive(thiserror::Error, Debug)] 20 | pub enum ClientPayloadSendError { 21 | /// There is no default channel 22 | #[error("There is no default channel")] 23 | NoDefaultChannel, 24 | 25 | // /// Error when sending data on the connection 26 | // #[error("Error when sending data on the connection")] 27 | // ConnectionSendError(#[from] ConnectionSendError), 28 | /// Error when sending 29 | #[error("Error when sending")] 30 | SendError(#[from] ClientSendError), 31 | } 32 | 33 | /// The client connection is closed 34 | #[derive(thiserror::Error, Debug)] 35 | #[error("The client connection is closed")] 36 | pub struct ConnectionClosed; 37 | 38 | /// Error while closing a connection 39 | #[derive(thiserror::Error, Debug)] 40 | pub enum ClientConnectionCloseError { 41 | /// A connection is already closed 42 | #[error("Connection is already closed")] 43 | ConnectionAlreadyClosed(#[from] ConnectionAlreadyClosed), 44 | /// A connection id is invalid 45 | #[error("Connection id `{0}` is invalid")] 46 | InvalidConnectionId(ConnectionLocalId), 47 | } 48 | 49 | #[derive(thiserror::Error, Debug)] 50 | /// An host file is invalid 51 | #[error("The hosts file is invalid")] 52 | pub struct InvalidHostFile; 53 | 54 | /// Error while applying a certificate action 55 | #[derive(thiserror::Error, Debug)] 56 | pub enum CertificateInteractionError { 57 | /// A Certificate action was already sent for a CertificateInteractionEvent 58 | #[error("A Certificate action was already sent for a CertificateInteractionEvent")] 59 | CertificateActionAlreadyApplied, 60 | /// A lock acquisition failed 61 | #[error("Lock acquisition failure")] 62 | LockAcquisitionFailure, 63 | /// Quinnet async channel error 64 | #[error("Quinnet async channel error")] 65 | AsyncChannelError(#[from] AsyncChannelError), 66 | } 67 | 68 | impl From> for CertificateInteractionError { 69 | fn from(_: PoisonError) -> Self { 70 | Self::LockAcquisitionFailure 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_quinnet" 3 | version = "0.19.0" 4 | description = "Bevy plugin for Client/Server multiplayer games using QUIC" 5 | repository = "https://github.com/Henauxg/bevy_quinnet" 6 | documentation = "https://docs.rs/bevy_quinnet" 7 | edition = "2021" 8 | license = "MIT OR Apache-2.0" 9 | keywords = ["gamedev", "networking", "quic", "bevy", "plugin"] 10 | categories = ["game-development", "network-programming"] 11 | readme = "README.md" 12 | exclude = ["assets/"] 13 | 14 | [dependencies] 15 | bevy = { version = "0.17.0", default-features = false, features = ["bevy_log"] } 16 | rustls = { version = "0.23", default-features = false, features = [] } 17 | rustls-pemfile = "2" 18 | rustls-platform-verifier = "0.5" 19 | ring = "0.17.7" 20 | tokio = { version = "1.36.0", features = ["sync", "rt-multi-thread", "macros"] } 21 | tokio-util = { version = "0.7.4", features = ["codec"] } 22 | rcgen = "0.13" 23 | quinn = { version = "0.11.5", default-features = true } 24 | quinn-proto = "0.11.8" 25 | futures-util = "0.3.24" 26 | futures = "0.3.24" 27 | bytes = "1.5.0" 28 | base64 = "0.13.1" 29 | thiserror = "1.0.37" 30 | 31 | # Optional 32 | bincode = { version = "2.0.1", optional = true, features = ["serde"] } 33 | serde = { version = "1.0.145", optional = true, features = ["derive"] } 34 | 35 | [features] 36 | default = ["client", "server", "recv_channels", "shared-client-id"] 37 | 38 | # Enables client features 39 | client = [] 40 | # Enables server features 41 | server = [] 42 | 43 | # Automatically dispatches received payloads to channel specific queues (for both client and server) 44 | recv_channels = [] 45 | # Server sends the client id to the client, client wait for it before being “connected” 46 | shared-client-id = [] 47 | # Adds some quick utility methods on server and client to send messages directly (serialized/deserialized with bincode) 48 | bincode-messages = ["bincode", "serde"] 49 | 50 | [dev-dependencies] 51 | bevy = { version = "0.17.0", default-features = false, features = [ 52 | "bevy_asset", 53 | "bevy_audio", 54 | "vorbis", # OGG/VORBIS audio format support 55 | "bevy_window", # Windowing layer 56 | "bevy_render", 57 | "bevy_sprite", 58 | "bevy_sprite_render", 59 | "bevy_ui", 60 | "bevy_ui_render", 61 | "bevy_state", 62 | "bevy_text", 63 | "bevy_core_pipeline", 64 | "serialize", 65 | "x11", # X11 display server support 66 | ] } 67 | rand = "0.8.5" 68 | strum = { version = "0.27.2", features = ["derive"] } 69 | 70 | [[example]] 71 | name = "breakout" 72 | path = "examples/breakout/breakout.rs" 73 | required-features = ["bincode-messages"] 74 | 75 | [[example]] 76 | name = "chat-server" 77 | path = "examples/chat/server.rs" 78 | required-features = ["bincode-messages"] 79 | 80 | [[example]] 81 | name = "chat-client" 82 | path = "examples/chat/client.rs" 83 | required-features = ["bincode-messages"] 84 | -------------------------------------------------------------------------------- /src/shared/channels/unreliable/send.rs: -------------------------------------------------------------------------------- 1 | use bevy::log::{error, trace, warn}; 2 | use bytes::{BufMut, Bytes, BytesMut}; 3 | use quinn::SendDatagramError; 4 | 5 | use crate::shared::channels::{ 6 | tasks::SendChannelTaskData, ChannelAsyncMessage, ChannelId, CloseReason, PROTOCOL_HEADER_LEN, 7 | }; 8 | 9 | pub(crate) async fn unreliable_channel_task(mut task: SendChannelTaskData) { 10 | let close_reason = tokio::select! { 11 | close_reason = task.close_recv.recv() => { 12 | trace!("Unreliable Channel task received a close signal"); 13 | match close_reason { 14 | Ok(reason) => reason, 15 | Err(_) => CloseReason::LocalOrder, 16 | } 17 | } 18 | _ = task.channel_close_recv.recv() => { 19 | trace!("Unreliable Channel task received a channel close signal"); 20 | CloseReason::LocalOrder 21 | } 22 | _ = async { 23 | while let Some(msg_bytes) = task.bytes_recv.recv().await { 24 | if let Err(err) = send_unreliable_message(&task.connection, msg_bytes, task.id) { 25 | error!("Error while sending message on Unreliable Channel, {}", err); 26 | match err { 27 | SendDatagramError::UnsupportedByPeer => (), 28 | SendDatagramError::Disabled => (), 29 | SendDatagramError::TooLarge => (), 30 | SendDatagramError::ConnectionLost(_) => { 31 | task.from_channels_send.send( 32 | ChannelAsyncMessage::LostConnection) 33 | .await 34 | .expect("Failed to signal connection lost from channels"); 35 | }, 36 | } 37 | } 38 | } 39 | } => { 40 | trace!("Unreliable Channel task ended"); 41 | CloseReason::LocalOrder 42 | } 43 | }; 44 | // No need to try to flush if we know that the peer is already closed 45 | if close_reason != CloseReason::PeerClosed { 46 | while let Ok(msg_bytes) = task.bytes_recv.try_recv() { 47 | if let Err(err) = send_unreliable_message(&task.connection, msg_bytes, task.id) { 48 | warn!( 49 | "Failed to send a remaining message on Unreliable Channel, {}", 50 | err 51 | ); 52 | } 53 | } 54 | } 55 | } 56 | 57 | fn send_unreliable_message( 58 | connection: &quinn::Connection, 59 | msg_bytes: Bytes, 60 | channel_id: ChannelId, 61 | ) -> Result<(), SendDatagramError> { 62 | let mut datagram = BytesMut::with_capacity(PROTOCOL_HEADER_LEN + msg_bytes.len()); 63 | datagram.put_u8(channel_id); 64 | datagram.extend_from_slice(&msg_bytes[..]); 65 | connection.send_datagram(datagram.into()) 66 | } 67 | -------------------------------------------------------------------------------- /src/server/error.rs: -------------------------------------------------------------------------------- 1 | use crate::shared::{ 2 | error::{AsyncChannelError, ConnectionSendError}, 3 | ClientId, 4 | }; 5 | 6 | /// Error when sending data from the server 7 | #[derive(thiserror::Error, Debug)] 8 | pub enum ServerSendError { 9 | /// A client id is unknown 10 | #[error("Client with id `{0}` is unknown")] 11 | UnknownClient(ClientId), 12 | /// Error when sending data on the connection 13 | #[error("Error when sending data on the connection")] 14 | ConnectionSendError(#[from] ConnectionSendError), 15 | } 16 | 17 | /// Error while sending a payload on the server 18 | #[derive(thiserror::Error, Debug)] 19 | pub enum ServerPayloadSendError { 20 | /// There is no default channel 21 | #[error("There is no default channel")] 22 | NoDefaultChannel, 23 | /// Error when sending data 24 | #[error("Error when sending data")] 25 | SendError(#[from] ServerSendError), 26 | } 27 | 28 | /// Error while sending data on the server to a group of clients 29 | #[derive(thiserror::Error, Debug)] 30 | #[error("Error while sending to multiple recipients")] 31 | pub struct ServerGroupSendError(pub Vec<(ClientId, ServerSendError)>); 32 | 33 | /// Error while sending a payload on the server to a group of clients 34 | #[derive(thiserror::Error, Debug)] 35 | pub enum ServerGroupPayloadSendError { 36 | /// There is no default channel 37 | #[error("There is no default channel")] 38 | NoDefaultChannel, 39 | /// Error while sending data to a group of clients 40 | #[error("Error while sending data to a group of clients")] 41 | GroupSendError(#[from] ServerGroupSendError), 42 | } 43 | 44 | /// Error while receiving data on the server 45 | #[derive(thiserror::Error, Debug)] 46 | pub enum ServerReceiveError { 47 | /// A client id is unknown 48 | #[error("Client with id `{0}` is unknown")] 49 | UnknownClient(ClientId), 50 | } 51 | 52 | /// Error while disconnecting a client on the server 53 | #[derive(thiserror::Error, Debug)] 54 | pub enum ServerDisconnectError { 55 | /// A client id is unknown 56 | #[error("Client with id `{0}` is unknown")] 57 | UnknownClient(ClientId), 58 | /// A client is already disconnected 59 | #[error("Client with id `{0}` is already disconnected")] 60 | ClientAlreadyDisconnected(ClientId), 61 | } 62 | 63 | /// Endpoint is already closed 64 | #[derive(thiserror::Error, Debug)] 65 | #[error("Endpoint is already closed")] 66 | pub struct EndpointAlreadyClosed; 67 | 68 | /// Error while starting an Endpoint 69 | #[derive(thiserror::Error, Debug)] 70 | pub enum EndpointStartError { 71 | /// A lock acquisition failed 72 | #[error("Lock acquisition failure")] 73 | LockAcquisitionFailure, 74 | /// I/O Error 75 | #[error("I/O error")] 76 | IoError(#[from] std::io::Error), 77 | /// Certificate error 78 | #[error("Certificate error")] 79 | CertificateError(#[from] EndpointCertificateError), 80 | ///Rustls protocol error 81 | #[error("Rustls protocol error")] 82 | RustlsError(#[from] rustls::Error), 83 | /// Quinnet async channel error 84 | #[error("Quinnet async channel error")] 85 | AsyncChannelError(#[from] AsyncChannelError), 86 | } 87 | 88 | /// Error while retrieving a certificate on the server 89 | #[derive(thiserror::Error, Debug)] 90 | pub enum EndpointCertificateError { 91 | /// Failed to generate a self-signed certificate 92 | #[error("Failed to generate a self-signed certificate")] 93 | CertificateGenerationFailed(#[from] rcgen::Error), 94 | /// I/O Error 95 | #[error("I/O error")] 96 | IoError(#[from] std::io::Error), 97 | } 98 | -------------------------------------------------------------------------------- /src/shared/error.rs: -------------------------------------------------------------------------------- 1 | use super::channels::ChannelId; 2 | 3 | /// Quinnet internal error in async<->sync communications 4 | #[derive(thiserror::Error, Debug)] 5 | pub enum AsyncChannelError { 6 | /// The data could not be sent on the channel because the channel is currently full and sending would require blocking 7 | #[error("The data could not be sent on the channel because the channel is currently full and sending would require blocking")] 8 | FullQueue, 9 | /// The receiving half of an internal channel was explicitly closed or has been dropped 10 | #[error( 11 | "The receiving half of the internal channel was explicitly closed or has been dropped" 12 | )] 13 | InternalChannelClosed, 14 | } 15 | 16 | /// Error while closing a channel 17 | #[derive(thiserror::Error, Debug)] 18 | pub enum ChannelCloseError { 19 | /// A channel is closed 20 | #[error("Channel is closed already")] 21 | ChannelAlreadyClosed, 22 | /// A channel id is invalid 23 | #[error("Channel with id `{0}` is invalid")] 24 | InvalidChannelId(ChannelId), 25 | } 26 | 27 | /// Errro while creating a channel 28 | #[derive(thiserror::Error, Debug)] 29 | pub enum ChannelCreationError { 30 | /// The maximum number of simultaneously opened channels has been reached 31 | #[error("The maximum number of simultaneously opened channels has been reached")] 32 | MaxChannelsCountReached, 33 | /// Quinnet async channel error 34 | #[error("Quinnet async channel error")] 35 | AsyncChannelError(#[from] AsyncChannelError), 36 | } 37 | 38 | /// Error while configuring channels 39 | #[derive(thiserror::Error, Debug)] 40 | pub enum ChannelConfigError { 41 | /// The maximum number of configured channels has been reached 42 | #[error("The maximum number of configured channels has been reached")] 43 | MaxChannelsCountReached, 44 | } 45 | 46 | /// Error when sending data from the server 47 | #[derive(thiserror::Error, Debug)] 48 | pub enum ConnectionSendError { 49 | /// A channel id is invalid 50 | #[error("Channel with id `{0}` is invalid")] 51 | InvalidChannelId(ChannelId), 52 | /// A channel is closed 53 | #[error("Channel is closed")] 54 | ChannelClosed, 55 | /// Quinnet async channel error 56 | #[error("Quinnet async channel error")] 57 | ChannelSendError(#[from] AsyncChannelError), 58 | } 59 | 60 | /// Connection is already closed 61 | #[derive(thiserror::Error, Debug)] 62 | #[error("Connection is already closed")] 63 | pub struct ConnectionAlreadyClosed; 64 | 65 | #[cfg(feature = "recv_channels")] 66 | /// Error while receiving payloads on a recv channel 67 | #[derive(thiserror::Error, Debug, Clone)] 68 | #[error("Error while receiving payload on a recv channel")] 69 | pub enum RecvChannelError { 70 | /// The receiving channel queue is full, a payload has been dropped 71 | #[error("Channel queue with id {0} is full, a payload has been dropped")] 72 | RecvChannelFull(ChannelId), 73 | /// The maximum number of opened receive channels has been reached, a payload has been dropped 74 | #[error( 75 | "The maximum number of opened receive channels has been reached, triggered by channel id {0}. A payload has been dropped" 76 | )] 77 | MaxRecvChannelCountReached(ChannelId), 78 | } 79 | 80 | #[cfg(feature = "recv_channels")] 81 | /// Event raised when there is an error while receiving data from the server 82 | #[derive(bevy::ecs::message::Message, Debug, Clone)] 83 | pub struct RecvChannelErrorEvent { 84 | /// Local id of the connection 85 | pub id: T, 86 | /// Error raised during the reception 87 | pub error: RecvChannelError, 88 | } 89 | -------------------------------------------------------------------------------- /examples/breakout/protocol.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::{Entity, Vec2, Vec3}; 2 | use bevy_quinnet::shared::{ 3 | channels::{ChannelConfig, ChannelId, SendChannelsConfiguration}, 4 | ClientId, 5 | }; 6 | use serde::{Deserialize, Serialize}; 7 | use strum::{EnumIter, IntoEnumIterator}; 8 | 9 | use crate::BrickId; 10 | 11 | #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)] 12 | pub(crate) enum PaddleInput { 13 | #[default] 14 | None, 15 | Left, 16 | Right, 17 | } 18 | 19 | // Messages from clients 20 | #[derive(Debug, Clone, Serialize, Deserialize)] 21 | pub(crate) enum ClientMessage { 22 | PaddleInput { input: PaddleInput }, 23 | } 24 | 25 | // Game setup messages from the server 26 | #[derive(Debug, Clone, Serialize, Deserialize)] 27 | pub(crate) enum ServerSetupMessage { 28 | InitClient { 29 | client_id: ClientId, 30 | }, 31 | SpawnPaddle { 32 | owner_client_id: ClientId, 33 | entity: Entity, 34 | position: Vec3, 35 | }, 36 | SpawnBall { 37 | owner_client_id: ClientId, 38 | entity: Entity, 39 | position: Vec3, 40 | direction: Vec2, 41 | }, 42 | SpawnBricks { 43 | offset: Vec2, 44 | rows: usize, 45 | columns: usize, 46 | }, 47 | StartGame, 48 | } 49 | 50 | // Game events from the server 51 | #[derive(Debug, Clone, Serialize, Deserialize)] 52 | pub(crate) enum ServerEvent { 53 | BrickDestroyed { 54 | by_client_id: ClientId, 55 | brick_id: BrickId, 56 | }, 57 | BallCollided { 58 | owner_client_id: ClientId, 59 | entity: Entity, 60 | position: Vec3, 61 | velocity: Vec2, 62 | }, 63 | } 64 | 65 | // Game updates from the server 66 | #[derive(Debug, Clone, Serialize, Deserialize)] 67 | pub(crate) enum ServerUpdate { 68 | PaddleMoved { entity: Entity, position: Vec3 }, 69 | } 70 | 71 | #[derive(Debug, Clone, Copy, EnumIter)] 72 | #[repr(u8)] 73 | pub enum ClientChannel { 74 | PaddleCommands, 75 | } 76 | impl Into for ClientChannel { 77 | fn into(self) -> ChannelId { 78 | self as ChannelId 79 | } 80 | } 81 | impl ClientChannel { 82 | pub fn to_channel_config(self) -> ChannelConfig { 83 | match self { 84 | ClientChannel::PaddleCommands => ChannelConfig::default_ordered_reliable(), 85 | } 86 | } 87 | pub fn channels_configuration() -> SendChannelsConfiguration { 88 | SendChannelsConfiguration::from_configs( 89 | ClientChannel::iter() 90 | .map(ClientChannel::to_channel_config) 91 | .collect(), 92 | ) 93 | .unwrap() 94 | } 95 | } 96 | 97 | #[derive(Debug, Clone, Copy, EnumIter)] 98 | #[repr(u8)] 99 | pub enum ServerChannel { 100 | GameSetup, 101 | GameEvents, 102 | PaddleUpdates, 103 | } 104 | 105 | impl Into for ServerChannel { 106 | fn into(self) -> ChannelId { 107 | self as ChannelId 108 | } 109 | } 110 | 111 | impl ServerChannel { 112 | pub fn to_channel_config(self) -> ChannelConfig { 113 | match self { 114 | ServerChannel::GameSetup => ChannelConfig::default_ordered_reliable(), 115 | ServerChannel::GameEvents => ChannelConfig::default_ordered_reliable(), 116 | ServerChannel::PaddleUpdates => ChannelConfig::default_unreliable(), 117 | } 118 | } 119 | 120 | pub fn channels_configuration() -> SendChannelsConfiguration { 121 | SendChannelsConfiguration::from_configs( 122 | ServerChannel::iter() 123 | .map(ServerChannel::to_channel_config) 124 | .collect(), 125 | ) 126 | .unwrap() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /assets/fonts/FiraMono-LICENSE: -------------------------------------------------------------------------------- 1 | Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /docs/Certificates.md: -------------------------------------------------------------------------------- 1 | # Certificates and server authentication 2 | 3 | ## Trust on first use 4 | 5 | ### Default configuration 6 | 7 | Use the default configuration like this: 8 | ```rust 9 | client.open_connection(/*...*/, CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig { 10 | ..Default::default() 11 | }), 12 | ); 13 | ``` 14 | 15 | With the default configuration, known hosts and their fingerprints are stored in a file, which defaults to `quinnet/known_hosts`. 16 | 17 | The defaults verifier behaviours are: 18 | - For an `unknown` certificate (first time this server is encountered) => the client trusts this certificate, stores its fingerprint and continue the connection; 19 | - For a `trusted` certificate (the fingerprint matches the one stored for this server) => the client trusts this certificate and continue the connection; 20 | - For an `untrusted` certificate (the certificate's fingerprint does not match the one in the store) => the client raises an event to the Bevy app and waits for an action to apply. 21 | 22 | ### Examples configurations 23 | 24 | Default verifier behaviours with a custom store file: 25 | ```rust 26 | client.open_connection(/*...*/, CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig { 27 | known_hosts: KnownHosts::HostsFile("MyCustomFile".to_string()), 28 | ..Default::default() 29 | }), 30 | ); 31 | ``` 32 | 33 | Custom verifier behaviours with a custom store: 34 | ```rust 35 | client.open_connection(/*...*/, CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig { 36 | known_hosts: KnownHosts::Store(my_cert_store), 37 | verifier_behaviour: HashMap::from([ 38 | ( 39 | CertVerificationStatus::UnknownCertificate, 40 | CertVerifierBehaviour::ImmediateAction(CertVerifierAction::TrustAndStore), 41 | ), 42 | ( 43 | CertVerificationStatus::UntrustedCertificate, 44 | CertVerifierBehaviour::ImmediateAction(CertVerifierAction::AbortConnection), 45 | ), 46 | ( 47 | CertVerificationStatus::TrustedCertificate, 48 | CertVerifierBehaviour::ImmediateAction(CertVerifierAction::TrustOnce), 49 | ), 50 | ]), 51 | }), 52 | ); 53 | ``` 54 | 55 | ### Events 56 | 57 | The Quinnet client plugin raises Bevy events during the connection process (during the certificate verification). 58 | 59 | - `CertInteractionEvent`: a user action is requested before continuing. This event is only raised when the verifier behaviour for a specific certificate status is set to `CertVerifierBehaviour::RequestClientAction` 60 | - `CertTrustUpdateEvent`: the client plugin encoutered a new trust entry to register. If the store is a file, the client plugin has already updated it. If the store is a custom hashmap given to the client plugin (via `KnownHosts::Store(my_cert_store)`), it is up to the user to update its store accordingly. 61 | - `CertConnectionAbortEvent`: signals that the connection was aborted during the certificate verification (through the `CertVerifierAction::AbortConnection`). 62 | 63 | Here is a simple example for a custom handler of `CertInteractionEvent`: 64 | 65 | ```rust 66 | fn handle_cert_events(mut cert_action_events: EventReader) { 67 | // We may receive a CertInteractionEvent during the connection 68 | for cert_event in cert_action_events.iter() { 69 | match cert_event.status { 70 | // We want to abort the connection if the certificate is untrusted, else we continue 71 | CertVerificationStatus::UntrustedCertificate => cert_event 72 | .apply_cert_verifier_action(CertVerifierAction::AbortConnection) 73 | .unwrap(), 74 | _ => cert_event 75 | .apply_cert_verifier_action(CertVerifierAction::TrustOnce) 76 | .unwrap(), 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | ### Fingerprints 83 | 84 | Fingerprints in Quinnet are a SHA-256 hash of the certificate data in DER form. 85 | 86 | ### Known hosts file format 87 | 88 | This hosts file format is really simplistic for now. 89 | There is one line per entry, and each entry is a server name (as dns or ip) followed by a space, followed by the currently known fingerprint encoded in base64. 90 | 91 | Example: 92 | ``` 93 | ::1 o1cpTe602uTq4pVwT+km8QtEPQE/xCAgk+3AicW/i9g= 94 | 1234::1234 kzXIwhvMSbWCQOimT3btnFlmc/Lq0UN0JhSeQadaGbg= 95 | ``` 96 | 97 | #### Limitations 98 | 99 | This simple format implies that if two servers are hosted on the same machine on two different ports, they should currently share the same certificate to avoid any conflict. 100 | -------------------------------------------------------------------------------- /src/shared/peer_connection/recv_channels.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use bytes::Bytes; 4 | 5 | use crate::shared::{ 6 | channels::{ChannelId, MAX_CHANNEL_COUNT}, 7 | error::RecvChannelError, 8 | peer_connection::PeerConnection, 9 | }; 10 | 11 | /// Default value for the `max_buffered_payloads_count_per_channel` field of a [`RecvChannelsConfiguration`] 12 | pub const DEFAULT_MAX_BUFFERED_PAYLOADS_COUNT_PER_CHANNEL: usize = 512; 13 | /// Default value for the `max_receive_channels_count` field of a [`RecvChannelsConfiguration`] 14 | pub const DEFAULT_MAX_RECEIVE_CHANNEL_COUNT: usize = MAX_CHANNEL_COUNT; 15 | /// Default value for the `clear_stale_payloads` fields 16 | pub const DEFAULT_CLEAR_STALE_RECEIVED_PAYLOADS: bool = false; 17 | 18 | /// Configuration for a [PeerConnection]. 19 | /// 20 | /// See [crate::server::connection::ServerSideConnection] and [crate::client::connection::ClientSideConnection]. 21 | #[derive(Debug, Clone)] 22 | pub struct RecvChannelsConfiguration { 23 | /// Maximum number of payloads that can be buffered per receive channel. 24 | pub max_buffered_payloads_count_per_channel: usize, 25 | /// Maximum number of receive channels that can be opened on this connection. 26 | pub max_receive_channels_count: usize, 27 | /// If `true`, payloads on receive channels that were not read during this update will be cleared at the end of an Update cycle, in the [crate::shared::QuinnetSyncLast] schedule. 28 | /// 29 | /// Defaults to [DEFAULT_CLEAR_STALE_RECEIVED_PAYLOADS]. 30 | pub clear_stale_received_payloads: bool, 31 | } 32 | impl Default for RecvChannelsConfiguration { 33 | fn default() -> Self { 34 | Self { 35 | max_buffered_payloads_count_per_channel: 36 | DEFAULT_MAX_BUFFERED_PAYLOADS_COUNT_PER_CHANNEL, 37 | max_receive_channels_count: DEFAULT_MAX_RECEIVE_CHANNEL_COUNT, 38 | clear_stale_received_payloads: DEFAULT_CLEAR_STALE_RECEIVED_PAYLOADS, 39 | } 40 | } 41 | } 42 | 43 | impl PeerConnection { 44 | /// Enables or disables [`RecvChannelsConfiguration::clear_stale_received_payloads`] on this connection. 45 | #[inline(always)] 46 | pub fn set_clear_stale_received_payloads(&mut self, enable: bool) { 47 | self.recv_channels_cfg.clear_stale_received_payloads = enable; 48 | } 49 | 50 | /// Buffers of received payloads per receive channel 51 | pub(crate) fn internal_receive_payload(&mut self, channel_id: ChannelId) -> Option { 52 | match self.recv_channels_payloads.get_mut(channel_id as usize) { 53 | Some(payloads) => payloads.pop_front(), 54 | None => None, 55 | } 56 | } 57 | 58 | pub(crate) fn clear_stale_received_payloads(&mut self) { 59 | if self.recv_channels_cfg.clear_stale_received_payloads { 60 | self.clear_received_payloads(); 61 | } 62 | } 63 | 64 | /// Clears all the received payloads buffers for this connection. 65 | pub fn clear_received_payloads(&mut self) { 66 | for payloads in self.recv_channels_payloads.iter_mut() { 67 | payloads.clear(); 68 | } 69 | } 70 | 71 | pub(crate) fn dispatch_received_payloads_to_channel_buffers( 72 | &mut self, 73 | ) -> Result<(), Vec> { 74 | // Note on handling of TryRecvError::Disconnected 75 | // This error means that the receiving end of the channel is closed, which only happens when the client connection is closed/closing. 76 | // In this case we decide to consider that there is no more messages to receive. 77 | let mut errs = Vec::new(); 78 | while let Ok((channel_id, payload)) = self.dequeue_undispatched_bytes_from_peer() { 79 | match self.recv_channels_payloads.get_mut(channel_id as usize) { 80 | Some(payloads) => { 81 | if payloads.len() 82 | < self 83 | .recv_channels_cfg 84 | .max_buffered_payloads_count_per_channel 85 | { 86 | payloads.push_back(payload); 87 | } else { 88 | errs.push(RecvChannelError::RecvChannelFull(channel_id)); 89 | } 90 | } 91 | None => { 92 | if (channel_id as usize) < self.recv_channels_cfg.max_receive_channels_count { 93 | self.recv_channels_payloads.extend( 94 | (self.recv_channels_payloads.len()..channel_id as usize) 95 | .map(|_| VecDeque::new()), 96 | ); 97 | self.recv_channels_payloads.push(VecDeque::from([payload])); 98 | } else { 99 | errs.push(RecvChannelError::MaxRecvChannelCountReached(channel_id)); 100 | } 101 | } 102 | } 103 | } 104 | match errs.is_empty() { 105 | true => Ok(()), 106 | false => Err(errs), 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/client/connection/messages.rs: -------------------------------------------------------------------------------- 1 | use bevy::log::error; 2 | 3 | use crate::{ 4 | client::{connection::ClientSideConnection, ClientSendError, ConnectionClosed}, 5 | shared::channels::ChannelId, 6 | }; 7 | 8 | /// Error when sending a message to be serialized from the client 9 | #[derive(thiserror::Error, Debug)] 10 | pub enum ClientMessageSendError { 11 | /// Failed serialization 12 | #[error("Failed serialization")] 13 | Serialization, 14 | /// There is no default channel 15 | #[error("There is no default channel")] 16 | NoDefaultChannel, 17 | /// Error when sending data 18 | #[error("Error when sending data")] 19 | SendError(#[from] ClientSendError), 20 | } 21 | 22 | /// Error while receiving a message on the client 23 | #[derive(thiserror::Error, Debug)] 24 | pub enum ClientMessageReceiveError { 25 | /// Failed deserialization 26 | #[error("Failed deserialization")] 27 | Deserialization, 28 | /// There is no default channel 29 | #[error("There is no default channel")] 30 | NoDefaultChannel, 31 | /// Error while receiving data, client connection is closed 32 | #[error("Error while receiving data")] 33 | ConnectionClosed(#[from] ConnectionClosed), 34 | } 35 | 36 | impl ClientSideConnection { 37 | /// Same as [Self::receive_message_on] but on the default channel 38 | pub fn receive_message( 39 | &mut self, 40 | ) -> Result, ClientMessageReceiveError> { 41 | match self.default_channel() { 42 | Some(channel) => self.receive_message_on(channel), 43 | None => Err(ClientMessageReceiveError::NoDefaultChannel), 44 | } 45 | } 46 | 47 | /// Same as [Self::receive_message] but will log the error instead of returning it 48 | pub fn try_receive_message(&mut self) -> Option { 49 | match self.receive_message() { 50 | Ok(message) => message, 51 | Err(err) => { 52 | error!("try_receive_message: {}", err); 53 | None 54 | } 55 | } 56 | } 57 | 58 | /// Attempt to deserialise a message into type `T`. 59 | /// 60 | /// Will return an [`Err`] if: 61 | /// - the bytes accumulated from the server aren't deserializable to T 62 | /// - or if the client is disconnected 63 | /// - (or if the message queue is full) 64 | pub fn receive_message_on>( 65 | &mut self, 66 | channel_id: C, 67 | ) -> Result, ClientMessageReceiveError> { 68 | match self.receive_payload(channel_id)? { 69 | Some(payload) => { 70 | match bincode::serde::decode_from_slice(&payload, bincode::config::standard()) { 71 | Ok((msg, _size)) => Ok(Some(msg)), 72 | Err(_) => Err(ClientMessageReceiveError::Deserialization), 73 | } 74 | } 75 | None => Ok(None), 76 | } 77 | } 78 | 79 | /// Same as [Self::receive_message] but will log the error instead of returning it 80 | pub fn try_receive_message_on>( 81 | &mut self, 82 | channel_id: C, 83 | ) -> Option { 84 | match self.receive_message_on(channel_id) { 85 | Ok(message) => message, 86 | Err(err) => { 87 | error!("try_receive_message: {}", err); 88 | None 89 | } 90 | } 91 | } 92 | 93 | /// Queues a message to be sent to the server on the specified channel 94 | /// 95 | /// Will return an [`Err`] if: 96 | /// - the specified channel does not exist/is closed 97 | /// - or if the client is disconnected 98 | /// - or if a serialization error occurs 99 | /// - (or if the message queue is full) 100 | pub fn send_message_on>( 101 | &mut self, 102 | channel_id: C, 103 | message: T, 104 | ) -> Result<(), ClientMessageSendError> { 105 | match bincode::serde::encode_to_vec(&message, bincode::config::standard()) { 106 | Ok(payload) => Ok(self.send_payload_on(channel_id, payload)?), 107 | Err(_) => Err(ClientMessageSendError::Serialization), 108 | } 109 | } 110 | 111 | /// Same as [Self::send_message_on] but on the default channel 112 | pub fn send_message( 113 | &mut self, 114 | message: T, 115 | ) -> Result<(), ClientMessageSendError> { 116 | match self.default_channel() { 117 | Some(channel) => self.send_message_on(channel, message), 118 | None => Err(ClientMessageSendError::NoDefaultChannel), 119 | } 120 | } 121 | 122 | /// Same as [Self::send_message] but will log the error instead of returning it 123 | pub fn try_send_message(&mut self, message: T) { 124 | if let Err(err) = self.send_message(message) { 125 | error!("try_send_message: {}", err); 126 | } 127 | } 128 | 129 | /// Same as [Self::send_message_on] but will log the error instead of returning it 130 | pub fn try_send_message_on>( 131 | &mut self, 132 | channel_id: C, 133 | message: T, 134 | ) { 135 | if let Err(err) = self.send_message_on(channel_id, message) { 136 | error!("try_send_message_on: {}", err); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/shared/channels/reliable/codec.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | io::{self, Cursor}, 4 | }; 5 | 6 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 7 | use tokio_util::codec::{Decoder, Encoder}; 8 | 9 | use crate::shared::channels::PROTOCOL_HEADER_LEN; 10 | 11 | use super::{RELIABLE_FRAME_LENGTH_FIELD_LEN, RELIABLE_FRAME_TOTAL_HEADER_LEN}; 12 | 13 | #[derive(Debug, Clone, Copy)] 14 | enum DecodeState { 15 | Head, 16 | Data(usize), 17 | } 18 | 19 | /// An error when the number of bytes read is more than max frame length. 20 | pub struct QuinnetProtocolCodecError { 21 | _priv: (), 22 | } 23 | 24 | impl fmt::Debug for QuinnetProtocolCodecError { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | f.debug_struct("QuinnetProtocolCodecError").finish() 27 | } 28 | } 29 | 30 | impl fmt::Display for QuinnetProtocolCodecError { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | f.write_str("frame size too big") 33 | } 34 | } 35 | 36 | impl std::error::Error for QuinnetProtocolCodecError {} 37 | 38 | #[derive(Debug)] 39 | pub struct QuinnetProtocolCodecEncoder { 40 | max_frame_len: usize, 41 | raw_channel_id: u8, 42 | } 43 | 44 | impl QuinnetProtocolCodecEncoder { 45 | pub fn new(raw_channel_id: u8, max_frame_len: usize) -> Self { 46 | Self { 47 | raw_channel_id, 48 | max_frame_len, 49 | } 50 | } 51 | } 52 | 53 | impl Encoder for QuinnetProtocolCodecEncoder { 54 | type Error = io::Error; 55 | 56 | fn encode(&mut self, frame: Bytes, dst: &mut BytesMut) -> Result<(), io::Error> { 57 | if frame.len() > self.max_frame_len { 58 | return Err(io::Error::new( 59 | io::ErrorKind::InvalidInput, 60 | QuinnetProtocolCodecError { _priv: () }, 61 | )); 62 | } 63 | 64 | // Reserve capacity in the destination buffer to fit the frame and header fields. 65 | dst.reserve(RELIABLE_FRAME_TOTAL_HEADER_LEN + frame.len()); 66 | // Header 67 | dst.put_uint( 68 | PROTOCOL_HEADER_LEN as u64 + frame.len() as u64, 69 | RELIABLE_FRAME_LENGTH_FIELD_LEN, 70 | ); 71 | dst.put_u8(self.raw_channel_id); 72 | 73 | // Write the frame to the buffer 74 | dst.extend_from_slice(&frame[..]); 75 | 76 | Ok(()) 77 | } 78 | } 79 | 80 | #[derive(Debug)] 81 | pub struct QuinnetProtocolCodecDecoder { 82 | // Read state 83 | state: DecodeState, 84 | // Maximum frame length 85 | max_frame_len: usize, 86 | } 87 | 88 | impl QuinnetProtocolCodecDecoder { 89 | pub fn new(max_frame_len: usize) -> Self { 90 | Self { 91 | max_frame_len, 92 | state: DecodeState::Head, 93 | } 94 | } 95 | 96 | fn decode_head(&mut self, src: &mut BytesMut) -> io::Result> { 97 | if src.len() < RELIABLE_FRAME_TOTAL_HEADER_LEN { 98 | // Not enough data 99 | return Ok(None); 100 | } 101 | 102 | let payload_length = { 103 | let mut src = Cursor::new(&mut *src); 104 | 105 | let payload_length = src.get_uint(RELIABLE_FRAME_LENGTH_FIELD_LEN); 106 | 107 | if payload_length > self.max_frame_len as u64 { 108 | return Err(io::Error::new( 109 | io::ErrorKind::InvalidData, 110 | QuinnetProtocolCodecError { _priv: () }, 111 | )); 112 | } 113 | 114 | // The check above ensures there is no overflow 115 | payload_length as usize 116 | }; 117 | 118 | // src.advance(self.builder.get_num_skip()); 119 | src.advance(RELIABLE_FRAME_LENGTH_FIELD_LEN); 120 | 121 | // Ensure that the buffer has enough space to read the incoming 122 | // payload 123 | src.reserve(payload_length.saturating_sub(src.len())); 124 | 125 | Ok(Some(payload_length)) 126 | } 127 | 128 | fn decode_data(&self, payload_length: usize, src: &mut BytesMut) -> Option { 129 | // At this point, the buffer has already had the required capacity 130 | // reserved. All there is to do is read. 131 | if src.len() < payload_length { 132 | return None; 133 | } 134 | 135 | Some(src.split_to(payload_length)) 136 | } 137 | } 138 | 139 | impl Decoder for QuinnetProtocolCodecDecoder { 140 | type Item = BytesMut; 141 | type Error = io::Error; 142 | 143 | fn decode(&mut self, src: &mut BytesMut) -> io::Result> { 144 | let payload_length = match self.state { 145 | DecodeState::Head => match self.decode_head(src)? { 146 | Some(payload_length) => { 147 | self.state = DecodeState::Data(payload_length); 148 | payload_length 149 | } 150 | None => return Ok(None), 151 | }, 152 | DecodeState::Data(payload_length) => payload_length, 153 | }; 154 | 155 | match self.decode_data(payload_length, src) { 156 | Some(data) => { 157 | // Update the decode state 158 | self.state = DecodeState::Head; 159 | 160 | // Make sure the buffer has enough space to read the next head 161 | src.reserve(RELIABLE_FRAME_TOTAL_HEADER_LEN.saturating_sub(src.len())); 162 | 163 | Ok(Some(data)) 164 | } 165 | None => Ok(None), 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /examples/chat/server.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use bevy::{app::ScheduleRunnerPlugin, log::LogPlugin, prelude::*}; 4 | use bevy_quinnet::{ 5 | server::{ 6 | certificate::CertificateRetrievalMode, endpoint::Endpoint, ConnectionLostEvent, 7 | EndpointAddrConfiguration, QuinnetServer, QuinnetServerPlugin, ServerEndpointConfiguration, 8 | }, 9 | shared::ClientId, 10 | }; 11 | 12 | use protocol::{ClientMessage, ServerMessage}; 13 | 14 | mod protocol; 15 | 16 | #[derive(Resource, Debug, Clone, Default)] 17 | struct Users { 18 | names: HashMap, 19 | } 20 | 21 | fn handle_client_messages(mut server: ResMut, mut users: ResMut) { 22 | let endpoint = server.endpoint_mut(); 23 | for client_id in endpoint.clients() { 24 | while let Some(message) = endpoint.try_receive_message(client_id) { 25 | match message { 26 | ClientMessage::Join { name } => { 27 | if users.names.contains_key(&client_id) { 28 | warn!( 29 | "Received a Join from an already connected client: {}", 30 | client_id 31 | ) 32 | } else { 33 | info!("{} connected", name); 34 | users.names.insert(client_id, name.clone()); 35 | // Initialize this client with existing state 36 | endpoint 37 | .send_message( 38 | client_id, 39 | ServerMessage::InitClient { 40 | client_id: client_id, 41 | usernames: users.names.clone(), 42 | }, 43 | ) 44 | .unwrap(); 45 | // Broadcast the connection event 46 | endpoint 47 | .send_group_message( 48 | users.names.keys(), 49 | ServerMessage::ClientConnected { 50 | client_id: client_id, 51 | username: name, 52 | }, 53 | ) 54 | .unwrap(); 55 | } 56 | } 57 | ClientMessage::Disconnect {} => { 58 | // Tell the server endpoint to disconnect this user 59 | endpoint.disconnect_client(client_id).unwrap(); 60 | handle_disconnect(endpoint, &mut users, client_id); 61 | } 62 | ClientMessage::ChatMessage { message } => { 63 | info!( 64 | "Chat message | {:?}: {}", 65 | users.names.get(&client_id), 66 | message 67 | ); 68 | endpoint.try_send_group_message( 69 | users.names.keys(), 70 | ServerMessage::ChatMessage { 71 | client_id: client_id, 72 | message: message, 73 | }, 74 | ); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | fn handle_server_events( 82 | mut connection_lost_events: MessageReader, 83 | mut server: ResMut, 84 | mut users: ResMut, 85 | ) { 86 | // The server signals us about users that lost connection 87 | for client in connection_lost_events.read() { 88 | handle_disconnect(server.endpoint_mut(), &mut users, client.id); 89 | } 90 | } 91 | 92 | /// Shared disconnection behaviour, whether the client lost connection or asked to disconnect 93 | fn handle_disconnect(endpoint: &mut Endpoint, users: &mut ResMut, client_id: ClientId) { 94 | // Remove this user 95 | if let Some(username) = users.names.remove(&client_id) { 96 | // Broadcast its deconnection 97 | 98 | endpoint 99 | .send_group_message( 100 | users.names.keys(), 101 | ServerMessage::ClientDisconnected { 102 | client_id: client_id, 103 | }, 104 | ) 105 | .unwrap(); 106 | info!("{} disconnected", username); 107 | } else { 108 | warn!( 109 | "Received a Disconnect from an unknown or disconnected client: {}", 110 | client_id 111 | ) 112 | } 113 | } 114 | 115 | fn start_listening(mut server: ResMut) { 116 | server 117 | .start_endpoint(ServerEndpointConfiguration { 118 | addr_config: EndpointAddrConfiguration::from_string("[::]:6000").unwrap(), 119 | cert_mode: CertificateRetrievalMode::GenerateSelfSigned { 120 | server_hostname: "::1".to_owned(), 121 | }, 122 | defaultables: Default::default(), 123 | }) 124 | .unwrap(); 125 | } 126 | 127 | fn main() { 128 | App::new() 129 | .add_plugins(( 130 | ScheduleRunnerPlugin::default(), 131 | LogPlugin::default(), 132 | QuinnetServerPlugin::default(), 133 | )) 134 | .insert_resource(Users::default()) 135 | .add_systems(Startup, start_listening) 136 | .add_systems(Update, (handle_client_messages, handle_server_events)) 137 | .run(); 138 | } 139 | -------------------------------------------------------------------------------- /tests/connection.rs: -------------------------------------------------------------------------------- 1 | use std::{thread::sleep, time::Duration}; 2 | 3 | use bevy_quinnet::{client::QuinnetClient, server::QuinnetServer}; 4 | 5 | // https://github.com/rust-lang/rust/issues/46379 6 | pub use utils::*; 7 | 8 | mod utils; 9 | 10 | /////////////////////////////////////////////////////////// 11 | /// /// 12 | /// Test /// 13 | /// /// 14 | /////////////////////////////////////////////////////////// 15 | 16 | #[test] 17 | fn connection_with_two_apps() { 18 | let port = 6000; // TODO Use port 0 and retrieve the port used by the server. 19 | 20 | let mut client_app = start_simple_client_app(port); 21 | let mut server_app = start_simple_server_app(port); 22 | 23 | assert!( 24 | client_app 25 | .world() 26 | .resource::() 27 | .get_connection() 28 | .is_some(), 29 | "The default connection should exist" 30 | ); 31 | let server = server_app.world().resource::(); 32 | assert!(server.is_listening(), "The server should be listening"); 33 | 34 | let client_id = wait_for_client_connected(&mut client_app, &mut server_app); 35 | 36 | assert_eq!( 37 | server_app 38 | .world() 39 | .resource::() 40 | .connection_events_received, 41 | 1 42 | ); 43 | 44 | assert!( 45 | client_app 46 | .world() 47 | .resource::() 48 | .is_connected(), 49 | "The default connection should be connected to the server" 50 | ); 51 | assert_eq!( 52 | client_app 53 | .world() 54 | .resource::() 55 | .connection_events_received, 56 | 1 57 | ); 58 | 59 | client_app 60 | .world_mut() 61 | .resource_mut::() 62 | .connection_mut() 63 | .send_payload(TEST_MESSAGE_PAYLOAD) 64 | .unwrap(); 65 | 66 | // Client->Server Message 67 | sleep(Duration::from_secs_f32(0.1)); 68 | server_app.update(); 69 | 70 | let default_server_channel_id = server_app 71 | .world_mut() 72 | .resource_mut::() 73 | .endpoint_mut() 74 | .default_channel() 75 | .unwrap(); 76 | let client_payload = server_app 77 | .world_mut() 78 | .resource_mut::() 79 | .endpoint_mut() 80 | .receive_payload(client_id, default_server_channel_id) 81 | .expect("Failed to receive client message"); 82 | assert_eq!( 83 | client_payload.unwrap().iter().as_slice(), 84 | TEST_MESSAGE_PAYLOAD 85 | ); 86 | 87 | server_app 88 | .world_mut() 89 | .resource_mut::() 90 | .endpoint_mut() 91 | .broadcast_payload(TEST_MESSAGE_PAYLOAD) 92 | .unwrap(); 93 | 94 | // Server->Client Message 95 | sleep(Duration::from_secs_f32(0.1)); 96 | client_app.update(); 97 | 98 | let server_message = client_app 99 | .world_mut() 100 | .resource_mut::() 101 | .connection_mut() 102 | .receive_payload(default_server_channel_id) 103 | .expect("Failed to receive server message"); 104 | assert_eq!( 105 | server_message.unwrap().iter().as_slice(), 106 | TEST_MESSAGE_PAYLOAD 107 | ); 108 | } 109 | 110 | /////////////////////////////////////////////////////////// 111 | /// /// 112 | /// Test /// 113 | /// /// 114 | /////////////////////////////////////////////////////////// 115 | 116 | #[test] 117 | fn reconnection() { 118 | let port = 6005; // TODO Use port 0 and retrieve the port used by the server. 119 | 120 | let mut client_app = start_simple_client_app(port); 121 | let mut server_app = start_simple_server_app(port); 122 | 123 | let client_id_1 = wait_for_client_connected(&mut client_app, &mut server_app); 124 | 125 | assert_eq!( 126 | server_app 127 | .world() 128 | .resource::() 129 | .connection_events_received, 130 | 1 131 | ); 132 | 133 | assert_eq!( 134 | client_app 135 | .world() 136 | .resource::() 137 | .connection_events_received, 138 | 1 139 | ); 140 | 141 | client_app 142 | .world_mut() 143 | .resource_mut::() 144 | .connection_mut() 145 | .disconnect() 146 | .unwrap(); 147 | 148 | let last_disconnected_client_id = wait_for_all_clients_disconnected(&mut server_app); 149 | assert_eq!(last_disconnected_client_id, client_id_1); 150 | 151 | client_app 152 | .world_mut() 153 | .resource_mut::() 154 | .connection_mut() 155 | .reconnect() 156 | .unwrap(); 157 | 158 | let client_id_2 = wait_for_client_connected(&mut client_app, &mut server_app); 159 | 160 | assert_ne!( 161 | client_id_1, client_id_2, 162 | "The two connections should be assigned a different client id" 163 | ); 164 | 165 | assert_eq!( 166 | server_app 167 | .world() 168 | .resource::() 169 | .connection_events_received, 170 | 2 171 | ); 172 | 173 | assert_eq!( 174 | client_app 175 | .world() 176 | .resource::() 177 | .connection_events_received, 178 | 2 179 | ); 180 | } 181 | -------------------------------------------------------------------------------- /src/shared/channels/tasks.rs: -------------------------------------------------------------------------------- 1 | use bevy::log::trace; 2 | use bytes::Bytes; 3 | use quinn::VarInt; 4 | use tokio::sync::{ 5 | broadcast, 6 | mpsc::{self}, 7 | }; 8 | 9 | use crate::shared::channels::{ 10 | reliable::{ 11 | recv::reliable_channels_receiver_task, 12 | send::{ordered_reliable_channel_task, unordered_reliable_channel_task}, 13 | }, 14 | unreliable::{recv::unreliable_channel_receiver_task, send::unreliable_channel_task}, 15 | ChannelAsyncMessage, ChannelConfig, ChannelId, ChannelSyncMessage, CloseReason, CloseRecv, 16 | }; 17 | 18 | /// Spawn a task to handle send channels creation for this connection 19 | pub(crate) fn spawn_send_channels_tasks_spawner( 20 | connection_handle: quinn::Connection, 21 | close_recv: broadcast::Receiver, 22 | to_channels_recv: mpsc::Receiver, 23 | from_channels_send: mpsc::Sender, 24 | ) { 25 | tokio::spawn(async move { 26 | send_channels_tasks_spawner( 27 | connection_handle, 28 | close_recv, 29 | to_channels_recv, 30 | from_channels_send, 31 | ) 32 | .await 33 | }); 34 | } 35 | 36 | pub(crate) struct SendChannelTaskData { 37 | pub(crate) connection: quinn::Connection, 38 | pub(crate) id: ChannelId, 39 | pub(crate) channels_keepalive: mpsc::Sender<()>, 40 | pub(crate) from_channels_send: mpsc::Sender, 41 | pub(crate) close_recv: CloseRecv, 42 | pub(crate) channel_close_recv: mpsc::Receiver<()>, 43 | pub(crate) bytes_recv: mpsc::Receiver, 44 | } 45 | 46 | pub(crate) async fn send_channels_tasks_spawner( 47 | connection: quinn::Connection, 48 | mut close_recv: broadcast::Receiver, 49 | mut to_channels_recv: mpsc::Receiver, 50 | from_channels_send: mpsc::Sender, 51 | ) { 52 | // Use an mpsc channel where, instead of sending messages, we wait for the channel to be closed, which happens when every sender has been dropped. We can't use a JoinSet as simply here since we would also need to drain closed channels from it. 53 | let (channel_tasks_keepalive, mut channel_tasks_waiter) = mpsc::channel::<()>(1); 54 | 55 | let close_receiver_clone = close_recv.resubscribe(); 56 | tokio::select! { 57 | _ = close_recv.recv() => { 58 | trace!("Connection Channels listener received a close signal") 59 | } 60 | _ = async { 61 | while let Some(ChannelSyncMessage::CreateChannel { 62 | id, 63 | config, 64 | bytes_to_channel_recv: bytes_recv, 65 | channel_close_recv, 66 | }) = to_channels_recv.recv().await { 67 | 68 | let channel_task_data = SendChannelTaskData { 69 | connection: connection.clone(), 70 | id, 71 | channels_keepalive: channel_tasks_keepalive.clone(), 72 | from_channels_send: from_channels_send.clone(), 73 | close_recv: close_receiver_clone.resubscribe(), 74 | channel_close_recv, 75 | bytes_recv, 76 | }; 77 | 78 | match config { 79 | ChannelConfig::OrderedReliable { max_frame_size } => { 80 | tokio::spawn(async move { ordered_reliable_channel_task(channel_task_data, max_frame_size).await }); 81 | } 82 | ChannelConfig::UnorderedReliable { max_frame_size } => { 83 | tokio::spawn( 84 | async move { unordered_reliable_channel_task(channel_task_data, max_frame_size).await }, 85 | ); 86 | } 87 | ChannelConfig::Unreliable => { 88 | tokio::spawn(async move { unreliable_channel_task(channel_task_data).await }); 89 | } 90 | } 91 | } 92 | } => { 93 | trace!("Connection Channels listener ended") 94 | } 95 | }; 96 | 97 | // Wait for all the channels to have flushed/finished: 98 | // We drop our sender first because the recv() call otherwise sleeps forever. 99 | // When every sender has gone out of scope, the recv call will return with an error. We ignore the error. 100 | drop(channel_tasks_keepalive); 101 | let _ = channel_tasks_waiter.recv().await; 102 | 103 | connection.close(VarInt::from_u32(0), "closed".as_bytes()); 104 | } 105 | 106 | pub(crate) fn spawn_recv_channels_tasks( 107 | connection_handle: quinn::Connection, 108 | connection_id: u64, 109 | close_recv: broadcast::Receiver, 110 | bytes_incoming_send: mpsc::Sender<(ChannelId, Bytes)>, 111 | ) { 112 | // Spawn a task to listen for reliable messages 113 | { 114 | let connection_handle = connection_handle.clone(); 115 | let close_recv = close_recv.resubscribe(); 116 | let bytes_incoming_send = bytes_incoming_send.clone(); 117 | tokio::spawn(async move { 118 | reliable_channels_receiver_task( 119 | connection_id, 120 | connection_handle, 121 | close_recv, 122 | bytes_incoming_send, 123 | ) 124 | .await 125 | }); 126 | } 127 | 128 | // Spawn a task to listen for unreliable datagrams 129 | { 130 | let connection_handle = connection_handle.clone(); 131 | let close_recv = close_recv.resubscribe(); 132 | let bytes_incoming_send = bytes_incoming_send.clone(); 133 | tokio::spawn(async move { 134 | unreliable_channel_receiver_task( 135 | connection_id, 136 | connection_handle, 137 | close_recv, 138 | bytes_incoming_send, 139 | ) 140 | .await 141 | }); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /tests/channels.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::App; 2 | 3 | use bevy_quinnet::{ 4 | client::{ClientPayloadSendError, QuinnetClient}, 5 | server::{QuinnetServer, ServerGroupPayloadSendError}, 6 | shared::channels::ChannelConfig, 7 | }; 8 | 9 | // https://github.com/rust-lang/rust/issues/46379 10 | pub use utils::*; 11 | 12 | mod utils; 13 | 14 | /////////////////////////////////////////////////////////// 15 | /// /// 16 | /// Test /// 17 | /// /// 18 | /////////////////////////////////////////////////////////// 19 | 20 | #[test] 21 | fn default_channel() { 22 | let port = 6001; // TODO Use port 0 and retrieve the port used by the server. 23 | let mut server_app: App = start_simple_server_app(port); 24 | let mut client_app: App = start_simple_client_app(port); 25 | 26 | let client_default_channel = get_default_client_channel(&client_app); 27 | let server_default_channel = get_default_server_channel(&server_app); 28 | 29 | for channel in vec![client_default_channel, server_default_channel] { 30 | assert!(matches!(channel, 0), "Default channel id should be 0"); 31 | } 32 | 33 | close_client_channel(client_default_channel, &mut client_app); 34 | close_server_channel(server_default_channel, &mut server_app); 35 | 36 | { 37 | let mut server = server_app.world_mut().resource_mut::(); 38 | assert_eq!( 39 | server.endpoint().default_channel(), 40 | None, 41 | "Default server channel should be None" 42 | ); 43 | 44 | assert!( 45 | matches!( 46 | server 47 | .endpoint_mut() 48 | .broadcast_payload(TEST_MESSAGE_PAYLOAD), 49 | Err(ServerGroupPayloadSendError::NoDefaultChannel) 50 | ), 51 | "Should not be able to send on default channel" 52 | ); 53 | } 54 | { 55 | let mut client = client_app.world_mut().resource_mut::(); 56 | assert_eq!( 57 | client.connection().default_channel(), 58 | None, 59 | "Default client channel should be None" 60 | ); 61 | 62 | assert!( 63 | matches!( 64 | client.connection_mut().send_payload(TEST_MESSAGE_PAYLOAD), 65 | Err(ClientPayloadSendError::NoDefaultChannel) 66 | ), 67 | "Should not be able to send on default channel" 68 | ); 69 | } 70 | 71 | let client_channel = open_client_channel(ChannelConfig::default(), &mut client_app); 72 | let server_channel = open_server_channel(ChannelConfig::default(), &mut server_app); 73 | 74 | let client_id = wait_for_client_connected(&mut client_app, &mut server_app); 75 | 76 | let mut msg_counter = 0; 77 | send_and_test_client_message( 78 | client_id, 79 | client_channel, 80 | &mut client_app, 81 | &mut server_app, 82 | &mut msg_counter, 83 | ); 84 | send_and_test_server_message( 85 | client_id, 86 | server_channel, 87 | &mut server_app, 88 | &mut client_app, 89 | &mut msg_counter, 90 | ); 91 | } 92 | 93 | /////////////////////////////////////////////////////////// 94 | /// /// 95 | /// Test /// 96 | /// /// 97 | /////////////////////////////////////////////////////////// 98 | 99 | #[test] 100 | fn multi_instance_channels() { 101 | let port = 6002; // TODO Use port 0 and retrieve the port used by the server. 102 | let mut server_app: App = start_simple_server_app(port); 103 | 104 | for channel_type in vec![ 105 | ChannelConfig::default_ordered_reliable(), 106 | ChannelConfig::default_unordered_reliable(), 107 | ChannelConfig::default_unreliable(), 108 | ] { 109 | let mut client_app_1: App = start_simple_client_app(port); 110 | 111 | let client_id_1 = wait_for_client_connected(&mut client_app_1, &mut server_app); 112 | 113 | let client_1_channel_1 = get_default_client_channel(&client_app_1); 114 | let client_1_channel_2 = open_client_channel(channel_type, &mut client_app_1); 115 | 116 | let server_channel_1 = get_default_server_channel(&server_app); 117 | let server_channel_2 = open_server_channel(channel_type, &mut server_app); 118 | 119 | let mut msg_counter = 0; 120 | for channel in vec![client_1_channel_1, client_1_channel_2] { 121 | send_and_test_client_message( 122 | client_id_1, 123 | channel, 124 | &mut client_app_1, 125 | &mut server_app, 126 | &mut msg_counter, 127 | ); 128 | } 129 | for channel in vec![server_channel_1, server_channel_2] { 130 | send_and_test_server_message( 131 | client_id_1, 132 | channel, 133 | &mut server_app, 134 | &mut client_app_1, 135 | &mut msg_counter, 136 | ); 137 | } 138 | 139 | let mut client_app_2 = start_simple_client_app(port); 140 | let client_id_2 = wait_for_client_connected(&mut client_app_2, &mut server_app); 141 | 142 | for (client_id, mut client_app) in 143 | vec![(client_id_1, client_app_1), (client_id_2, client_app_2)] 144 | { 145 | send_and_test_server_message( 146 | client_id, 147 | server_channel_1, 148 | &mut server_app, 149 | &mut client_app, 150 | &mut msg_counter, 151 | ); 152 | send_and_test_server_message( 153 | client_id, 154 | server_channel_2, 155 | &mut server_app, 156 | &mut client_app, 157 | &mut msg_counter, 158 | ); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/server/certificate.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{self, File}, 3 | io::BufReader, 4 | path::Path, 5 | }; 6 | 7 | use bevy::log::{trace, warn}; 8 | 9 | use super::EndpointCertificateError; 10 | use crate::shared::certificate::CertificateFingerprint; 11 | 12 | /// Represents the origin of a certificate. 13 | #[derive(Debug, Clone)] 14 | pub enum CertOrigin { 15 | /// Indicates that the certificate was generated. 16 | Generated { 17 | /// Contains the hostname used when generating the certificate. 18 | server_hostname: String, 19 | }, 20 | /// Indicates that the certificate was loaded from a file. 21 | Loaded, 22 | } 23 | 24 | /// How the server should retrieve its certificate. 25 | #[derive(Debug, Clone)] 26 | pub enum CertificateRetrievalMode { 27 | /// The server will always generate a new self-signed certificate when starting up, 28 | GenerateSelfSigned { 29 | /// Used as the subject of the certificate. 30 | server_hostname: String, 31 | }, 32 | /// Try to load cert & key from files `cert_file``and `key_file`. 33 | LoadFromFile { 34 | /// Path of the file containing the certificate in PEM form 35 | cert_file: String, 36 | /// Path of the file containing the private key in PEM form 37 | key_file: String, 38 | }, 39 | /// Try to load cert & key from files `cert_file``and `key_file`. 40 | /// If the files do not exist, generates a self-signed certificate. 41 | LoadFromFileOrGenerateSelfSigned { 42 | /// Path of the file containing the certificate in PEM form 43 | cert_file: String, 44 | /// Path of the file containing the private key in PEM form 45 | key_file: String, 46 | /// Saves the generated certificate to disk if enabled. 47 | save_on_disk: bool, 48 | /// Used as the subject of the certificate. 49 | server_hostname: String, 50 | }, 51 | } 52 | 53 | /// Represents a server certificate. 54 | pub struct ServerCertificate { 55 | /// The server's certificate chain. 56 | pub cert_chain: Vec>, 57 | /// The server's private key. 58 | pub priv_key: rustls::pki_types::PrivateKeyDer<'static>, 59 | /// The fingerprint of the server's main certificate (first in the chain) 60 | pub fingerprint: CertificateFingerprint, 61 | } 62 | 63 | fn read_cert_from_files( 64 | cert_file: &String, 65 | key_file: &String, 66 | ) -> Result { 67 | let mut cert_chain_reader = BufReader::new(File::open(cert_file)?); 68 | let cert_chain: Vec = 69 | rustls_pemfile::certs(&mut cert_chain_reader).collect::>()?; 70 | 71 | assert!(!cert_chain.is_empty()); 72 | 73 | let mut key_reader = BufReader::new(File::open(key_file)?); 74 | let priv_key = rustls_pemfile::private_key(&mut key_reader)?.expect("private key is present"); 75 | let fingerprint = CertificateFingerprint::from(&cert_chain[0]); 76 | 77 | Ok(ServerCertificate { 78 | cert_chain, 79 | priv_key, 80 | fingerprint, 81 | }) 82 | } 83 | 84 | fn write_cert_to_files( 85 | cert: &rcgen::CertifiedKey, 86 | cert_file: &String, 87 | key_file: &String, 88 | ) -> std::io::Result<()> { 89 | for file in [cert_file, key_file] { 90 | if let Some(parent) = std::path::Path::new(file).parent() { 91 | std::fs::create_dir_all(parent)?; 92 | } 93 | } 94 | 95 | fs::write(cert_file, cert.cert.pem())?; 96 | fs::write(key_file, cert.key_pair.serialize_pem())?; 97 | 98 | Ok(()) 99 | } 100 | 101 | fn generate_self_signed_certificate( 102 | server_host: &String, 103 | ) -> Result<(ServerCertificate, rcgen::CertifiedKey), EndpointCertificateError> { 104 | let generated = rcgen::generate_simple_self_signed(vec![server_host.into()])?; 105 | 106 | let priv_key_der = 107 | rustls::pki_types::PrivatePkcs8KeyDer::from(generated.key_pair.serialize_der()).into(); 108 | let cert_der = generated.cert.der(); 109 | let fingerprint = CertificateFingerprint::from(cert_der); 110 | 111 | Ok(( 112 | ServerCertificate { 113 | cert_chain: vec![cert_der.clone()], 114 | priv_key: priv_key_der, 115 | fingerprint, 116 | }, 117 | generated, 118 | )) 119 | } 120 | 121 | pub(crate) fn retrieve_certificate( 122 | cert_mode: CertificateRetrievalMode, 123 | ) -> Result { 124 | match cert_mode { 125 | CertificateRetrievalMode::GenerateSelfSigned { server_hostname } => { 126 | let (server_cert, _rcgen_cert) = generate_self_signed_certificate(&server_hostname)?; 127 | trace!("Generatied a new self-signed certificate"); 128 | Ok(server_cert) 129 | } 130 | CertificateRetrievalMode::LoadFromFile { 131 | cert_file, 132 | key_file, 133 | } => { 134 | let server_cert = read_cert_from_files(&cert_file, &key_file)?; 135 | trace!("Successfuly loaded cert and key from files"); 136 | Ok(server_cert) 137 | } 138 | CertificateRetrievalMode::LoadFromFileOrGenerateSelfSigned { 139 | save_on_disk, 140 | cert_file, 141 | key_file, 142 | server_hostname, 143 | } => { 144 | if Path::new(&cert_file).exists() && Path::new(&key_file).exists() { 145 | let server_cert = read_cert_from_files(&cert_file, &key_file)?; 146 | trace!("Successfuly loaded cert and key from files"); 147 | Ok(server_cert) 148 | } else { 149 | warn!("{} and/or {} do not exist, could not load existing certificate. Generating a new self-signed certificate.", cert_file, key_file); 150 | let (server_cert, rcgen_cert) = generate_self_signed_certificate(&server_hostname)?; 151 | if save_on_disk { 152 | write_cert_to_files(&rcgen_cert, &cert_file, &key_file)?; 153 | trace!("Successfuly saved cert and key to files"); 154 | } 155 | Ok(server_cert) 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /examples/chat/client.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | thread::{self, sleep}, 4 | time::Duration, 5 | }; 6 | 7 | use bevy::{ 8 | app::{AppExit, ScheduleRunnerPlugin}, 9 | ecs::{ 10 | message::{MessageReader, MessageWriter}, 11 | schedule::IntoScheduleConfigs, 12 | }, 13 | log::{info, warn, LogPlugin}, 14 | prelude::{App, Commands, Deref, DerefMut, PostUpdate, ResMut, Resource, Startup, Update}, 15 | }; 16 | use bevy_quinnet::{ 17 | client::{ 18 | certificate::CertificateVerificationMode, 19 | client_connected, 20 | connection::{ClientAddrConfiguration, ConnectionEvent, ConnectionFailedEvent}, 21 | ClientConnectionConfiguration, QuinnetClient, QuinnetClientPlugin, 22 | }, 23 | shared::ClientId, 24 | }; 25 | use rand::{distributions::Alphanumeric, Rng}; 26 | use tokio::sync::mpsc; 27 | 28 | use protocol::{ClientMessage, ServerMessage}; 29 | 30 | // use crate::protocol::NetworkChannels; 31 | 32 | mod protocol; 33 | 34 | #[derive(Resource, Debug, Clone, Default)] 35 | struct Users { 36 | self_id: ClientId, 37 | names: HashMap, 38 | } 39 | 40 | #[derive(Resource, Deref, DerefMut)] 41 | struct TerminalReceiver(mpsc::Receiver); 42 | 43 | pub fn on_app_exit(app_exit_events: MessageReader, mut client: ResMut) { 44 | if !app_exit_events.is_empty() { 45 | client 46 | .connection_mut() 47 | .send_message(ClientMessage::Disconnect {}) 48 | .unwrap(); 49 | // TODO Clean: event to let the async client send his last messages. 50 | sleep(Duration::from_secs_f32(0.1)); 51 | } 52 | } 53 | 54 | fn handle_server_messages(mut users: ResMut, mut client: ResMut) { 55 | while let Some(message) = client.connection_mut().try_receive_message() { 56 | match message { 57 | ServerMessage::ClientConnected { 58 | client_id, 59 | username, 60 | } => { 61 | info!("{} joined", username); 62 | users.names.insert(client_id, username); 63 | } 64 | ServerMessage::ClientDisconnected { client_id } => { 65 | if let Some(username) = users.names.remove(&client_id) { 66 | println!("{} left", username); 67 | } else { 68 | warn!("ClientDisconnected for an unknown client_id: {}", client_id) 69 | } 70 | } 71 | ServerMessage::ChatMessage { client_id, message } => { 72 | if let Some(username) = users.names.get(&client_id) { 73 | if client_id != users.self_id { 74 | println!("{}: {}", username, message); 75 | } 76 | } else { 77 | warn!("Chat message from an unknown client_id: {}", client_id) 78 | } 79 | } 80 | ServerMessage::InitClient { 81 | client_id, 82 | usernames, 83 | } => { 84 | users.self_id = client_id; 85 | users.names = usernames; 86 | } 87 | } 88 | } 89 | } 90 | 91 | fn handle_terminal_messages( 92 | mut terminal_messages: ResMut, 93 | mut app_exit_events: MessageWriter, 94 | mut client: ResMut, 95 | ) { 96 | while let Ok(message) = terminal_messages.try_recv() { 97 | if message == "quit" { 98 | app_exit_events.write(AppExit::Success); 99 | } else { 100 | client 101 | .connection_mut() 102 | .try_send_message(ClientMessage::ChatMessage { message: message }); 103 | } 104 | } 105 | } 106 | 107 | fn start_terminal_listener(mut commands: Commands) { 108 | let (from_terminal_sender, from_terminal_receiver) = mpsc::channel::(100); 109 | 110 | thread::spawn(move || loop { 111 | let mut buffer = String::new(); 112 | std::io::stdin().read_line(&mut buffer).unwrap(); 113 | from_terminal_sender 114 | .try_send(buffer.trim_end().to_string()) 115 | .unwrap(); 116 | }); 117 | 118 | commands.insert_resource(TerminalReceiver(from_terminal_receiver)); 119 | } 120 | 121 | fn start_connection(mut client: ResMut) { 122 | client 123 | .open_connection(ClientConnectionConfiguration { 124 | addr_config: ClientAddrConfiguration::from_strings("[::1]:6000", "[::]:0").unwrap(), 125 | cert_mode: CertificateVerificationMode::SkipVerification, 126 | defaultables: Default::default(), 127 | }) 128 | .unwrap(); 129 | 130 | // You can already send message(s) even before being connected, they will be buffered. In this example we will wait for a ConnectionEvent. 131 | } 132 | 133 | fn handle_client_events( 134 | mut connection_events: MessageReader, 135 | mut connection_failed_events: MessageReader, 136 | mut client: ResMut, 137 | ) { 138 | if !connection_events.is_empty() { 139 | // We are connected 140 | let username: String = rand::thread_rng() 141 | .sample_iter(&Alphanumeric) 142 | .take(7) 143 | .map(char::from) 144 | .collect(); 145 | 146 | println!("--- Joining with name: {}", username); 147 | println!("--- Type 'quit' to disconnect"); 148 | 149 | client 150 | .connection_mut() 151 | .send_message(ClientMessage::Join { name: username }) 152 | .unwrap(); 153 | 154 | connection_events.clear(); 155 | } 156 | for ev in connection_failed_events.read() { 157 | println!( 158 | "Failed to connect: {:?}, make sure the chat-server is running.", 159 | ev.err 160 | ); 161 | } 162 | } 163 | 164 | fn main() { 165 | App::new() 166 | .add_plugins(( 167 | ScheduleRunnerPlugin::default(), 168 | LogPlugin::default(), 169 | QuinnetClientPlugin::default(), 170 | )) 171 | .insert_resource(Users::default()) 172 | .add_systems(Startup, (start_terminal_listener, start_connection)) 173 | .add_systems( 174 | Update, 175 | ( 176 | handle_client_events, 177 | (handle_terminal_messages, handle_server_messages).run_if(client_connected), 178 | ), 179 | ) 180 | // CoreSet::PostUpdate so that AppExit events generated in the previous stage are available 181 | .add_systems(PostUpdate, on_app_exit) 182 | .run(); 183 | } 184 | -------------------------------------------------------------------------------- /src/shared/channels/reliable/send.rs: -------------------------------------------------------------------------------- 1 | use bevy::log::{error, trace, warn}; 2 | use futures::sink::SinkExt; 3 | use quinn::SendStream; 4 | use tokio_util::codec::FramedWrite; 5 | 6 | use crate::shared::channels::{ 7 | tasks::SendChannelTaskData, ChannelAsyncMessage, ChannelId, CloseReason, 8 | }; 9 | 10 | use super::codec::QuinnetProtocolCodecEncoder; 11 | 12 | async fn new_uni_frame_sender( 13 | connection: &quinn::Connection, 14 | raw_channel_id: ChannelId, 15 | max_frame_len: usize, 16 | ) -> FramedWrite { 17 | let uni_sender = connection 18 | .open_uni() 19 | .await 20 | .expect("Failed to open send stream"); 21 | FramedWrite::new( 22 | uni_sender, 23 | QuinnetProtocolCodecEncoder::new(raw_channel_id, max_frame_len), 24 | ) 25 | } 26 | 27 | pub(crate) async fn ordered_reliable_channel_task( 28 | mut channel_task: SendChannelTaskData, 29 | max_frame_len: usize, 30 | ) { 31 | let mut frame_sender = 32 | new_uni_frame_sender(&channel_task.connection, channel_task.id, max_frame_len).await; 33 | 34 | let close_reason = tokio::select! { 35 | close_reason = channel_task.close_recv.recv() => { 36 | trace!("Ordered Reliable Channel task received a close signal"); 37 | match close_reason { 38 | Ok(reason) => reason, 39 | Err(_) => CloseReason::LocalOrder, 40 | } 41 | } 42 | _ = channel_task.channel_close_recv.recv() => { 43 | trace!("Ordered Reliable Channel task received a channel close signal"); 44 | CloseReason::LocalOrder 45 | } 46 | _ = async { 47 | // Send channel messages 48 | while let Some(msg_bytes) = channel_task.bytes_recv.recv().await { 49 | if let Err(err) = frame_sender.send(msg_bytes).await { 50 | error!("Error while sending on Ordered Reliable Channel, {}", err); 51 | channel_task.from_channels_send.send( 52 | ChannelAsyncMessage::LostConnection) 53 | .await 54 | .expect("Failed to signal connection lost on Ordered Reliable Channel"); 55 | } 56 | } 57 | } => { 58 | trace!("Ordered Reliable Channel task ended"); 59 | CloseReason::LocalOrder 60 | } 61 | }; 62 | // No need to try to flush if we know that the peer is already closed 63 | if close_reason != CloseReason::PeerClosed { 64 | while let Ok(msg_bytes) = channel_task.bytes_recv.try_recv() { 65 | if let Err(err) = frame_sender.send(msg_bytes).await { 66 | warn!( 67 | "Failed to send a remaining message on Ordered Reliable Channel, {}", 68 | err 69 | ); 70 | } 71 | } 72 | if let Err(err) = frame_sender.flush().await { 73 | warn!( 74 | "Error while flushing Ordered Reliable Channel stream: {}", 75 | err 76 | ); 77 | } 78 | if let Err(err) = frame_sender.into_inner().finish() { 79 | warn!( 80 | "Failed to shutdown Ordered Reliable Channel stream gracefully: {}", 81 | err 82 | ); 83 | } 84 | } 85 | } 86 | 87 | pub(crate) async fn unordered_reliable_channel_task( 88 | mut channel_task: SendChannelTaskData, 89 | max_frame_len: usize, 90 | ) { 91 | let close_reason = tokio::select! { 92 | close_reason = channel_task.close_recv.recv() => { 93 | trace!("Unordered Reliable Channel task received a close signal"); 94 | match close_reason { 95 | Ok(reason) => reason, 96 | Err(_) => CloseReason::LocalOrder, 97 | } 98 | } 99 | _ = channel_task.channel_close_recv.recv() => { 100 | trace!("Unordered Reliable Channel task received a channel close signal"); 101 | CloseReason::LocalOrder 102 | } 103 | _ = async { 104 | while let Some(msg_bytes) = channel_task.bytes_recv.recv().await { 105 | let conn = channel_task.connection.clone(); 106 | let from_channels_send_clone = channel_task.from_channels_send.clone(); 107 | let channels_keepalive_clone = channel_task.channels_keepalive.clone(); 108 | tokio::spawn(async move { 109 | let mut frame_sender = new_uni_frame_sender(&conn,channel_task.id, max_frame_len).await; 110 | if let Err(err) = frame_sender.send(msg_bytes).await { 111 | error!("Error while sending on Unordered Reliable Channel, {}", err); 112 | from_channels_send_clone.send( 113 | ChannelAsyncMessage::LostConnection) 114 | .await 115 | .expect("Failed to signal connection lost on Unordered Reliable Channel"); 116 | } 117 | if let Err(err) = frame_sender.into_inner().finish() { 118 | warn!("Failed to shutdown Unordered Reliable Channel stream gracefully: {}", err); 119 | } 120 | drop(channels_keepalive_clone) 121 | }); 122 | } 123 | } => { 124 | trace!("Unordered Reliable Channel task ended"); 125 | CloseReason::LocalOrder 126 | } 127 | }; 128 | // No need to try to flush if we know that the peer is already closed 129 | if close_reason != CloseReason::PeerClosed { 130 | while let Ok(msg_bytes) = channel_task.bytes_recv.try_recv() { 131 | let conn = channel_task.connection.clone(); 132 | let channels_keepalive_clone = channel_task.channels_keepalive.clone(); 133 | tokio::spawn(async move { 134 | let mut frame_sender = 135 | new_uni_frame_sender(&conn, channel_task.id, max_frame_len).await; 136 | if let Err(err) = frame_sender.send(msg_bytes).await { 137 | warn!( 138 | "Failed to send a remaining message on Unordered Reliable Channel, {}", 139 | err 140 | ); 141 | } 142 | if let Err(err) = frame_sender.into_inner().finish() { 143 | warn!( 144 | "Failed to shutdown Unordered Reliable Channel stream gracefully: {}", 145 | err 146 | ); 147 | } 148 | drop(channels_keepalive_clone) 149 | }); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /examples/breakout/breakout.rs: -------------------------------------------------------------------------------- 1 | //! A simplified implementation of the classic game "Breakout". 2 | //! => Original example by Bevy, modified for Bevy Quinnet to add a 2 players versus mode. 3 | 4 | use std::net::Ipv6Addr; 5 | 6 | use bevy::prelude::*; 7 | use bevy_quinnet::{ 8 | client::QuinnetClientPlugin, 9 | server::{QuinnetServer, QuinnetServerPlugin}, 10 | }; 11 | use client::BACKGROUND_COLOR; 12 | 13 | mod client; 14 | mod protocol; 15 | mod server; 16 | 17 | const SERVER_HOST: Ipv6Addr = Ipv6Addr::LOCALHOST; 18 | const LOCAL_BIND_IP: Ipv6Addr = Ipv6Addr::UNSPECIFIED; 19 | const SERVER_PORT: u16 = 6000; 20 | 21 | // Defines the amount of time that should elapse between each physics step. 22 | const TIME_STEP: f32 = 1.0 / 60.0; 23 | 24 | // These constants are defined in `Transform` units. 25 | // Using the default 2D camera they correspond 1:1 with screen pixels. 26 | const PADDLE_SIZE: Vec3 = Vec3::new(120.0, 20.0, 0.0); 27 | const GAP_BETWEEN_PADDLE_AND_FLOOR: f32 = 60.0; 28 | const PADDLE_SPEED: f32 = 500.0; 29 | // How close can the paddle get to the wall 30 | const PADDLE_PADDING: f32 = 10.0; 31 | 32 | const BALL_DIAMETER: f32 = 30.; 33 | const BALL_SIZE: Vec3 = Vec3::new(BALL_DIAMETER, BALL_DIAMETER, 0.0); 34 | const BALL_SPEED: f32 = 400.0; 35 | 36 | const WALL_THICKNESS: f32 = 10.0; 37 | // x coordinates 38 | const LEFT_WALL: f32 = -450.; 39 | const RIGHT_WALL: f32 = 450.; 40 | // y coordinates 41 | const BOTTOM_WALL: f32 = -300.; 42 | const TOP_WALL: f32 = 300.; 43 | 44 | const BRICK_SIZE: Vec2 = Vec2::new(100., 30.); 45 | // These values are exact 46 | const GAP_BETWEEN_PADDLE_AND_BRICKS: f32 = 140.0; 47 | const GAP_BETWEEN_BRICKS: f32 = 5.0; 48 | // These values are lower bounds, as the number of bricks is computed 49 | const GAP_BETWEEN_BRICKS_AND_SIDES: f32 = 20.0; 50 | 51 | #[derive(Default, Clone, Eq, PartialEq, Debug, Hash, States)] 52 | enum GameState { 53 | #[default] 54 | MainMenu, 55 | HostingLobby, 56 | JoiningLobby, 57 | Running, 58 | } 59 | 60 | #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] 61 | pub enum GameSystems { 62 | HostSystems, 63 | ClientSystems, 64 | } 65 | 66 | #[derive(Component, Deref, DerefMut)] 67 | struct Velocity(Vec2); 68 | 69 | #[derive(Default, Message)] 70 | struct CollisionEvent; 71 | 72 | #[derive(Component)] 73 | struct Score; 74 | 75 | #[derive(Resource, Deref)] 76 | struct CollisionSound(Handle); 77 | 78 | pub type BrickId = u64; 79 | 80 | /// Which side of the arena is this wall located on? 81 | enum WallLocation { 82 | Left, 83 | Right, 84 | Bottom, 85 | Top, 86 | } 87 | 88 | impl WallLocation { 89 | fn position(&self) -> Vec2 { 90 | match self { 91 | WallLocation::Left => Vec2::new(LEFT_WALL, 0.), 92 | WallLocation::Right => Vec2::new(RIGHT_WALL, 0.), 93 | WallLocation::Bottom => Vec2::new(0., BOTTOM_WALL), 94 | WallLocation::Top => Vec2::new(0., TOP_WALL), 95 | } 96 | } 97 | 98 | fn size(&self) -> Vec2 { 99 | let arena_height = TOP_WALL - BOTTOM_WALL; 100 | let arena_width = RIGHT_WALL - LEFT_WALL; 101 | // Make sure we haven't messed up our constants 102 | assert!(arena_height > 0.0); 103 | assert!(arena_width > 0.0); 104 | 105 | match self { 106 | WallLocation::Left | WallLocation::Right => { 107 | Vec2::new(WALL_THICKNESS, arena_height + WALL_THICKNESS) 108 | } 109 | WallLocation::Bottom | WallLocation::Top => { 110 | Vec2::new(arena_width + WALL_THICKNESS, WALL_THICKNESS) 111 | } 112 | } 113 | } 114 | } 115 | 116 | fn server_is_listening(server: Res) -> bool { 117 | server.is_listening() 118 | } 119 | 120 | fn main() { 121 | let mut app = App::new(); 122 | app.add_plugins(( 123 | DefaultPlugins, 124 | QuinnetServerPlugin::default(), 125 | QuinnetClientPlugin::default(), 126 | )); 127 | app.add_message::(); 128 | app.init_state::(); 129 | app.insert_resource(ClearColor(BACKGROUND_COLOR)) 130 | .insert_resource(client::Scoreboard { score: 0 }) 131 | .insert_resource(client::ClientData::default()) 132 | .insert_resource(client::NetworkMapping::default()) 133 | .insert_resource(client::BricksMapping::default()); 134 | 135 | // ------ Main menu ------ 136 | app.add_systems(Update, close_on_esc) 137 | .add_systems(OnEnter(GameState::MainMenu), client::setup_main_menu) 138 | .add_systems( 139 | Update, 140 | client::handle_menu_buttons.run_if(in_state(GameState::MainMenu)), 141 | ) 142 | .add_systems(OnExit(GameState::MainMenu), client::teardown_main_menu); 143 | 144 | // Hosting as both server & client 145 | app.add_systems( 146 | OnEnter(GameState::HostingLobby), 147 | (server::start_listening, client::start_connection), 148 | ) 149 | .add_systems( 150 | Update, 151 | ( 152 | server::handle_client_messages, 153 | server::handle_server_events, 154 | client::handle_server_setup_messages, 155 | ) 156 | .run_if(in_state(GameState::HostingLobby)), 157 | ); 158 | 159 | // Or just Joining as a client 160 | app.add_systems(OnEnter(GameState::JoiningLobby), client::start_connection) 161 | .add_systems( 162 | Update, 163 | client::handle_server_setup_messages.run_if(in_state(GameState::JoiningLobby)), 164 | ); 165 | 166 | // ------ Running the game ------ 167 | 168 | // Every app is a client 169 | app.add_systems(OnEnter(GameState::Running), client::setup_breakout); 170 | 171 | app.edit_schedule(FixedUpdate, |schedule| { 172 | schedule.configure_sets(GameSystems::ClientSystems.run_if(in_state(GameState::Running))); 173 | schedule.add_systems( 174 | ( 175 | ( 176 | client::handle_server_gameplay_events, 177 | client::handle_server_updates, 178 | client::apply_velocity, 179 | ) 180 | .chain(), 181 | client::move_paddle, 182 | client::update_scoreboard, 183 | client::play_collision_sound, 184 | ) 185 | .in_set(GameSystems::ClientSystems), 186 | ); 187 | }); 188 | 189 | // But hosting apps are also a server 190 | app.edit_schedule(FixedUpdate, |schedule| { 191 | schedule.configure_sets( 192 | GameSystems::HostSystems 193 | .run_if(in_state(GameState::Running)) 194 | .run_if(server_is_listening), 195 | ); 196 | schedule.add_systems( 197 | (( 198 | server::handle_client_messages, 199 | (server::update_paddles, server::apply_velocity), 200 | server::check_for_collisions, 201 | ) 202 | .chain(),) 203 | .in_set(GameSystems::HostSystems), 204 | ); 205 | }); 206 | 207 | app.run(); 208 | } 209 | 210 | pub fn close_on_esc( 211 | mut commands: Commands, 212 | focused_windows: Query<(Entity, &Window)>, 213 | input: Res>, 214 | ) { 215 | for (window, focus) in focused_windows.iter() { 216 | if !focus.focused { 217 | continue; 218 | } 219 | 220 | if input.just_pressed(KeyCode::Escape) { 221 | commands.entity(window).despawn(); 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/shared/channels.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use std::fmt::Debug; 3 | use tokio::sync::{ 4 | broadcast, 5 | mpsc::{self, error::TrySendError}, 6 | }; 7 | 8 | mod reliable; 9 | pub(crate) mod tasks; 10 | mod unreliable; 11 | 12 | pub use reliable::DEFAULT_MAX_RELIABLE_FRAME_LEN; 13 | 14 | use super::error::{AsyncChannelError, ChannelCloseError, ChannelConfigError}; 15 | 16 | /// Id of an opened channel 17 | pub type ChannelId = u8; 18 | /// Maximum number of channels that can be opened simultaneously 19 | pub const MAX_CHANNEL_COUNT: usize = u8::MAX as usize + 1; 20 | 21 | pub(crate) const CHANNEL_ID_LEN: usize = size_of::(); 22 | pub(crate) const PROTOCOL_HEADER_LEN: usize = CHANNEL_ID_LEN; 23 | pub(crate) type CloseSend = broadcast::Sender; 24 | pub(crate) type CloseRecv = broadcast::Receiver; 25 | 26 | #[derive(PartialEq, Clone, Copy)] 27 | pub(crate) enum CloseReason { 28 | LocalOrder, 29 | PeerClosed, 30 | } 31 | 32 | /// Type of a channel, offering different delivery guarantees. 33 | #[derive(Debug, Copy, Clone)] 34 | pub enum ChannelConfig { 35 | /// An OrderedReliable channel ensures that messages sent are delivered, and are processed by the receiving end in the same order as they were sent. 36 | OrderedReliable { 37 | /// Maximum size of payloads sent on this channel, in bytes 38 | max_frame_size: usize, 39 | }, 40 | /// An UnorderedReliable channel ensures that messages sent are delivered, but they may be delivered out of order. 41 | UnorderedReliable { 42 | /// Maximum size of payloads sent on this channel, in bytes 43 | max_frame_size: usize, 44 | }, 45 | /// Channel which transmits messages as unreliable and unordered datagrams (may be lost or delivered out of order). 46 | /// 47 | /// The maximum allowed size of a datagram may change over the lifetime of a connection according to variation in the path MTU estimate. This is guaranteed to be a little over a kilobyte at minimum. 48 | Unreliable, 49 | } 50 | 51 | impl Default for ChannelConfig { 52 | fn default() -> Self { 53 | ChannelConfig::default_ordered_reliable() 54 | } 55 | } 56 | impl ChannelConfig { 57 | /// Default channel configuration for an Unreliable channel. 58 | pub const fn default_unreliable() -> Self { 59 | ChannelConfig::Unreliable 60 | } 61 | /// Default channel configuration for an UnorderedReliable channel. 62 | pub const fn default_unordered_reliable() -> Self { 63 | ChannelConfig::UnorderedReliable { 64 | max_frame_size: DEFAULT_MAX_RELIABLE_FRAME_LEN, 65 | } 66 | } 67 | /// Default channel configuration for an OrderedReliable channel. 68 | pub const fn default_ordered_reliable() -> Self { 69 | ChannelConfig::OrderedReliable { 70 | max_frame_size: DEFAULT_MAX_RELIABLE_FRAME_LEN, 71 | } 72 | } 73 | } 74 | 75 | #[derive(Debug)] 76 | /// From async to sync 77 | pub(crate) enum ChannelAsyncMessage { 78 | LostConnection, 79 | } 80 | 81 | #[derive(Debug)] 82 | /// From sync to async 83 | pub(crate) enum ChannelSyncMessage { 84 | CreateChannel { 85 | id: ChannelId, 86 | config: ChannelConfig, 87 | bytes_to_channel_recv: mpsc::Receiver, 88 | channel_close_recv: mpsc::Receiver<()>, 89 | }, 90 | } 91 | 92 | #[derive(Debug)] 93 | pub(crate) struct Channel { 94 | id: ChannelId, 95 | sender: mpsc::Sender, 96 | close_sender: mpsc::Sender<()>, 97 | } 98 | 99 | impl Channel { 100 | pub(crate) fn new( 101 | id: ChannelId, 102 | sender: mpsc::Sender, 103 | close_sender: mpsc::Sender<()>, 104 | ) -> Self { 105 | Self { 106 | id, 107 | sender, 108 | close_sender, 109 | } 110 | } 111 | 112 | pub fn id(&self) -> ChannelId { 113 | self.id 114 | } 115 | 116 | pub(crate) fn send_payload(&self, payload: Bytes) -> Result<(), AsyncChannelError> { 117 | match self.sender.try_send(payload) { 118 | Ok(_) => Ok(()), 119 | Err(err) => match err { 120 | TrySendError::Full(_) => Err(AsyncChannelError::FullQueue), 121 | TrySendError::Closed(_) => Err(AsyncChannelError::InternalChannelClosed), 122 | }, 123 | } 124 | } 125 | 126 | pub(crate) fn close(&self) -> Result<(), ChannelCloseError> { 127 | match self.close_sender.blocking_send(()) { 128 | Ok(_) => Ok(()), 129 | Err(_) => { 130 | // The only possible error for a send is that there is no active receivers, meaning that the tasks are already terminated. 131 | Err(ChannelCloseError::ChannelAlreadyClosed) 132 | } 133 | } 134 | } 135 | } 136 | 137 | /// Stores a configuration that represents multiple channels to be opened by a [`crate::client::connection::ClientSideConnection`] or [`crate::server::endpoint::Endpoint`]. 138 | /// 139 | /// Each channel in a [SendChannelsConfiguration] is assigned a [ChannelId], starting from 0 and incrementing sequentially by 1. 140 | /// 141 | /// ### Example 142 | /// 143 | /// Declare 3 configured channels with their respective ids `0`, `1` and `2`: 144 | /// ``` 145 | /// use bevy_quinnet::shared::channels::{ChannelConfig, SendChannelsConfiguration}; 146 | /// 147 | /// let configs = SendChannelsConfiguration::from_configs(vec![ 148 | /// ChannelConfig::OrderedReliable { 149 | /// max_frame_size: 8 * 1_024 * 1_024, 150 | /// }, 151 | /// ChannelConfig::UnorderedReliable { 152 | /// max_frame_size: 10 * 1_024, 153 | /// }, 154 | /// ChannelConfig::OrderedReliable { 155 | /// max_frame_size: 10 * 1_024, 156 | /// }, 157 | /// ]).unwrap(); 158 | /// ``` 159 | #[derive(Debug, Clone)] 160 | pub struct SendChannelsConfiguration { 161 | channels: Vec, 162 | } 163 | 164 | impl Default for SendChannelsConfiguration { 165 | fn default() -> Self { 166 | Self { 167 | channels: vec![ChannelConfig::OrderedReliable { 168 | max_frame_size: DEFAULT_MAX_RELIABLE_FRAME_LEN, 169 | }], 170 | } 171 | } 172 | } 173 | 174 | impl SendChannelsConfiguration { 175 | /// New empty configuration 176 | pub fn new() -> Self { 177 | Self { 178 | channels: Vec::new(), 179 | } 180 | } 181 | 182 | /// New configuration from a simple list of [`ChannelConfig`]. 183 | /// 184 | /// Opened channels (and their [`ChannelId`]) will have the same order as in this collection 185 | pub fn from_configs( 186 | channel_types: Vec, 187 | ) -> Result { 188 | if channel_types.len() > MAX_CHANNEL_COUNT { 189 | Err(ChannelConfigError::MaxChannelsCountReached) 190 | } else { 191 | Ok(Self { 192 | channels: channel_types, 193 | }) 194 | } 195 | } 196 | 197 | /// Adds one element to the configuration from a [`ChannelConfig`]. 198 | /// 199 | /// Opened channels (and their [`ChannelId`]) will have the same order as their insertion order. 200 | pub fn add(&mut self, channel_type: ChannelConfig) -> Option { 201 | if self.channels.len() < MAX_CHANNEL_COUNT { 202 | self.channels.push(channel_type); 203 | Some((self.channels.len() - 1) as u8) 204 | } else { 205 | None 206 | } 207 | } 208 | 209 | pub(crate) fn configs(&self) -> &Vec { 210 | &self.channels 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/server/messages.rs: -------------------------------------------------------------------------------- 1 | use bevy::log::error; 2 | use bytes::Bytes; 3 | 4 | use crate::{ 5 | server::{Endpoint, ServerGroupSendError, ServerReceiveError, ServerSendError}, 6 | shared::{channels::ChannelId, ClientId}, 7 | }; 8 | 9 | /// Error while sending a message to deserialize on the server 10 | #[derive(thiserror::Error, Debug)] 11 | pub enum ServerMessageSendError { 12 | /// Failed serialization 13 | #[error("Failed serialization")] 14 | Serialization, 15 | /// There is no default channel 16 | #[error("There is no default channel")] 17 | NoDefaultChannel, 18 | /// Error when sending data 19 | #[error("Error when sending data")] 20 | SendError(#[from] ServerSendError), 21 | } 22 | 23 | /// Error while sending a message to deserialize on the server to a group of clients 24 | #[derive(thiserror::Error, Debug)] 25 | pub enum ServerGroupMessageSendError { 26 | /// Failed serialization 27 | #[error("Failed serialization")] 28 | Serialization, 29 | /// There is no default channel 30 | #[error("There is no default channel")] 31 | NoDefaultChannel, 32 | /// Error while sending data to a group of clients 33 | #[error("Error while sending data to a group of clients")] 34 | GroupSendError(#[from] ServerGroupSendError), 35 | } 36 | 37 | /// Error while receiving a message to deserialize on the server 38 | #[derive(thiserror::Error, Debug)] 39 | pub enum ServerMessageReceiveError { 40 | /// Failed deserialization 41 | #[error("Failed deserialization")] 42 | Deserialization, 43 | /// There is no default channel 44 | #[error("There is no default channel")] 45 | NoDefaultChannel, 46 | /// Error while receiving data 47 | #[error("Error while receiving data")] 48 | ReceiveError(#[from] ServerReceiveError), 49 | } 50 | 51 | impl Endpoint { 52 | /// Same as [Self::receive_message_from] but on the default channel 53 | pub fn receive_message( 54 | &mut self, 55 | client_id: ClientId, 56 | ) -> Result, ServerMessageReceiveError> { 57 | match self.default_channel() { 58 | Some(channel) => self.receive_message_from(client_id, channel), 59 | None => Err(ServerMessageReceiveError::NoDefaultChannel), 60 | } 61 | } 62 | 63 | /// Same as [Self::receive_message_from] but will log the error instead of returning it 64 | pub fn try_receive_message( 65 | &mut self, 66 | client_id: ClientId, 67 | ) -> Option { 68 | match self.receive_message(client_id) { 69 | Ok(message) => message, 70 | Err(err) => { 71 | error!("try_receive_message: {}", err); 72 | None 73 | } 74 | } 75 | } 76 | 77 | /// Attempt to deserialise a message into type `T`. 78 | /// 79 | /// Will return [`Err`] if: 80 | /// - the bytes accumulated from the client aren't deserializable to T. 81 | /// - or if this client is disconnected. 82 | pub fn receive_message_from>( 83 | &mut self, 84 | client_id: ClientId, 85 | channel_id: C, 86 | ) -> Result, ServerMessageReceiveError> { 87 | match self.receive_payload(client_id, channel_id)? { 88 | Some(payload) => { 89 | match bincode::serde::decode_from_slice(&payload, bincode::config::standard()) { 90 | Ok((msg, _size)) => Ok(Some(msg)), 91 | Err(_) => Err(ServerMessageReceiveError::Deserialization), 92 | } 93 | } 94 | None => Ok(None), 95 | } 96 | } 97 | 98 | /// [`Endpoint::receive_message_from`] that logs the error instead of returning a result. 99 | pub fn try_receive_message_from>( 100 | &mut self, 101 | client_id: ClientId, 102 | channel_id: C, 103 | ) -> Option { 104 | match self.receive_message_from(client_id, channel_id) { 105 | Ok(message) => message, 106 | Err(err) => { 107 | error!("try_receive_message: {}", err); 108 | None 109 | } 110 | } 111 | } 112 | 113 | /// Same as [Endpoint::send_message_on] but on the default channel 114 | pub fn send_message( 115 | &mut self, 116 | client_id: ClientId, 117 | message: T, 118 | ) -> Result<(), ServerMessageSendError> { 119 | match self.default_channel() { 120 | Some(channel) => self.send_message_on(client_id, channel, message), 121 | None => Err(ServerMessageSendError::NoDefaultChannel), 122 | } 123 | } 124 | 125 | /// Sends a message to the specified client on the specified channel 126 | /// 127 | /// Will return an [`Err`] if: 128 | /// - the specified channel does not exist/is closed 129 | /// - or if the client is disconnected 130 | /// - or if a serialization error occurs 131 | /// - (or if the message queue is full) 132 | pub fn send_message_on>( 133 | &mut self, 134 | client_id: ClientId, 135 | channel_id: C, 136 | message: T, 137 | ) -> Result<(), ServerMessageSendError> { 138 | match bincode::serde::encode_to_vec(&message, bincode::config::standard()) { 139 | Ok(payload) => Ok(self.send_payload_on(client_id, channel_id, payload)?), 140 | Err(_) => Err(ServerMessageSendError::Serialization), 141 | } 142 | } 143 | 144 | /// [`Endpoint::send_message`] that logs the error instead of returning a result. 145 | pub fn try_send_message(&mut self, client_id: ClientId, message: T) { 146 | match self.send_message(client_id, message) { 147 | Ok(_) => {} 148 | Err(err) => error!("try_send_message: {}", err), 149 | } 150 | } 151 | 152 | /// [`Endpoint::send_message_on`] that logs the error instead of returning a result. 153 | pub fn try_send_message_on>( 154 | &mut self, 155 | client_id: ClientId, 156 | channel_id: C, 157 | message: T, 158 | ) { 159 | match self.send_message_on(client_id, channel_id, message) { 160 | Ok(_) => {} 161 | Err(err) => error!("try_send_message: {}", err), 162 | } 163 | } 164 | 165 | /// Same as [Endpoint::send_group_message_on] but on the default channel 166 | pub fn send_group_message<'a, I: Iterator, T: serde::Serialize>( 167 | &mut self, 168 | client_ids: I, 169 | message: T, 170 | ) -> Result<(), ServerGroupMessageSendError> { 171 | match self.default_channel() { 172 | Some(channel) => self.send_group_message_on(client_ids, channel, message), 173 | None => Err(ServerGroupMessageSendError::NoDefaultChannel), 174 | } 175 | } 176 | 177 | /// Sends the message to the specified clients on the specified channel. 178 | /// 179 | /// Tries to send to each client before returning. Returns an [`Err`] if sending failed for at least 1 client. Information about the failed sendings will be available in the [`ServerGroupMessageSendError`]. 180 | pub fn send_group_message_on< 181 | 'a, 182 | I: Iterator, 183 | T: serde::Serialize, 184 | C: Into, 185 | >( 186 | &mut self, 187 | client_ids: I, 188 | channel_id: C, 189 | message: T, 190 | ) -> Result<(), ServerGroupMessageSendError> { 191 | let channel_id = channel_id.into(); 192 | let Ok(payload) = bincode::serde::encode_to_vec(&message, bincode::config::standard()) 193 | else { 194 | return Err(ServerGroupMessageSendError::Serialization); 195 | }; 196 | let bytes = Bytes::from(payload); 197 | let mut errs = vec![]; 198 | for &client_id in client_ids { 199 | if let Err(e) = self.send_payload_on(client_id, channel_id, bytes.clone()) { 200 | errs.push((client_id, e.into())); 201 | } 202 | } 203 | match errs.is_empty() { 204 | true => Ok(()), 205 | false => Err(ServerGroupSendError(errs).into()), 206 | } 207 | } 208 | 209 | /// Same as [Endpoint::send_group_message] but will log the error instead of returning it 210 | pub fn try_send_group_message<'a, I: Iterator, T: serde::Serialize>( 211 | &mut self, 212 | client_ids: I, 213 | message: T, 214 | ) { 215 | if let Err(err) = self.send_group_message(client_ids, message) { 216 | error!("try_send_group_message: {}", err); 217 | } 218 | } 219 | 220 | /// Same as [Endpoint::send_group_message_on] but will log the error instead of returning it 221 | pub fn try_send_group_message_on< 222 | 'a, 223 | I: Iterator, 224 | T: serde::Serialize, 225 | C: Into, 226 | >( 227 | &mut self, 228 | client_ids: I, 229 | channel_id: C, 230 | message: T, 231 | ) { 232 | if let Err(err) = self.send_group_message_on(client_ids, channel_id, message) { 233 | error!("try_send_group_message: {}", err); 234 | } 235 | } 236 | 237 | /// Same as [Endpoint::broadcast_message_on] but on the default channel 238 | pub fn broadcast_message( 239 | &mut self, 240 | message: T, 241 | ) -> Result<(), ServerGroupMessageSendError> { 242 | match self.default_channel() { 243 | Some(channel) => self.broadcast_message_on(channel, message), 244 | None => Err(ServerGroupMessageSendError::NoDefaultChannel), 245 | } 246 | } 247 | 248 | /// Same as [Endpoint::broadcast_payload_on] but will serialize the message to a payload before 249 | pub fn broadcast_message_on>( 250 | &mut self, 251 | channel_id: C, 252 | message: T, 253 | ) -> Result<(), ServerGroupMessageSendError> { 254 | match bincode::serde::encode_to_vec(&message, bincode::config::standard()) { 255 | Ok(payload) => Ok(self.broadcast_payload_on(channel_id, payload)?), 256 | Err(_) => Err(ServerGroupMessageSendError::Serialization), 257 | } 258 | } 259 | 260 | /// Same as [Endpoint::broadcast_message] but will log the error instead of returning it 261 | pub fn try_broadcast_message(&mut self, message: T) { 262 | if let Err(err) = self.broadcast_message(message) { 263 | error!("try_broadcast_message: {}", err); 264 | } 265 | } 266 | 267 | /// Same as [Endpoint::broadcast_message_on] but will log the error instead of returning it 268 | pub fn try_broadcast_message_on>( 269 | &mut self, 270 | channel_id: C, 271 | message: T, 272 | ) { 273 | if let Err(err) = self.broadcast_message_on(channel_id, message) { 274 | error!("try_broadcast_message: {}", err); 275 | } 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{net::Ipv6Addr, thread::sleep, time::Duration}; 2 | 3 | use bevy::{ 4 | app::ScheduleRunnerPlugin, 5 | ecs::message::MessageReader, 6 | prelude::{App, Res, ResMut, Resource, Startup, Update}, 7 | }; 8 | use bevy_quinnet::{ 9 | client::{ 10 | self, 11 | certificate::{ 12 | CertConnectionAbortEvent, CertInteractionEvent, CertTrustUpdateEvent, 13 | CertVerificationInfo, CertVerificationStatus, CertVerifierAction, 14 | CertificateVerificationMode, 15 | }, 16 | connection::ClientAddrConfiguration, 17 | ClientConnectionConfiguration, QuinnetClient, QuinnetClientPlugin, 18 | }, 19 | server::{ 20 | self, certificate::CertificateRetrievalMode, EndpointAddrConfiguration, QuinnetServer, 21 | QuinnetServerPlugin, ServerEndpointConfiguration, 22 | }, 23 | shared::{ 24 | channels::{ChannelConfig, ChannelId}, 25 | ClientId, 26 | }, 27 | }; 28 | 29 | #[derive(Resource, Debug, Clone, Default)] 30 | pub struct ClientTestData { 31 | pub connection_events_received: u64, 32 | 33 | pub cert_trust_update_events_received: u64, 34 | pub last_trusted_cert_info: Option, 35 | 36 | pub cert_interactions_received: u64, 37 | pub last_cert_interactions_status: Option, 38 | pub last_cert_interactions_info: Option, 39 | 40 | pub cert_verif_connection_abort_events_received: u64, 41 | pub last_abort_cert_status: Option, 42 | pub last_abort_cert_info: Option, 43 | } 44 | 45 | #[derive(Resource, Debug, Clone, Default)] 46 | pub struct ServerTestData { 47 | pub connection_events_received: u64, 48 | pub last_connected_client_id: Option, 49 | pub connection_lost_events_received: u64, 50 | pub last_disconnected_client_id: Option, 51 | } 52 | 53 | #[derive(Resource, Debug, Clone, Default)] 54 | pub struct Port(u16); 55 | 56 | pub const TEST_MESSAGE_PAYLOAD: &[u8] = &[0x1, 0x2, 0x3, 0x4]; 57 | 58 | pub const SERVER_IP: Ipv6Addr = Ipv6Addr::LOCALHOST; 59 | pub const LOCAL_BIND_IP: Ipv6Addr = Ipv6Addr::UNSPECIFIED; 60 | 61 | pub fn build_client_app() -> App { 62 | let mut client_app = App::new(); 63 | client_app 64 | .add_plugins(( 65 | ScheduleRunnerPlugin::default(), 66 | QuinnetClientPlugin::default(), 67 | )) 68 | .insert_resource(ClientTestData::default()) 69 | .add_systems(Startup, start_simple_connection) 70 | .add_systems(Update, handle_client_events); 71 | client_app 72 | } 73 | 74 | pub fn build_server_app() -> App { 75 | let mut server_app = App::new(); 76 | server_app 77 | .add_plugins(( 78 | ScheduleRunnerPlugin::default(), 79 | QuinnetServerPlugin::default(), 80 | )) 81 | .insert_resource(ServerTestData::default()) 82 | .add_systems(Startup, start_listening) 83 | .add_systems(Update, handle_server_events); 84 | server_app 85 | } 86 | 87 | pub fn default_client_addr_configuration(port: u16) -> ClientAddrConfiguration { 88 | ClientAddrConfiguration::from_ips(SERVER_IP, port, LOCAL_BIND_IP, 0) 89 | } 90 | 91 | pub fn start_simple_connection(mut client: ResMut, port: Res) { 92 | client 93 | .open_connection(ClientConnectionConfiguration { 94 | addr_config: default_client_addr_configuration(port.0), 95 | cert_mode: CertificateVerificationMode::SkipVerification, 96 | defaultables: Default::default(), 97 | }) 98 | .unwrap(); 99 | } 100 | 101 | pub fn start_listening(mut server: ResMut, port: Res) { 102 | server 103 | .start_endpoint(ServerEndpointConfiguration { 104 | addr_config: EndpointAddrConfiguration::from_ip(LOCAL_BIND_IP, port.0), 105 | cert_mode: CertificateRetrievalMode::GenerateSelfSigned { 106 | server_hostname: SERVER_IP.to_string(), 107 | }, 108 | defaultables: Default::default(), 109 | }) 110 | .unwrap(); 111 | } 112 | 113 | pub fn handle_client_events( 114 | mut connection_events: MessageReader, 115 | mut cert_trust_update_events: MessageReader, 116 | mut cert_interaction_events: MessageReader, 117 | mut cert_connection_abort_events: MessageReader, 118 | mut test_data: ResMut, 119 | ) { 120 | for _connected_event in connection_events.read() { 121 | test_data.connection_events_received += 1; 122 | } 123 | for trust_update in cert_trust_update_events.read() { 124 | test_data.cert_trust_update_events_received += 1; 125 | test_data.last_trusted_cert_info = Some(trust_update.cert_info.clone()); 126 | } 127 | for cert_interaction in cert_interaction_events.read() { 128 | test_data.cert_interactions_received += 1; 129 | test_data.last_cert_interactions_status = Some(cert_interaction.status.clone()); 130 | test_data.last_cert_interactions_info = Some(cert_interaction.info.clone()); 131 | 132 | match cert_interaction.status { 133 | CertVerificationStatus::UnknownCertificate => todo!(), 134 | CertVerificationStatus::UntrustedCertificate => { 135 | cert_interaction 136 | .apply_cert_verifier_action(CertVerifierAction::AbortConnection) 137 | .expect("Failed to apply cert verification action"); 138 | } 139 | CertVerificationStatus::TrustedCertificate => todo!(), 140 | } 141 | } 142 | for connection_abort in cert_connection_abort_events.read() { 143 | test_data.cert_verif_connection_abort_events_received += 1; 144 | test_data.last_abort_cert_status = Some(connection_abort.status.clone()); 145 | test_data.last_abort_cert_info = Some(connection_abort.cert_info.clone()); 146 | } 147 | } 148 | 149 | pub fn handle_server_events( 150 | mut connection_events: MessageReader, 151 | mut connection_lost_events: MessageReader, 152 | mut test_data: ResMut, 153 | ) { 154 | for event in connection_events.read() { 155 | test_data.connection_events_received += 1; 156 | test_data.last_connected_client_id = Some(event.id); 157 | } 158 | for event in connection_lost_events.read() { 159 | test_data.connection_lost_events_received += 1; 160 | test_data.last_disconnected_client_id = Some(event.id); 161 | } 162 | } 163 | 164 | pub fn start_simple_server_app(port: u16) -> App { 165 | let mut server_app = build_server_app(); 166 | server_app.insert_resource(Port(port)); 167 | 168 | // Startup 169 | server_app.update(); 170 | server_app 171 | } 172 | 173 | pub fn start_simple_client_app(port: u16) -> App { 174 | let mut client_app = build_client_app(); 175 | client_app.insert_resource(Port(port)); 176 | 177 | // Startup 178 | client_app.update(); 179 | client_app 180 | } 181 | 182 | pub fn wait_for_client_connected(client_app: &mut App, server_app: &mut App) -> ClientId { 183 | loop { 184 | client_app.update(); 185 | server_app.update(); 186 | if client_app 187 | .world() 188 | .resource::() 189 | .is_connected() 190 | { 191 | break; 192 | } 193 | } 194 | server_app 195 | .world() 196 | .resource::() 197 | .last_connected_client_id 198 | .expect("A client should have connected") 199 | } 200 | 201 | pub fn wait_for_all_clients_disconnected(server_app: &mut App) -> ClientId { 202 | loop { 203 | server_app.update(); 204 | if server_app 205 | .world() 206 | .resource::() 207 | .endpoint() 208 | .clients() 209 | .len() 210 | == 0 211 | { 212 | break; 213 | } 214 | } 215 | server_app 216 | .world() 217 | .resource::() 218 | .last_disconnected_client_id 219 | .expect("A client should have connected") 220 | } 221 | 222 | pub fn get_default_client_channel(app: &App) -> ChannelId { 223 | let client = app.world().resource::(); 224 | client 225 | .connection() 226 | .default_channel() 227 | .expect("Expected some default channel") 228 | } 229 | 230 | pub fn get_default_server_channel(app: &App) -> ChannelId { 231 | let server = app.world().resource::(); 232 | server 233 | .endpoint() 234 | .default_channel() 235 | .expect("Expected some default channel") 236 | } 237 | 238 | pub fn close_client_channel(channel_id: ChannelId, app: &mut App) { 239 | let mut client = app.world_mut().resource_mut::(); 240 | client 241 | .connection_mut() 242 | .close_channel(channel_id) 243 | .expect("Failed to close channel") 244 | } 245 | 246 | pub fn close_server_channel(channel_id: ChannelId, app: &mut App) { 247 | let mut server = app.world_mut().resource_mut::(); 248 | server 249 | .endpoint_mut() 250 | .close_channel(channel_id) 251 | .expect("Failed to close channel") 252 | } 253 | 254 | pub fn open_client_channel(channel_type: ChannelConfig, app: &mut App) -> ChannelId { 255 | let mut client = app.world_mut().resource_mut::(); 256 | client 257 | .connection_mut() 258 | .open_channel(channel_type) 259 | .expect("Failed to open channel") 260 | } 261 | 262 | pub fn open_server_channel(channel_type: ChannelConfig, app: &mut App) -> ChannelId { 263 | let mut server = app.world_mut().resource_mut::(); 264 | server 265 | .endpoint_mut() 266 | .open_channel(channel_type) 267 | .expect("Failed to open channel") 268 | } 269 | 270 | pub fn wait_for_client_message( 271 | client_id: ClientId, 272 | channel_id: ChannelId, 273 | server_app: &mut App, 274 | ) -> bytes::Bytes { 275 | for _ in 0..20 { 276 | server_app.update(); 277 | match server_app 278 | .world_mut() 279 | .resource_mut::() 280 | .endpoint_mut() 281 | .receive_payload(client_id, channel_id) 282 | { 283 | Ok(Some(payload)) => return payload, 284 | Ok(None) => (), 285 | Err(err) => panic!("Error when receiving payload from client: {:?}", err), 286 | } 287 | sleep(Duration::from_secs_f32(0.05)); 288 | } 289 | panic!("Did not receive a message from client in time"); 290 | } 291 | 292 | pub fn wait_for_server_message(client_app: &mut App, channel_id: ChannelId) -> bytes::Bytes { 293 | for _ in 0..20 { 294 | client_app.update(); 295 | match client_app 296 | .world_mut() 297 | .resource_mut::() 298 | .connection_mut() 299 | .receive_payload(channel_id) 300 | { 301 | Ok(Some(payload)) => return payload, 302 | Ok(None) => (), 303 | Err(err) => panic!("Error when receiving payload from server: {:?}", err), 304 | } 305 | sleep(Duration::from_secs_f32(0.05)); 306 | } 307 | panic!("Did not receive a message from server in time"); 308 | } 309 | 310 | pub fn send_and_test_client_message( 311 | client_id: ClientId, 312 | channel_id: ChannelId, 313 | client_app: &mut App, 314 | server_app: &mut App, 315 | msg_counter: &mut u64, 316 | ) { 317 | *msg_counter += 1; 318 | let client_message_payload = bytes::Bytes::from(vec![client_id as u8, *msg_counter as u8]); 319 | 320 | let mut client = client_app.world_mut().resource_mut::(); 321 | client 322 | .connection_mut() 323 | .send_payload_on(channel_id, client_message_payload.clone()) 324 | .unwrap(); 325 | 326 | let server_received = wait_for_client_message(client_id, channel_id, server_app); 327 | assert_eq!(client_message_payload, server_received); 328 | } 329 | 330 | pub fn send_and_test_server_message( 331 | client_id: ClientId, 332 | channel_id: ChannelId, 333 | server_app: &mut App, 334 | client_app: &mut App, 335 | msg_counter: &mut u64, 336 | ) { 337 | *msg_counter += 1; 338 | let server_message_payload = bytes::Bytes::from(vec![client_id as u8, *msg_counter as u8]); 339 | 340 | let mut server = server_app.world_mut().resource_mut::(); 341 | server 342 | .endpoint_mut() 343 | .send_payload_on(client_id, channel_id, server_message_payload.clone()) 344 | .unwrap(); 345 | 346 | let client_received = wait_for_server_message(client_app, channel_id); 347 | assert_eq!(server_message_payload, client_received); 348 | } 349 | -------------------------------------------------------------------------------- /tests/certificates.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, path::Path, thread::sleep, time::Duration}; 2 | 3 | use bevy::{ 4 | app::ScheduleRunnerPlugin, 5 | prelude::{App, Update}, 6 | }; 7 | use bevy_quinnet::{ 8 | client::{ 9 | certificate::{CertVerificationStatus, CertificateVerificationMode, TrustOnFirstUseConfig}, 10 | ClientConnectionConfiguration, QuinnetClient, QuinnetClientPlugin, 11 | DEFAULT_KNOWN_HOSTS_FILE, 12 | }, 13 | server::{ 14 | certificate::CertificateRetrievalMode, EndpointAddrConfiguration, QuinnetServer, 15 | QuinnetServerPlugin, ServerEndpointConfiguration, 16 | }, 17 | }; 18 | 19 | // https://github.com/rust-lang/rust/issues/46379 20 | pub use utils::*; 21 | 22 | mod utils; 23 | 24 | /////////////////////////////////////////////////////////// 25 | /// /// 26 | /// Test /// 27 | /// /// 28 | /////////////////////////////////////////////////////////// 29 | 30 | const TEST_CERT_FILE: &str = "assets/tests/cert.pem.test"; 31 | const TEST_KEY_FILE: &str = "assets/tests/key.pem.test"; 32 | const TEST_CERT_FINGERPRINT_B64: &str = "sieQJ9J6DIrQP37HAlUFk2hYhLZDY9G5OZQpqzkWlKo="; 33 | 34 | #[test] 35 | fn trust_on_first_use() { 36 | // TOFU With default parameters 37 | // Server listens with a cert loaded from a file 38 | // Client connects with empty cert store 39 | // -> The server's certificate is treatead as Unknown by the client, which stores it and continues the connection 40 | // Clients disconnects 41 | // Client reconnects with the updated cert store 42 | // -> The server's certificate is treatead as Trusted by the client, which continues the connection 43 | // Clients disconnects 44 | // Server reboots, and generates a new self-signed certificate 45 | // Client reconnects with its cert store 46 | // -> The server's certificate is treatead as Untrusted by the client, which requests a client action 47 | // We receive the client action request and ask to abort the connection 48 | 49 | let port = 6004; // TODO Use port 0 and retrieve the port used by the server. 50 | 51 | if Path::new(DEFAULT_KNOWN_HOSTS_FILE).exists() { 52 | fs::remove_file(DEFAULT_KNOWN_HOSTS_FILE) 53 | .expect("Failed to remove default known hosts file"); 54 | } 55 | 56 | let mut client_app = App::new(); 57 | client_app 58 | .add_plugins(( 59 | ScheduleRunnerPlugin::default(), 60 | QuinnetClientPlugin::default(), 61 | )) 62 | .insert_resource(ClientTestData::default()) 63 | .add_systems(Update, handle_client_events); 64 | 65 | let mut server_app = App::new(); 66 | server_app 67 | .add_plugins(( 68 | ScheduleRunnerPlugin::default(), 69 | QuinnetServerPlugin::default(), 70 | )) 71 | .insert_resource(ServerTestData::default()) 72 | .add_systems(Update, handle_server_events); 73 | 74 | // Startup 75 | client_app.update(); 76 | server_app.update(); 77 | 78 | // Client connects with empty cert store 79 | { 80 | let mut client = client_app.world_mut().resource_mut::(); 81 | client 82 | .open_connection(ClientConnectionConfiguration { 83 | addr_config: default_client_addr_configuration(port), 84 | cert_mode: CertificateVerificationMode::TrustOnFirstUse( 85 | TrustOnFirstUseConfig::default(), 86 | ), 87 | // cert_mode: CertificateVerificationMode::SkipVerification, 88 | defaultables: Default::default(), 89 | }) 90 | .unwrap(); 91 | } 92 | 93 | // Server listens with a cert loaded from a file 94 | { 95 | let mut server = server_app.world_mut().resource_mut::(); 96 | let server_cert = server 97 | .start_endpoint(ServerEndpointConfiguration { 98 | addr_config: EndpointAddrConfiguration::from_ip(LOCAL_BIND_IP, port), 99 | cert_mode: CertificateRetrievalMode::LoadFromFile { 100 | cert_file: TEST_CERT_FILE.to_string(), 101 | key_file: TEST_KEY_FILE.to_string(), 102 | }, 103 | defaultables: Default::default(), 104 | }) 105 | .unwrap(); 106 | assert_eq!( 107 | TEST_CERT_FINGERPRINT_B64.to_string(), 108 | server_cert.fingerprint.to_base64(), 109 | "The loaded cert fingerprint should match the known test fingerprint" 110 | ); 111 | } 112 | 113 | assert!( 114 | client_app 115 | .world() 116 | .resource::() 117 | .get_connection() 118 | .is_some(), 119 | "The default connection should exist" 120 | ); 121 | let server = server_app.world().resource::(); 122 | assert!(server.is_listening(), "The server should be listening"); 123 | 124 | // Let the async runtime connection connect. 125 | wait_for_client_connected(&mut client_app, &mut server_app); 126 | 127 | // The server's certificate is treatead as Unknown by the client, which stores it and continues the connection 128 | { 129 | let mut client_test_data = client_app.world_mut().resource_mut::(); 130 | assert_eq!( 131 | client_test_data.cert_trust_update_events_received, 1, 132 | "The client should have received exactly 1 certificate trust update event" 133 | ); 134 | let cert_info = client_test_data 135 | .last_trusted_cert_info 136 | .as_mut() 137 | .expect("A certificate trust update should have happened"); 138 | assert_eq!( 139 | cert_info.fingerprint.to_base64(), 140 | TEST_CERT_FINGERPRINT_B64.to_string(), 141 | "The certificate rceived by the client should match the known test certificate" 142 | ); 143 | assert!( 144 | cert_info.known_fingerprint.is_none(), 145 | "The client should not have any previous certificate fingerprint for this server" 146 | ); 147 | assert_eq!( 148 | cert_info.server_name.to_string(), 149 | SERVER_IP.to_string(), 150 | "The server name should match the one we configured" 151 | ); 152 | 153 | let mut client = client_app.world_mut().resource_mut::(); 154 | assert!( 155 | client.is_connected(), 156 | "The default connection should be connected to the server" 157 | ); 158 | 159 | // Clients disconnects 160 | // Client reconnects with the updated cert store 161 | client.close_all_connections(); 162 | 163 | client 164 | .open_connection(ClientConnectionConfiguration { 165 | addr_config: default_client_addr_configuration(port), 166 | cert_mode: CertificateVerificationMode::TrustOnFirstUse( 167 | TrustOnFirstUseConfig::default(), 168 | ), 169 | defaultables: Default::default(), 170 | }) 171 | .unwrap(); 172 | } 173 | 174 | // Let the async runtime connection connect. 175 | wait_for_client_connected(&mut client_app, &mut server_app); 176 | 177 | { 178 | assert!( 179 | client_app 180 | .world_mut() 181 | .resource_mut::() 182 | .is_connected(), 183 | "The default connection should be connected to the server" 184 | ); 185 | 186 | let client_test_data = client_app.world().resource::(); 187 | assert_eq!(client_test_data.cert_trust_update_events_received, 1, "The client should still have only 1 certificate trust update event after his reconnection"); 188 | 189 | // Clients disconnects 190 | client_app 191 | .world_mut() 192 | .resource_mut::() 193 | .close_all_connections(); 194 | } 195 | 196 | // Server reboots, and generates a new self-signed certificate 197 | server_app 198 | .world_mut() 199 | .resource_mut::() 200 | .stop_endpoint() 201 | .unwrap(); 202 | 203 | // Let the endpoint fully stop. 204 | sleep(Duration::from_secs_f32(0.2)); 205 | 206 | let server_cert = server_app 207 | .world_mut() 208 | .resource_mut::() 209 | .start_endpoint(ServerEndpointConfiguration { 210 | addr_config: EndpointAddrConfiguration::from_ip(LOCAL_BIND_IP, port), 211 | cert_mode: CertificateRetrievalMode::GenerateSelfSigned { 212 | server_hostname: SERVER_IP.to_string(), 213 | }, 214 | defaultables: Default::default(), 215 | }) 216 | .unwrap(); 217 | 218 | // Client reconnects with its cert store containing the previously store certificate fingerprint 219 | { 220 | let mut client = client_app.world_mut().resource_mut::(); 221 | client 222 | .open_connection(ClientConnectionConfiguration { 223 | addr_config: default_client_addr_configuration(port), 224 | cert_mode: CertificateVerificationMode::TrustOnFirstUse( 225 | TrustOnFirstUseConfig::default(), 226 | ), 227 | defaultables: Default::default(), 228 | }) 229 | .unwrap(); 230 | } 231 | 232 | // Let the async runtime connection connect. 233 | sleep(Duration::from_secs_f32(0.1)); 234 | 235 | // Connection & event propagation: certificate interaction event 236 | server_app.update(); 237 | client_app.update(); 238 | 239 | // Let the async runtime process the certificate action & connection. 240 | sleep(Duration::from_secs_f32(0.1)); 241 | 242 | // Connection abort event 243 | client_app.update(); 244 | 245 | // The server's certificate is treatead as Untrusted by the client, which requests a client action 246 | // We received the client action request and asked to abort the connection 247 | { 248 | let mut client_test_data = client_app.world_mut().resource_mut::(); 249 | assert_eq!( 250 | client_test_data.cert_interactions_received, 1, 251 | "The client should have received exactly 1 certificate interaction event" 252 | ); 253 | assert_eq!( 254 | client_test_data.cert_verif_connection_abort_events_received, 1, 255 | "The client should have received exactly 1 certificate connection abort event" 256 | ); 257 | 258 | // Verify the cert info in the certificate interaction event 259 | let interaction_cert_info = client_test_data 260 | .last_cert_interactions_info 261 | .as_mut() 262 | .expect( 263 | "A certificate interaction event should have happened during certificate verification", 264 | ); 265 | assert_eq!( 266 | interaction_cert_info.fingerprint.to_base64(), 267 | server_cert.fingerprint.to_base64(), 268 | "The fingerprint received by the client should match the one generated by the server" 269 | ); 270 | // Verify the known fingerprint 271 | assert_eq!( 272 | interaction_cert_info 273 | .known_fingerprint 274 | .as_mut() 275 | .expect("There should be a known fingerprint in the store") 276 | .to_base64(), 277 | TEST_CERT_FINGERPRINT_B64.to_string(), 278 | "The previously known fingeprint for this server should be the test fingerprint" 279 | ); 280 | assert_eq!( 281 | interaction_cert_info.server_name.to_string(), 282 | SERVER_IP.to_string(), 283 | "The server name in the certificate interaction event should be the server we want to connect to" 284 | ); 285 | assert_eq!( 286 | client_test_data.last_cert_interactions_status, 287 | Some(CertVerificationStatus::UntrustedCertificate), 288 | "The certificate verification status in the certificate interaction event should be `Untrusted`" 289 | ); 290 | 291 | // Verify the cert info in the connection abort event 292 | assert_eq!( 293 | client_test_data.last_abort_cert_info, 294 | client_test_data.last_cert_interactions_info, 295 | "The certificate info in the connection abort event should match those of the certificate interaction event" 296 | ); 297 | assert_eq!( 298 | client_test_data.last_abort_cert_status, 299 | Some(CertVerificationStatus::UntrustedCertificate), 300 | "The certificate verification status in the connection abort event should be `Untrusted`" 301 | ); 302 | 303 | let client = client_app.world().resource::(); 304 | assert!( 305 | client.is_connected() == false, 306 | "The default connection should not be connected to the server" 307 | ); 308 | } 309 | 310 | // Leave the workspace clean 311 | fs::remove_file(DEFAULT_KNOWN_HOSTS_FILE).expect("Failed to remove default known hosts file"); 312 | } 313 | -------------------------------------------------------------------------------- /src/shared/peer_connection.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | use bevy::log::error; 4 | use bytes::Bytes; 5 | use tokio::sync::{ 6 | broadcast, 7 | mpsc::{ 8 | self, 9 | error::{TryRecvError, TrySendError}, 10 | }, 11 | }; 12 | 13 | use crate::shared::{ 14 | channels::{ 15 | Channel, ChannelAsyncMessage, ChannelConfig, ChannelId, ChannelSyncMessage, CloseReason, 16 | CloseSend, 17 | }, 18 | error::{ 19 | AsyncChannelError, ChannelCloseError, ChannelCreationError, ConnectionAlreadyClosed, 20 | ConnectionSendError, 21 | }, 22 | DEFAULT_KILL_MESSAGE_QUEUE_SIZE, DEFAULT_MESSAGE_QUEUE_SIZE, 23 | }; 24 | 25 | #[cfg(feature = "recv_channels")] 26 | mod recv_channels; 27 | 28 | #[cfg(feature = "recv_channels")] 29 | pub use recv_channels::*; 30 | 31 | pub(crate) type PayloadSend = mpsc::Sender<(ChannelId, Bytes)>; 32 | pub(crate) type PayloadRecv = mpsc::Receiver<(ChannelId, Bytes)>; 33 | pub(crate) type ChannelAsyncMsgSend = mpsc::Sender; 34 | pub(crate) type ChannelAsyncMsgRecv = mpsc::Receiver; 35 | pub(crate) type ChannelSyncMsgSend = mpsc::Sender; 36 | pub(crate) type ChannelSyncMsgRecv = mpsc::Receiver; 37 | 38 | pub(crate) struct ChannelsIdsPool { 39 | /// Internal ordered pool of available channel ids 40 | available_channel_ids: BTreeSet, 41 | 42 | /// Default channel id 43 | default_channel: Option, 44 | } 45 | 46 | impl ChannelsIdsPool { 47 | pub(crate) fn new() -> Self { 48 | let available_channel_ids = (0..=u8::MAX).collect(); 49 | Self { 50 | available_channel_ids, 51 | default_channel: None, 52 | } 53 | } 54 | 55 | /// Allocates a new channel id from the pool 56 | /// 57 | /// Returns an error if no channel ids are available 58 | pub(crate) fn take_id(&mut self) -> Result { 59 | match self.available_channel_ids.pop_first() { 60 | Some(channel_id) => { 61 | if self.default_channel.is_none() { 62 | self.default_channel = Some(channel_id); 63 | } 64 | Ok(channel_id) 65 | } 66 | None => Err(ChannelCreationError::MaxChannelsCountReached), 67 | } 68 | } 69 | 70 | /// Releases a previously allocated channel id back to the pool 71 | pub(crate) fn release_id(&mut self, channel_id: ChannelId) { 72 | self.available_channel_ids.insert(channel_id); 73 | if self.default_channel == Some(channel_id) { 74 | self.default_channel = None; 75 | } 76 | } 77 | 78 | /// Sets the default send channel id 79 | #[inline(always)] 80 | pub(crate) fn set_default_channel(&mut self, channel_id: ChannelId) { 81 | self.default_channel = Some(channel_id); 82 | } 83 | 84 | /// Gets the default send channel id 85 | #[inline(always)] 86 | pub(crate) fn default_channel(&self) -> Option { 87 | self.default_channel 88 | } 89 | } 90 | 91 | /// A connection to a peer, containing multiple channels for sending data, as well as buffers for received data. 92 | pub struct PeerConnection { 93 | pub(crate) specific: S, 94 | /// Send channels opened on this connection 95 | send_channels: Vec>, 96 | /// Internal queue of received bytes from the peer 97 | bytes_from_peer_recv: mpsc::Receiver<(ChannelId, Bytes)>, 98 | /// Sender for internal quinnet messages going to the async channels task 99 | to_channels_send: mpsc::Sender, 100 | /// Receiver for internal quinnet messages coming from the async channels task 101 | from_channels_recv: mpsc::Receiver, 102 | close_send: broadcast::Sender, 103 | stats: ConnectionStats, 104 | 105 | /// Buffers of received payloads per receive channel 106 | #[cfg(feature = "recv_channels")] 107 | recv_channels_payloads: Vec>, 108 | /// Parameters for the connection 109 | #[cfg(feature = "recv_channels")] 110 | recv_channels_cfg: RecvChannelsConfiguration, 111 | } 112 | 113 | impl PeerConnection { 114 | pub(crate) fn new( 115 | specific: S, 116 | bytes_from_peer_recv: mpsc::Receiver<(ChannelId, Bytes)>, 117 | close_send: broadcast::Sender, 118 | from_channels_recv: mpsc::Receiver, 119 | to_channels_send: mpsc::Sender, 120 | #[cfg(feature = "recv_channels")] recv_channels_cfg: RecvChannelsConfiguration, 121 | ) -> Self { 122 | Self { 123 | #[cfg(feature = "recv_channels")] 124 | recv_channels_payloads: Vec::with_capacity( 125 | recv_channels_cfg.max_receive_channels_count, 126 | ), 127 | #[cfg(feature = "recv_channels")] 128 | recv_channels_cfg, 129 | 130 | specific, 131 | send_channels: Vec::new(), 132 | bytes_from_peer_recv, 133 | close_send, 134 | to_channels_send, 135 | from_channels_recv, 136 | stats: ConnectionStats::default(), 137 | } 138 | } 139 | 140 | pub(crate) fn internal_send_payload( 141 | &mut self, 142 | channel_id: ChannelId, 143 | payload: Bytes, 144 | ) -> Result<(), ConnectionSendError> { 145 | match self.send_channels.get(channel_id as usize) { 146 | Some(Some(channel)) => { 147 | self.stats.sent(payload.len()); 148 | Ok(channel.send_payload(payload)?) 149 | } 150 | Some(None) => Err(ConnectionSendError::ChannelClosed), 151 | None => Err(ConnectionSendError::InvalidChannelId(channel_id)), 152 | } 153 | } 154 | 155 | /// This method can be used to dequeue a received payload from the internal buffer of received bytes from the peer. 156 | /// Returns: 157 | /// - the [ChannelId] the payload was received on, and the payload itself, as [Ok] if any 158 | /// - a [TryRecvError::Empty] if there is no payload to dequeue 159 | /// 160 | /// If using the `recv_channels` feature, you should probably **not call** this method as it is already automatically called by the plugins during each Update. If not using the `recv_channels` feature, this is the only way to get received payloads on a [PeerConnection]. 161 | pub fn dequeue_undispatched_bytes_from_peer( 162 | &mut self, 163 | ) -> Result<(ChannelId, Bytes), TryRecvError> { 164 | let (channel_id, payload) = self.bytes_from_peer_recv.try_recv()?; 165 | self.stats.received(payload.len()); 166 | Ok((channel_id, payload)) 167 | } 168 | 169 | /// Immediately prevents new messages from being sent on the channel and signal the channel to closes all its background tasks. 170 | /// Before trully closing, the channel will wait for all buffered messages to be properly sent according to the channel type. 171 | /// Can fail if the [ChannelId] is unknown, or if the channel is already closed. 172 | pub(crate) fn internal_close_channel( 173 | &mut self, 174 | channel_id: ChannelId, 175 | ) -> Result<(), ChannelCloseError> { 176 | if (channel_id as usize) < self.send_channels.len() { 177 | match self.send_channels[channel_id as usize].take() { 178 | Some(channel_to_close) => channel_to_close.close(), 179 | None => Err(ChannelCloseError::ChannelAlreadyClosed), 180 | } 181 | } else { 182 | Err(ChannelCloseError::InvalidChannelId(channel_id)) 183 | } 184 | } 185 | 186 | pub(crate) fn create_connection_channel( 187 | &mut self, 188 | id: ChannelId, 189 | channel_config: ChannelConfig, 190 | ) -> Result<(), AsyncChannelError> { 191 | let channel = self.create_unregistered_connection_channel(id, channel_config)?; 192 | self.register_connection_channel(channel); 193 | Ok(()) 194 | } 195 | 196 | pub(crate) fn create_unregistered_connection_channel( 197 | &mut self, 198 | id: ChannelId, 199 | config: ChannelConfig, 200 | ) -> Result { 201 | let (bytes_to_channel_send, bytes_to_channel_recv) = 202 | mpsc::channel::(DEFAULT_MESSAGE_QUEUE_SIZE); 203 | let (channel_close_send, channel_close_recv) = 204 | mpsc::channel(DEFAULT_KILL_MESSAGE_QUEUE_SIZE); 205 | 206 | match self 207 | .to_channels_send 208 | .try_send(ChannelSyncMessage::CreateChannel { 209 | id, 210 | config, 211 | bytes_to_channel_recv, 212 | channel_close_recv, 213 | }) { 214 | Ok(_) => Ok(Channel::new(id, bytes_to_channel_send, channel_close_send)), 215 | Err(err) => match err { 216 | TrySendError::Full(_) => Err(AsyncChannelError::FullQueue), 217 | TrySendError::Closed(_) => Err(AsyncChannelError::InternalChannelClosed), 218 | }, 219 | } 220 | } 221 | 222 | pub(crate) fn register_connection_channel(&mut self, channel: Channel) { 223 | let channel_index = channel.id() as usize; 224 | if channel_index < self.send_channels.len() { 225 | self.send_channels[channel_index] = Some(channel); 226 | } else { 227 | self.send_channels 228 | .extend((self.send_channels.len()..channel_index).map(|_| None)); 229 | self.send_channels.push(Some(channel)); 230 | } 231 | } 232 | 233 | /// Signal the connection to closes all its background tasks. Before trully closing, the connection will wait for all buffered messages in all its opened channels to be properly sent according to their respective channel type. 234 | pub(crate) fn close(&mut self, reason: CloseReason) -> Result<(), ConnectionAlreadyClosed> { 235 | match self.close_send.send(reason) { 236 | Ok(_) => Ok(()), 237 | Err(_) => { 238 | // The only possible error for a send is that there is no active receivers, meaning that the tasks are already terminated. 239 | Err(ConnectionAlreadyClosed) 240 | } 241 | } 242 | } 243 | 244 | #[inline(always)] 245 | pub(crate) fn try_recv_from_channels(&mut self) -> Result { 246 | self.from_channels_recv.try_recv() 247 | } 248 | 249 | /// Returns a mutable reference to the connection statistics 250 | pub fn stats_mut(&mut self) -> &mut ConnectionStats { 251 | &mut self.stats 252 | } 253 | 254 | /// Returns a reference to the connection statistics 255 | pub fn stats(&self) -> &ConnectionStats { 256 | &self.stats 257 | } 258 | 259 | pub(crate) fn try_close(&mut self, reason: CloseReason) { 260 | match &self.close(reason) { 261 | Ok(_) => (), 262 | Err(err) => error!("Failed to properly close clonnection: {}", err), 263 | } 264 | } 265 | 266 | pub(crate) fn internal_reset( 267 | &mut self, 268 | close_send: CloseSend, 269 | to_channels_send: ChannelSyncMsgSend, 270 | from_channels_recv: mpsc::Receiver, 271 | bytes_from_peer_recv: PayloadRecv, 272 | send_channels_capacity: usize, 273 | ) { 274 | self.close_send = close_send; 275 | self.to_channels_send = to_channels_send; 276 | self.from_channels_recv = from_channels_recv; 277 | self.bytes_from_peer_recv = bytes_from_peer_recv; 278 | self.send_channels = Vec::with_capacity(send_channels_capacity); 279 | self.stats_mut().reset(); 280 | 281 | #[cfg(feature = "recv_channels")] 282 | self.recv_channels_payloads.clear(); 283 | } 284 | } 285 | 286 | /// Basic quinnet stats about a connection 287 | #[derive(Default)] 288 | pub struct ConnectionStats { 289 | received_bytes_count: u64, 290 | sent_bytes_count: u64, 291 | received_messages_count: u64, 292 | } 293 | impl ConnectionStats { 294 | /// Returns how many bytes were received on this connection since the last time it was cleared 295 | pub fn received_bytes_count(&self) -> u64 { 296 | self.received_bytes_count 297 | } 298 | 299 | /// Returns how many bytes were sent on this connection since the last time it was cleared 300 | pub fn sent_bytes_count(&self) -> u64 { 301 | self.sent_bytes_count 302 | } 303 | 304 | /// Returns how many messages were received on this connection since the last time it was cleared 305 | pub fn received_messages_count(&self) -> u64 { 306 | self.received_messages_count 307 | } 308 | 309 | /// Resets all statistics (received/sent bytes and received messages count) to 0 310 | pub fn reset(&mut self) { 311 | self.received_bytes_count = 0; 312 | self.sent_bytes_count = 0; 313 | self.received_messages_count = 0; 314 | } 315 | 316 | /// Returns how many bytes were received on this connection since the last time it was cleared and reset this value to 0 317 | pub fn clear_received_bytes_count(&mut self) -> u64 { 318 | let bytes_count = self.received_bytes_count; 319 | self.received_bytes_count = 0; 320 | bytes_count 321 | } 322 | 323 | /// Returns how many bytes were received on this connection since the last time it was cleared and reset this value to 0 324 | pub fn clear_sent_bytes_count(&mut self) -> u64 { 325 | let bytes_count = self.sent_bytes_count; 326 | self.sent_bytes_count = 0; 327 | bytes_count 328 | } 329 | 330 | /// Returns how many messages were received on this connection since the last time it was cleared and reset this value to 0 331 | pub fn clear_received_messages_count(&mut self) -> u64 { 332 | let messages_count = self.received_messages_count; 333 | self.received_messages_count = 0; 334 | messages_count 335 | } 336 | 337 | fn received(&mut self, bytes_count: usize) { 338 | self.received_bytes_count += bytes_count as u64; 339 | self.received_messages_count += 1; 340 | } 341 | 342 | fn sent(&mut self, bytes_count: usize) { 343 | self.sent_bytes_count += bytes_count as u64; 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 0.19.0 (2025-10-14) 4 | 5 | - Updated `bevy` to 0.17 6 | 7 | ## Version 0.18.1 (2025-10-14) 8 | 9 | - Fixed: `receive_message_from` in server `Endpoint` when feature `bincode-messages` is enabled 10 | 11 | ## Version 0.18.0 (2025-10-13) 12 | 13 | - Added `recv_channels` feature: 14 | With the feature enabled, client and server buffer received payloads per channel, and allow fetching payloads from a specific channel. Buffers can be cleared automatically every frame via `RecvChannelsConfiguration::clear_stale_received_payloads` or manually by draining them or by calling `Endpoint::clear_payloads_from_clients`/`PeerConnection::clear_received_payloads`. 15 | - Added `RecvChannelError` enum and `RecvChannelErrorEvent` bevy Event 16 | - All `receive_payload_...` methods are now gated behind this feature. To retrieve received payloads without this feature enabled, the only function that can be called is `dequeue_undispatched_bytes_from_peer` 17 | 18 | 19 | - Added `bincode-messages` feature: 20 | - Added a new `bincode-messages` cargo feature, disabled by default 21 | - `bincode` and `serde` dependencies are now optional and enabled by this feature 22 | - Updated `bincode` from 1.0 to 2.0 23 | - Gated all `send_message_`/`receive_message_` methods behind this feature in both server & client 24 | 25 | 26 | - Renamed `QuinnetSyncUpdate` to `QuinnetSyncPreUpdate` 27 | - Added `QuinnetSyncLast` 28 | - Refactored `ClientSideConnection` & `ServerSideConnection` to use a common `PeerConnection` 29 | - Refactored some errors types 30 | - Added `PeerConnection::stats` & `PeerConnection::stats_mut` 31 | - Added `PeerConnection::clear_received_payloads` fn 32 | - Renamed `ChannelKind` to `ChannelConfig`, `ChannelsConfiguration` to `SendChannelsConfiguration` and `SendChannelsConfiguration::from_types` to `SendChannelsConfiguration::from_configs` 33 | - Added `default_ordered_reliable`, `default_unreliable`, `default_unordered_reliable` helpers 34 | 35 | #### Client 36 | - Renamed `ClientEndpointConfiguration` to `ClientAddrConfiguration` 37 | - Added `ClientConnectionConfiguration` as parameter to `open_connection` 38 | - Renamed `update_sync_client` to `handle_client_events` 39 | - Added `clear_stale_received_payloads` and `dispatch_received_payloads` systems 40 | - Renamed `ClientSideConnection::connection_stats` to `ClientSideConnection::quinn_connection_stats` 41 | 42 | #### Server 43 | - Renamed previous `ServerEndpointConfiguration` to `EndpointAddrConfiguration` 44 | - Added a new `ServerEndpointConfiguration` as parameter to `start_endpoint` 45 | - Moved `ServerSideConnection` and `Endpoint` to their own submodule 46 | - Renamed `update_sync_server` to `handle_server_events` 47 | - Added `clear_stale_received_payloads` and `dispatch_received_payloads` systems 48 | - Added `Endpoint::clear_payloads_from_clients` fn 49 | - Renamed `ServerSideConnection::connection_stats` to `ServerSideConnection::quinn_connection_stats` 50 | - Removed `received_message_count` from `EndpointStats` 51 | 52 | ## Version 0.17.0 (2025-04-27) 53 | 54 | - Updated `bevy` to 0.16 55 | - Reworked error handling in the sync client & server. 56 | - Multi-target methods (`send_group_...`, `broadcast_...`, ...) now properly try to do the specified task for each target before returning. If any, errors will be collected and all returned. 57 | - Removed `QuinnetError` type 58 | - Added new error types : 59 | - in the `client:error` module: 60 | - `ClientSendError` 61 | - `ClientPayloadSendError` 62 | - `ClientMessageSendError` 63 | - `ClientMessageReceiveError` 64 | - `ConnectionClosed` 65 | - `ClientConnectionCloseError` 66 | - `InvalidHostFile` 67 | - `CertificateInteractionError` 68 | - in the `server::error` module: 69 | - `ServerSendError` 70 | - `ServerPayloadSendError` 71 | - `ServerMessageSendError` 72 | - `ServerGroupSendError` 73 | - `ServerGroupPayloadSendError` 74 | - `ServerGroupMessageSendError` 75 | - `ServerMessageReceiveError` 76 | - `ServerReceiveError` 77 | - `ServerDisconnectError` 78 | - `EndpointAlreadyClosed` 79 | - `EndpointStartError` 80 | - `EndpointCertificateError` 81 | - `EndpointConnectionAlreadyClosed` 82 | - in the `shared::error` module: 83 | - `AsyncChannelError` 84 | - `ChannelCloseError` 85 | - `ChannelCreationError` 86 | - `ChannelConfigError` 87 | - Added new method variants to `Endpoint`: 88 | - `send_group_payload` 89 | - `try_send_group_payload` 90 | - `send_group_payload_on` 91 | - `try_send_group_payload_on` 92 | 93 | ## Version 0.16.0 (2025-03-24) 94 | 95 | - Renamed `ChannelType` to `ChannelKind` 96 | - Added `max_frame_size` configuration value to `ChannelKind::OrderedReliable` and `ChannelKind::UnorderedReliable` 97 | 98 | ## Version 0.15.0 (2025-03-13) 99 | 100 | - Added `max_datagram_size` method to `ServerSideConnection` and `ClientSideConnection` 101 | 102 | ## Version 0.14.0 (2025-01-16) 103 | 104 | - Updated `rustls-platform-verifier` from 0.4 to 0.5 105 | - Client: 106 | - Added `Debug`, `Clone` and `Copy` derives to client events by @florianfelix 107 | - Server: 108 | - Changed `start_endpoint` to return an error (instead of a panic in the async task) if the socket binding fails (thanks to @NonbinaryCoder) 109 | 110 | ## Version 0.13.0 (2024-12-02) 111 | 112 | - Renamed client `Connection` to `ClientSideConnection` and server `ClientConnection` to `ServerSideConnection` 113 | - Added `get_connection` and `get_connection_mut` on `Endpoint` to retrieve a `ServerSideConnection` 114 | - Added `connection_stats` to `ServerSideConnection` 115 | - Renamed `connection_stats` on `Endpoint` to `get_connection_stats` 116 | - Added `received_bytes_count`, `clear_received_bytes_count`, `sent_bytes_count` and `clear_sent_bytes_count` to both `ClientSideConnection` and `ServerSideConnection` 117 | - Changed `try_send_payload`, `try_send_payload_on`, `send_payload_on`, `send_payload`, `send_message`, `send_message_on`, `try_send_message`, `try_send_message_on` on `ClientSideConnection` to take `&mut self` 118 | - Changed `..._send_message_...`, `..._send_group_message_...`, `..._broadcast_...` methods on `Endpoint` to take `&mut self` 119 | 120 | ## Version 0.12.0 (2024-12-01) 121 | 122 | - Updated `bevy` to 0.15 123 | 124 | ## Version 0.11.0 (2024-11-30) 125 | 126 | - Updated `rustls` to 0.23 and `quinn` to 0.11 (thanks to [Cyannide](https://github.com/Cyannide), PR [#28](https://github.com/Henauxg/bevy_quinnet/pull/28)) 127 | - Changed doc & examples to use IPv6 by default (thanks to [MyZeD](https://github.com/MyZeD), PR [#29](https://github.com/Henauxg/bevy_quinnet/pull/29)) 128 | - Changed some errors logs to be warnnings 129 | - Fixed: channels send tasks won't try to flush anymore if we know that the peer connection is closed/lost: less warnings logs emitted. 130 | 131 | ## Version 0.10.0 (2024-09-09) 132 | 133 | - Added `client` & `server` features. 134 | 135 | ## Version 0.9.0 (2024-07-05) 136 | 137 | - Update to use Bevy 0.14 138 | 139 | ## Version 0.9.0 (2024-07-05) 140 | 141 | - Update to use Bevy 0.14 142 | 143 | ## Version 0.8.0 (2024-05-12) 144 | 145 | - Added a new crate `bevy_replicon_quinnet` with tests and examples, providing an integration of bevy_quinnet as a replicon back-end. 146 | - Added a `shared-client-id` cargo feature: server sends the client id to the client, client wait for it before being “connected” 147 | - Added #![warn(missing_docs)] 148 | - Channels: 149 | - Changed `ChannelId` to be a `u8` 150 | - Some channels can be preconfigured to be opened on a client or server connection by using `ChannelsConfiguration` 151 | - Channels payloads now contain the `channel_id` 152 | - When receiving a message, the message's `channel_id` is now available 153 | - You can now have only up to 256 channels opened simultaneously 154 | - You can now have more than 1 `Unreliable` or `UnorderedReliable` channel 155 | - Client: 156 | - Renamed `Client` to `QuinnetClient` 157 | - In `QuinnetClient::Connection` 158 | - Changed `disconnect` function to be `pub` (was previously only accessible through `Client::close_connection`) 159 | - Added `reconnect` function 160 | - Added a new bevy event `ConnectionFailedEvent` raised when a connection fails 161 | - Added a `QuinnetConnectionError` type 162 | - Renamed `ConnectionId` to `ConnectionLocalId` 163 | - State: 164 | - Removed `is_connected` 165 | - Renamed internal `ConnectionState` to `InternalConnectionState` 166 | - Added a new `ConnectionState` 167 | - Added `state` function 168 | - Changed `update_sync_client` system to be `pub` 169 | - Added `client_connecting`, `client_connected`, `client_just_connected` and `client_just_disconnected` ergonomic system conditions 170 | - Server: 171 | - Renamed `Server` to `QuinnetServer` 172 | - Added `Debug`, `Copy`, `Clone` traits to the server's bevy events 173 | - Changed `update_sync_server` system to be `pub` 174 | - Added `server_listening`, `server_just_opened` and `server_just_closed` ergonomic system conditions 175 | - Removed unnecessary `Clone` requirement from broadcast methods 176 | - Tests: 177 | - Updated to use the new channel API 178 | - Added a reconnection test 179 | - Moved `QuinnetError` to `shared::error` 180 | 181 | ## Version 0.7.0 (2024-02-18) 182 | 183 | - Update plugins, tests & examples to use Bevy 0.13 184 | - Update Tokio to 1.36 185 | - Update Bytes to 1.5 186 | - Update ring to 0.17.7 187 | - Update rcgen to 0.12.1 188 | - Fix breakout example UI (by [protofarer](https://github.com/protofarer)) 189 | - Fix minor code documentation 190 | 191 | ## Version 0.6.0 (2023-11-04) 192 | 193 | - Update plugins, tests & examples to use Bevy 0.12 194 | 195 | ## Version 0.5.0 (2023-07-11) 196 | 197 | - Update plugins, tests & examples to use Bevy 0.11 198 | - Update quinn & rustls 199 | - Update other dependencies: tokio, bytes, rcgen 200 | - [example:breakout] Reduce the collision sound volume 201 | 202 | ## Version 0.4.0 (2023-03-07) 203 | 204 | - Update Bevy to 0.10 205 | - [client]: Add missing `try_send_message_on` and `try_send_payload_on` in `connection` 206 | - Internal improvements to channels and server's `send_group_message` 207 | - `AsyncRuntime` is now pub 208 | - [client & server] In order to have more control over plugin initialization and only do the strict necessary, which is registering systems and events in the Bevy schedule, `.add_plugin(QuinnetClient/ServerPlugin { initialize_later: true })` can now be used, which will not create the Client/Server `Resource` immediately. 209 | - A command such as `commands.init_resource::();` can be used later on when needed. 210 | - Client & server plugins systems are scheduled to only run if their respective resource exists. 211 | - Their respective `Resource` can be removed through a command at runtime. 212 | - [client]: Fix IPv6 handling. 213 | - Remove `ConnectionConfiguration::new` 214 | - Add `ConnectionConfiguration::from_strings`, `ConnectionConfiguration::from_ips` and `ConnectionConfiguration::from_addrs` 215 | - [server]: Fix IPv6 handling. 216 | - Rename `ServerConfigurationData` to `ServerConfiguration`. 217 | - Remove `ServerConfiguration::new` 218 | - Add `ServerConfiguration::from_string`, `ServerConfiguration::from_ip` and `ServerConfiguration::from_addr` 219 | - Add `server_hostname` to `CertificateRetrievalMode::GenerateSelfSigned` and `CertificateRetrievalMode::LoadFromFileOrGenerateSelfSigned` 220 | 221 | ## Version 0.3.0 (2023-01-20) 222 | 223 | ### Added 224 | 225 | - [client & server] Add `OrderedReliable`, `UnorderedReliable` and `Unreliable` send channels. The existing API uses default channels, additional channel can be opened/closed with `open_channel`/`close_channel` and used with derivatives of `send_message_on`. 226 | - [client & server] Now also receive `Unreliable` messages 227 | - [client & server] Add a stats() function on a connection to retrieve statistics (thanks to [Andrewvy](https://github.com/andrewvy), PR [#4](https://github.com/Henauxg/bevy_quinnet/pull/4)) 228 | - [server] Add a clients() function to retrieve all the connected Client ids 229 | - [tests] Add tests for channels, and move tests to cargo integration test directory 230 | 231 | ### Changed 232 | 233 | - [server] `receive_message` and `receive_payload` functions are now `receive_message_from` and `receive_payload_from`, taking a ClientId as parameter (clients messages are now stored on separate queues to prevent a client from filling the shared queue) 234 | - [client] Expose `ConnectionId` as `pub` in `ConnectionEvent` and `ConnectionLostEvent ` (thanks to [Zheilbron](https://github.com/zheilbron), PR [#5](https://github.com/Henauxg/bevy_quinnet/pull/5)) 235 | - [example:breakout] Make use of Unreliable and UnorderedReliable channels 236 | - Updated dependencies 237 | 238 | ### Fixed 239 | - [client & server] Enhancement on disconnection behaviours, existing outgoing messages are now flushed (thanks to [Zheilbron](https://github.com/zheilbron), PR [#6](https://github.com/Henauxg/bevy_quinnet/pull/6)) 240 | - [client] Do not fail in store_known_hosts_to_file if the path has no prefix 241 | - [server] Do not fail in write_certs_to_files if the cert and key files have non-existing parent directories, create them instead 242 | 243 | ## Version 0.2.0 (2022-11-18) 244 | 245 | ### Added 246 | 247 | - New events: ConnectionEvent and ConnectionLostEvent on both client & server 248 | - Implemented Trust on First Use authentication scheme on the client 249 | - Added CertificateVerificationMode for client : SkipVerification, SignedByCertificateAuthority and TrustOnFirstUse 250 | - New client events for the certificate verification: CertInteractionEvent, CertTrustUpdateEvent and CertConnectionAbortEvent 251 | - Added new ways to handle server certificates with CertificateRetrievalMode: GenerateSelfSigned, LoadFromFile & LoadFromFileOrGenerateSelfSigned 252 | - Client can now have multiple connections simultaneously to multiple servers 253 | - Server now returns the generated/loaded certificate 254 | - It is now possible to host a server locally on a client 255 | - New example: Bevy breakout demo as a 2 players versus game, hosted from a client 256 | - New open_connection & close_connection methods on the client 257 | - New start_endpoint and close_endpoint on the server 258 | - New is_listening method on the server 259 | - New "try" methods that log the errors 260 | - Added tests 261 | - Added documentation 262 | 263 | ### Changed 264 | 265 | - Client & Server configurations now taken as parameter at connection/listen time rather than from a resource. (User can still store it inside a resource) 266 | - Raised DEFAULT_INTERNAL_MESSAGE_CHANNEL_SIZE in client to 100 267 | - Use thiserror internally 268 | - Update Quinn to 0.9 269 | - Update Bevy to 0.9 (by [Lemonzy](https://github.com/Lemonzyy)) 270 | - Updated all dependencies (minors) 271 | - Moved chat demo to its own directory 272 | 273 | ## Version 0.1.0 (2022-10-25) 274 | 275 | Initial release -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![crates.io](https://img.shields.io/crates/v/bevy_quinnet)](https://crates.io/crates/bevy_quinnet) 4 | [![bevy_quinnet on doc.rs](https://docs.rs/bevy_quinnet/badge.svg)](https://docs.rs/bevy_quinnet) 5 | [![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking) 6 | 7 | # Bevy Quinnet 8 | 9 | A Client/Server game networking plugin using [QUIC](https://www.chromium.org/quic/), for the [`Bevy`](https://github.com/bevyengine/bevy) engine. 10 | 11 |
12 | 13 | ## QUIC as a game networking protocol 14 | 15 | QUIC seems really attractive as a game networking protocol because most of the hard-work is done by the protocol specification and the implementation (here [Quinn](https://github.com/quinn-rs/quinn)). No need to reinvent the wheel once again on error-prones subjects such as a UDP reliability wrapper, encryption & authentication mechanisms or congestion-control. 16 | 17 | Most of the features proposed by the big networking libs are supported by default through QUIC. As an example, here is the list of features presented in [GameNetworkingSockets](https://github.com/ValveSoftware/GameNetworkingSockets): 18 | 19 | * *Connection-oriented API (like TCP)* -> by default 20 | * *... but message-oriented (like UDP), not stream-oriented* -> by default (*) 21 | * *Supports both reliable and unreliable message types* -> by default 22 | * *Messages can be larger than underlying MTU. The protocol performs fragmentation, reassembly, and retransmission for reliable messages* -> by default (frag & reassembly is not done by the protocol for unreliable packets) 23 | * *A reliability layer [...]. It is based on the "ack vector" model from DCCP (RFC 4340, section 11.4) and Google QUIC and discussed in the context of games by Glenn Fiedler [...]* -> by default. 24 | * *Encryption. [...] The details for shared key derivation and per-packet IV are based on the design used by Google's QUIC protocol* -> by default 25 | * *Tools for simulating packet latency/loss, and detailed stats measurement* -> Not by default 26 | * *Head-of-line blocking control and bandwidth sharing of multiple message streams on the same connection.* -> by default 27 | * *IPv6 support* -> by default 28 | * *Peer-to-peer networking (NAT traversal with ICE + signaling + symmetric connect mode)* -> Not by default 29 | * *Cross platform* -> by default, where UDP is available 30 | 31 | -> Roughly 9 points out of 11 by default. 32 | 33 | *(\*) Almost, when sharing a QUIC stream, reliable messages need to be framed.* 34 | 35 | ## Features 36 | 37 | Quinnet can be used as a transport layer. It currently features: 38 | 39 | - A **Client plugin** which can: 40 | - Connect/disconnect to/from one or more server 41 | - Send & receive unreliable and ordered/unordered reliable messages 42 | - A **Server plugin** which can: 43 | - Accept client connections & disconnect them 44 | - Send & receive unreliable and ordered/unordered reliable messages 45 | - Both client & server accept **custom protocol structs/enums** defined by the user as the message format, as well as **raw bytes**. 46 | - Communications are encrypted, and the client can [authenticate the server](#certificates-and-server-authentication). 47 | 48 | Although Quinn and parts of Quinnet are asynchronous, the APIs exposed by Quinnet for the client and server are synchronous. This makes the surface API easy to work with and adapted to a Bevy usage. 49 | The implementation uses [tokio channels](https://tokio.rs/tokio/tutorial/channels) internally to communicate with the networking async tasks. 50 | 51 | ## Quickstart 52 | 53 | ### Client 54 | 55 | - Add the `QuinnetClientPlugin` to the bevy app: 56 | 57 | ```rust 58 | App::new() 59 | // ... 60 | .add_plugins(QuinnetClientPlugin::default()) 61 | // ... 62 | .run(); 63 | ``` 64 | 65 | - You can then use the `Client` resource to connect, send & receive messages: 66 | 67 | ```rust 68 | fn start_connection(client: ResMut) { 69 | client 70 | .open_connection(ClientConnectionConfiguration { 71 | addr_config: ClientAddrConfiguration::from_ips( 72 | SERVER_HOST, 73 | SERVER_PORT, 74 | LOCAL_BIND_IP, 75 | 0, 76 | ), 77 | cert_mode: CertificateVerificationMode::SkipVerification, 78 | defaultables: Default::default(), 79 | }); 80 | 81 | // Once connected, you will receive a ConnectionEvent 82 | ``` 83 | 84 | - To process server messages, you can use a bevy system such as the one below. The function `receive_message` is generic, here `ServerMessage` is a user provided enum deriving `Serialize` and `Deserialize`. 85 | 86 | ```rust 87 | fn handle_server_messages( 88 | mut client: ResMut, 89 | /*...*/ 90 | ) { 91 | while let Some(message) = client.connection_mut().try_receive_message() { 92 | match message { 93 | // Match on your own message types ... 94 | ServerMessage::ClientConnected { client_id, username} => {/*...*/} 95 | ServerMessage::ClientDisconnected { client_id } => {/*...*/} 96 | ServerMessage::ChatMessage { client_id, message } => {/*...*/} 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | ### Server 103 | 104 | - Add the `QuinnetServerPlugin` to the bevy app: 105 | 106 | ```rust 107 | App::new() 108 | /*...*/ 109 | .add_plugins(QuinnetServerPlugin::default()) 110 | /*...*/ 111 | .run(); 112 | ``` 113 | 114 | - You can then use the `Server` resource to start the listening server: 115 | 116 | ```rust 117 | fn start_listening(mut server: ResMut) { 118 | server 119 | .start_endpoint(ServerEndpointConfiguration { 120 | addr_config: EndpointAddrConfiguration::from_ip(Ipv6Addr::UNSPECIFIED, 6000), 121 | cert_mode: CertificateRetrievalMode::GenerateSelfSigned { 122 | server_hostname: Ipv6Addr::LOCALHOST.to_string(), 123 | }, 124 | defaultables: Default::default(), 125 | }); 126 | } 127 | ``` 128 | 129 | - To process client messages & send messages, you can use a bevy system such as the one below. The function `receive_message` is generic, here `ClientMessage` is a user provided enum deriving `Serialize` and `Deserialize`. 130 | 131 | ```rust 132 | fn handle_client_messages( 133 | mut server: ResMut, 134 | /*...*/ 135 | ) { 136 | let mut endpoint = server.endpoint_mut(); 137 | for client_id in endpoint.clients() { 138 | while let Some(message) = endpoint.try_receive_message(client_id) { 139 | match message { 140 | // Match on your own message types ... 141 | ClientMessage::Join { username} => { 142 | // Send a messsage to 1 client 143 | endpoint.try_send_message(client_id, ServerMessage::InitClient {/*...*/}); 144 | /*...*/ 145 | } 146 | ClientMessage::Disconnect { } => { 147 | // Disconnect a client 148 | endpoint.disconnect_client(client_id); 149 | /*...*/ 150 | } 151 | ClientMessage::ChatMessage { message } => { 152 | // Send a message to a group of clients 153 | endpoint.try_send_group_message( 154 | client_group, // Iterator of ClientId 155 | ServerMessage::ChatMessage {/*...*/} 156 | ); 157 | /*...*/ 158 | } 159 | } 160 | } 161 | } 162 | } 163 | ``` 164 | 165 | You can also use `endpoint.broadcast_message`, which will send a message to all connected clients. "Connected" here means connected to the server plugin, which happens before your own app handshakes/verifications if you have any. Use `send_group_message` if you want to control the recipients. 166 | 167 | ## Channels 168 | 169 | There are currently 3 types of channels available when you send a message: 170 | - `OrderedReliable`: ensure that messages sent are delivered, and are processed by the receiving end in the same order as they were sent (exemple usage: chat messages) 171 | - `UnorderedReliable`: ensure that messages sent are delivered, in any order (exemple usage: an animation trigger) 172 | - `Unreliable`: no guarantees on the delivery or the order of processing by the receiving end (exemple usage: an entity position sent every ticks) 173 | 174 | When you open a connection/endpoint, some channels are created directly according to the given `SendChannelsConfiguration`. 175 | 176 | ```rust 177 | // Default channels configuration contains only 1 channel of the OrderedReliable type, 178 | // akin to a TCP connection. 179 | let channels_config = SendChannelsConfiguration::default(); 180 | // Creates 2 OrderedReliable channels, and 1 unreliable channel, 181 | // with channel ids being respectively 0, 1 and 2. 182 | let channels_config = SendChannelsConfiguration::from_configs(vec![ 183 | ChannelConfig::default_ordered_reliable(), 184 | ChannelConfig::default_ordered_reliable(), 185 | ChannelConfig::default_reliable()]); 186 | ``` 187 | 188 | Each channel is identified by its own `ChannelId`. Among those, there is a `default` channel which will be used when you don't specify the channel. At startup, the first opened channel becomes the default channel. 189 | 190 | ```rust 191 | let connection = client.connection(); 192 | // No channel specified, default channel is used 193 | connection.send_message(message); 194 | // Specifying the channel id 195 | connection.send_message_on(channel_id, message); 196 | // Changing the default channel 197 | connection.set_default_channel(channel_id); 198 | ``` 199 | 200 | In some cases, you may want to create more than one channel instance of the same type. As an example, using multiple `OrderedReliable` channels to avoid some [Head of line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking) issues. Although channels can be defined through a `SendChannelsConfiguration`, they can also currently be opened & closed at any time. You may have up to 256 differents channels opened simultaneously. 201 | 202 | ```rust 203 | // If you want to create more channels 204 | let chat_channel = client.connection().open_channel(ChannelConfig::default()).unwrap(); 205 | client.connection().send_message_on(chat_channel, chat_message); 206 | ``` 207 | 208 | On the server, channels are created and closed at the endpoint level and exist for all current & future clients. 209 | ```rust 210 | let chat_channel = server.endpoint().open_channel(ChannelConfig::default()).unwrap(); 211 | server.endpoint().send_message_on(client_id, chat_channel, chat_message); 212 | ``` 213 | 214 | ## Certificates and server authentication 215 | 216 | Bevy Quinnet (through Quinn & QUIC) uses TLS 1.3 for authentication, the server needs to provide the client with a certificate confirming its identity, and the client must be configured to trust the certificates it receives from the server. 217 | 218 | Here are the current options available to the server and client plugins for the server authentication: 219 | - Client : 220 | - [x] Skip certificate verification (messages are still encrypted, but the server is not authentified) 221 | - [x] Accept certificates issued by a Certificate Authority (implemented in [Quinn](https://github.com/quinn-rs/quinn), using [rustls](https://github.com/rustls/rustls)) 222 | - [x] [Trust on first use](https://en.wikipedia.org/wiki/Trust_on_first_use) certificates (implemented in Quinnet, using [rustls](https://github.com/rustls/rustls)) 223 | - Server: 224 | - [x] Generate and issue a self-signed certificate 225 | - [x] Issue an already existing certificate (CA or self-signed) 226 | 227 | On the client: 228 | 229 | ```rust 230 | // To accept any certificate 231 | client.open_connection(/*...*/ CertificateVerificationMode::SkipVerification); 232 | // To only accept certificates issued by a Certificate Authority 233 | client.open_connection(/*...*/ CertificateVerificationMode::SignedByCertificateAuthority); 234 | // To use the default configuration of the Trust on first use authentication scheme 235 | client.open_connection(/*...*/ CertificateVerificationMode::TrustOnFirstUse(TrustOnFirstUseConfig { 236 | // You can configure TrustOnFirstUse through the TrustOnFirstUseConfig: 237 | // Provide your own fingerprint store variable/file, 238 | // or configure the actions to apply for each possible certificate verification status. 239 | ..Default::default() 240 | }), 241 | ); 242 | ``` 243 | 244 | On the server: 245 | 246 | ```rust 247 | // To generate a new self-signed certificate on each startup 248 | server.start_endpoint(/*...*/ CertificateRetrievalMode::GenerateSelfSigned { 249 | server_hostname: Ipv6Addr::LOCALHOST.to_string(), 250 | }); 251 | // To load a pre-existing one from files 252 | server.start_endpoint(/*...*/ CertificateRetrievalMode::LoadFromFile { 253 | cert_file: "./certificates.pem".into(), 254 | key_file: "./privkey.pem".into(), 255 | }); 256 | // To load one from files, or to generate a new self-signed one if the files do not exist. 257 | server.start_endpoint(/*...*/ CertificateRetrievalMode::LoadFromFileOrGenerateSelfSigned { 258 | cert_file: "./certificates.pem".into(), 259 | key_file: "./privkey.pem".into(), 260 | save_on_disk: true, // To persist on disk if generated 261 | server_hostname: Ipv6Addr::LOCALHOST.to_string(), 262 | }); 263 | ``` 264 | 265 | See more about certificates in the [certificates readme](docs/Certificates.md) 266 | 267 | ## Examples 268 | 269 |
270 | Chat example 271 | 272 | This demo comes with an headless [server](examples/chat/server.rs), a [terminal client](examples/chat/client.rs) and a shared [protocol](examples/chat/protocol.rs). 273 | 274 | Start the server with 275 | ``` 276 | cargo run --example chat-server --features=bincode-messages 277 | ``` 278 | and as many clients as needed with 279 | ``` 280 | cargo run --example chat-client --features=bincode-messages 281 | ``` 282 | Type `quit` to disconnect with a client. 283 | 284 | ![terminal_chat_demo](https://user-images.githubusercontent.com/19689618/197757086-0643e6e7-6c69-4760-9af6-cb323529dc52.gif) 285 | 286 |
287 | 288 |
289 | Breakout versus example 290 | 291 | This demo is a modification of the classic [Bevy breakout](https://bevyengine.org/examples/games/breakout/) example to turn it into a 2 players versus game. 292 | 293 | It hosts a local server from inside a client, instead of a dedicated headless server as in the chat demo. You can find a [server module](examples/breakout/server.rs), a [client module](examples/breakout/client.rs), a shared [protocol](examples/breakout/protocol.rs) and the [bevy app schedule](examples/breakout/breakout.rs). 294 | 295 | It also makes uses of [`Channels`](#channels). The server broadcasts the paddle position every tick via the `PaddleMoved` message on an `Unreliable` channel, while the `BrickDestroyed`, `BallCollided` and the game setup and start are using `OrderedReliable` channels. 296 | 297 | Start two clients with: 298 | ``` 299 | cargo run --example breakout --features=bincode-messages 300 | ``` 301 | "Host" on one and "Join" on the other. 302 | 303 | [breakout_versus_demo_short.mp4](https://user-images.githubusercontent.com/19689618/213700921-85967bd7-9a47-44ac-9471-77a33938569f.mp4) 304 |
305 | 306 | Examples can be found in the [examples](examples) directory. 307 | 308 | ## Replicon integration 309 | 310 | Bevy Quinnet can be used as a transport in [`bevy_replicon`](https://github.com/projectharmonia/bevy_replicon) with the provided [`bevy_replicon_quinnet`](https://github.com/Henauxg/bevy_replicon_quinnet). 311 | 312 | ## Compatible Bevy versions 313 | 314 | | bevy_quinnet | bevy | 315 | | :----------- | :--- | 316 | | 0.19 | 0.17 | 317 | | 0.17-0.18 | 0.16 | 318 | | 0.12-0.16 | 0.15 | 319 | | 0.9-0.11 | 0.14 | 320 | | 0.7-0.8 | 0.13 | 321 | | 0.6 | 0.12 | 322 | | 0.5 | 0.11 | 323 | | 0.4 | 0.10 | 324 | | 0.2-0.3 | 0.9 | 325 | | 0.1 | 0.8 | 326 | 327 | ## Misc 328 | 329 | ### Cargo features 330 | 331 | *Find the list and description in [cargo.toml](Cargo.toml)* 332 | 333 | ### Logs 334 | 335 | For logs configuration, see the unoffical [bevy cheatbook](https://bevy-cheatbook.github.io/features/log.html). 336 | 337 | ### Limitations 338 | 339 | * QUIC is not available directly in a Browser (it is used by Browsers but not exposed directly as an API). [WebTransport](https://web.dev/webtransport/) is starting to be available on Browsers, however this crate does not provide nor use a WebTransport implementation, and as such, does not support Browser target. 340 | 341 | ## Credits 342 | 343 | Thanks to the [Renet](https://github.com/lucaspoffo/renet) crate for the initial inspiration on the high level API. 344 | 345 | ## License 346 | 347 | This crate is free and open source. All code in this repository is dual-licensed under either: 348 | 349 | * MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) 350 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) 351 | 352 | at your option. 353 | 354 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 355 | --------------------------------------------------------------------------------