diff options
author | Henauxg <19689618+Henauxg@users.noreply.github.com> | 2023-01-19 22:46:21 +0100 |
---|---|---|
committer | Henauxg <19689618+Henauxg@users.noreply.github.com> | 2023-01-19 22:46:21 +0100 |
commit | 1f8602fbec6b70efc57443671b0ec172ce9065ac (patch) | |
tree | 2fa07834b18af94f8c9150ca9d4a66bb6704b16b | |
parent | b8feaa1ba99346bead91714d6efd9df3f1696507 (diff) |
[tests] Add more channel tests and test utilities, as integration tests
-rw-r--r-- | src/lib.rs | 554 | ||||
-rw-r--r-- | tests/certificates.rs | 303 | ||||
-rw-r--r-- | tests/channels.rs | 217 | ||||
-rw-r--r-- | tests/connection.rs | 91 | ||||
-rw-r--r-- | tests/utils/mod.rs | 317 |
5 files changed, 928 insertions, 554 deletions
@@ -1,557 +1,3 @@ pub mod client; pub mod server; pub mod shared; - -/////////////////////////////////////////////////////////// -/// /// -/// Tests /// -/// /// -/////////////////////////////////////////////////////////// - -#[cfg(test)] -mod tests { - use std::{fs, path::Path, thread::sleep, time::Duration}; - - use crate::{ - client::{ - self, - certificate::{ - CertConnectionAbortEvent, CertInteractionEvent, CertTrustUpdateEvent, - CertVerificationInfo, CertVerificationStatus, CertVerifierAction, - CertificateVerificationMode, - }, - connection::ConnectionConfiguration, - Client, QuinnetClientPlugin, DEFAULT_KNOWN_HOSTS_FILE, - }, - server::{ - self, certificate::CertificateRetrievalMode, QuinnetServerPlugin, Server, - ServerConfigurationData, - }, - shared::ClientId, - }; - use bevy::{ - app::ScheduleRunnerPlugin, - prelude::{App, EventReader, Res, ResMut, Resource}, - }; - use serde::{Deserialize, Serialize}; - - const SERVER_HOST: &str = "127.0.0.1"; - const TEST_CERT_FILE: &str = "assets/tests/cert.pem.test"; - const TEST_KEY_FILE: &str = "assets/tests/key.pem.test"; - const TEST_CERT_FINGERPRINT_B64: &str = "sieQJ9J6DIrQP37HAlUFk2hYhLZDY9G5OZQpqzkWlKo="; - - #[derive(Resource, Debug, Clone, Default)] - struct ClientTestData { - connection_events_received: u64, - - cert_trust_update_events_received: u64, - last_trusted_cert_info: Option<CertVerificationInfo>, - - cert_interactions_received: u64, - last_cert_interactions_status: Option<CertVerificationStatus>, - last_cert_interactions_info: Option<CertVerificationInfo>, - - cert_verif_connection_abort_events_received: u64, - last_abort_cert_status: Option<CertVerificationStatus>, - last_abort_cert_info: Option<CertVerificationInfo>, - } - - #[derive(Resource, Debug, Clone, Default)] - struct ServerTestData { - connection_events_received: u64, - last_connected_client_id: Option<ClientId>, - } - - #[derive(Resource, Debug, Clone, Default)] - struct Port(u16); - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - pub enum SharedMessage { - ChatMessage(String), - } - - /////////////////////////////////////////////////////////// - /// /// - /// Test /// - /// /// - /////////////////////////////////////////////////////////// - - #[test] - fn connection_with_two_apps() { - let port = 6000; // TODO Use port 0 and retrieve the port used by the server. - - let mut client_app = build_client_app(); - client_app.insert_resource(Port(port)); - let mut server_app = build_server_app(); - server_app.insert_resource(Port(port)); - - // Startup - client_app.update(); - server_app.update(); - - { - let client = client_app.world.resource::<Client>(); - assert!( - client.get_connection().is_some(), - "The default connection should exist" - ); - let server = server_app.world.resource::<Server>(); - assert!(server.is_listening(), "The server should be listening"); - } - - // Let the async runtime connection connect. - sleep(Duration::from_secs_f32(0.1)); - - // Connection event propagation - client_app.update(); - server_app.update(); - - let sent_client_message = SharedMessage::ChatMessage("Test message content".to_string()); - { - let server_test_data = server_app.world.resource::<ServerTestData>(); - assert_eq!(server_test_data.connection_events_received, 1); - - let client = client_app.world.resource::<Client>(); - let client_test_data = client_app.world.resource::<ClientTestData>(); - assert!( - client.connection().is_connected(), - "The default connection should be connected to the server" - ); - assert_eq!(client_test_data.connection_events_received, 1); - - client - .connection() - .send_message(sent_client_message.clone()) - .unwrap(); - } - - // Client->Server Message - sleep(Duration::from_secs_f32(0.1)); - server_app.update(); - - { - let server_test_data = server_app.world.resource::<ServerTestData>(); - assert!( - server_test_data.last_connected_client_id.is_some(), - "A client should have connected" - ); - let client_id = server_test_data.last_connected_client_id.unwrap(); - - let client_message = server_app - .world - .resource_mut::<Server>() - .endpoint_mut() - .receive_message_from::<SharedMessage>(client_id) - .expect("Failed to receive client message") - .expect("There should be a client message"); - assert_eq!(client_message, sent_client_message); - } - - let sent_server_message = SharedMessage::ChatMessage("Server response".to_string()); - { - let server = server_app.world.resource::<Server>(); - server - .endpoint() - .broadcast_message(sent_server_message.clone()) - .unwrap(); - } - - // Server->Client Message - sleep(Duration::from_secs_f32(0.1)); - client_app.update(); - - { - let mut client = client_app.world.resource_mut::<Client>(); - let server_message = client - .connection_mut() - .receive_message::<SharedMessage>() - .expect("Failed to receive server message") - .expect("There should be a server message"); - assert_eq!(server_message, sent_server_message); - } - } - - /////////////////////////////////////////////////////////// - /// /// - /// Test /// - /// /// - /////////////////////////////////////////////////////////// - - #[test] - fn trust_on_first_use() { - // TOFU With default parameters - // Server listens with a cert loaded from a file - // Client connects with empty cert store - // -> The server's certificate is treatead as Unknown by the client, which stores it and continues the connection - // Clients disconnects - // Client reconnects with the updated cert store - // -> The server's certificate is treatead as Trusted by the client, which continues the connection - // Clients disconnects - // Server reboots, and generates a new self-signed certificate - // Client reconnects with its cert store - // -> The server's certificate is treatead as Untrusted by the client, which requests a client action - // We receive the client action request and ask to abort the connection - - let port = 6001; // TODO Use port 0 and retrieve the port used by the server. - - if Path::new(DEFAULT_KNOWN_HOSTS_FILE).exists() { - fs::remove_file(DEFAULT_KNOWN_HOSTS_FILE) - .expect("Failed to remove default known hosts file"); - } - - let mut client_app = App::new(); - client_app - .add_plugin(ScheduleRunnerPlugin::default()) - .add_plugin(QuinnetClientPlugin::default()) - .insert_resource(ClientTestData::default()) - .add_system(handle_client_events); - - let mut server_app = App::new(); - server_app - .add_plugin(ScheduleRunnerPlugin::default()) - .add_plugin(QuinnetServerPlugin::default()) - .insert_resource(ServerTestData::default()) - .add_system(handle_server_events); - - // Startup - client_app.update(); - server_app.update(); - - // Server listens with a cert loaded from a file - { - let mut server = server_app.world.resource_mut::<Server>(); - let (server_cert, _) = server - .start_endpoint( - ServerConfigurationData::new( - SERVER_HOST.to_string(), - port, - "0.0.0.0".to_string(), - ), - CertificateRetrievalMode::LoadFromFile { - cert_file: TEST_CERT_FILE.to_string(), - key_file: TEST_KEY_FILE.to_string(), - }, - ) - .unwrap(); - assert_eq!( - TEST_CERT_FINGERPRINT_B64.to_string(), - server_cert.fingerprint.to_base64(), - "The loaded cert fingerprint should match the known test fingerprint" - ); - } - - // Client connects with empty cert store - { - let mut client = client_app.world.resource_mut::<Client>(); - client - .open_connection( - default_client_configuration(port), - CertificateVerificationMode::TrustOnFirstUse( - client::certificate::TrustOnFirstUseConfig { - ..Default::default() - }, - ), - ) - .unwrap(); - } - - // Let the async runtime connection connect. - sleep(Duration::from_secs_f32(0.1)); - - // Connection & event propagation - server_app.update(); - client_app.update(); - - // The server's certificate is treatead as Unknown by the client, which stores it and continues the connection - { - let mut client_test_data = client_app.world.resource_mut::<ClientTestData>(); - assert_eq!( - client_test_data.cert_trust_update_events_received, 1, - "The client should have received exactly 1 certificate trust update event" - ); - let cert_info = client_test_data - .last_trusted_cert_info - .as_mut() - .expect("A certificate trust update should have happened"); - assert_eq!( - cert_info.fingerprint.to_base64(), - TEST_CERT_FINGERPRINT_B64.to_string(), - "The certificate rceived by the client should match the known test certificate" - ); - assert!( - cert_info.known_fingerprint.is_none(), - "The client should not have any previous certificate fingerprint for this server" - ); - assert_eq!( - cert_info.server_name.to_string(), - SERVER_HOST.to_string(), - "The server name should match the one we configured" - ); - - let mut client = client_app.world.resource_mut::<Client>(); - assert!( - client.connection().is_connected(), - "The default connection should be connected to the server" - ); - - // Clients disconnects - // Client reconnects with the updated cert store - client - .close_all_connections() - .expect("Failed to close connections on the client"); - - client - .open_connection( - default_client_configuration(port), - CertificateVerificationMode::TrustOnFirstUse( - client::certificate::TrustOnFirstUseConfig { - ..Default::default() - }, - ), - ) - .unwrap(); - } - - // Let the async runtime connection connect. - sleep(Duration::from_secs_f32(0.1)); - - // Connection & event propagation - server_app.update(); - client_app.update(); - - { - assert!( - client_app - .world - .resource_mut::<Client>() - .connection() - .is_connected(), - "The default connection should be connected to the server" - ); - - let client_test_data = client_app.world.resource::<ClientTestData>(); - 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"); - - // Clients disconnects - client_app - .world - .resource_mut::<Client>() - .close_all_connections() - .expect("Failed to close connections on the client"); - } - - // Server reboots, and generates a new self-signed certificate - server_app - .world - .resource_mut::<Server>() - .stop_endpoint() - .unwrap(); - - // Let the endpoint fully stop. - sleep(Duration::from_secs_f32(0.1)); - - let (server_cert, _) = server_app - .world - .resource_mut::<Server>() - .start_endpoint( - ServerConfigurationData::new(SERVER_HOST.to_string(), port, "0.0.0.0".to_string()), - CertificateRetrievalMode::GenerateSelfSigned, - ) - .unwrap(); - - // Client reconnects with its cert store containing the previously store certificate fingerprint - { - let mut client = client_app.world.resource_mut::<Client>(); - client - .open_connection( - default_client_configuration(port), - CertificateVerificationMode::TrustOnFirstUse( - client::certificate::TrustOnFirstUseConfig { - ..Default::default() - }, - ), - ) - .unwrap(); - } - - // Let the async runtime connection connect. - sleep(Duration::from_secs_f32(0.1)); - - // Connection & event propagation: certificate interaction event - server_app.update(); - client_app.update(); - - // Let the async runtime process the certificate action & connection. - sleep(Duration::from_secs_f32(0.1)); - - // Connection abort event - client_app.update(); - - // The server's certificate is treatead as Untrusted by the client, which requests a client action - // We received the client action request and asked to abort the connection - { - let mut client_test_data = client_app.world.resource_mut::<ClientTestData>(); - assert_eq!( - client_test_data.cert_interactions_received, 1, - "The client should have received exactly 1 certificate interaction event" - ); - assert_eq!( - client_test_data.cert_verif_connection_abort_events_received, 1, - "The client should have received exactly 1 certificate connection abort event" - ); - - // Verify the cert info in the certificate interaction event - let interaction_cert_info = client_test_data - .last_cert_interactions_info - .as_mut() - .expect("A certificate interaction event should have happened during certificate verification"); - assert_eq!( - interaction_cert_info.fingerprint.to_base64(), - server_cert.fingerprint.to_base64(), - "The fingerprint received by the client should match the one generated by the server" - ); - // Verify the known fingerprint - assert_eq!( - interaction_cert_info - .known_fingerprint - .as_mut() - .expect("There should be a known fingerprint in the store") - .to_base64(), - TEST_CERT_FINGERPRINT_B64.to_string(), - "The previously known fingeprint for this server should be the test fingerprint" - ); - assert_eq!( - interaction_cert_info.server_name.to_string(), - SERVER_HOST.to_string(), - "The server name in the certificate interaction event should be the server we want to connect to" - ); - assert_eq!( - client_test_data.last_cert_interactions_status, - Some(CertVerificationStatus::UntrustedCertificate), - "The certificate verification status in the certificate interaction event should be `Untrusted`" - ); - - // Verify the cert info in the connection abort event - assert_eq!( - client_test_data.last_abort_cert_info, - client_test_data.last_cert_interactions_info, - "The certificate info in the connection abort event should match those of the certificate interaction event" - ); - assert_eq!( - client_test_data.last_abort_cert_status, - Some(CertVerificationStatus::UntrustedCertificate), - "The certificate verification status in the connection abort event should be `Untrusted`" - ); - - let client = client_app.world.resource::<Client>(); - assert!( - client.connection().is_connected() == false, - "The default connection should not be connected to the server" - ); - } - - // Leave the workspace clean - fs::remove_file(DEFAULT_KNOWN_HOSTS_FILE) - .expect("Failed to remove default known hosts file"); - } - - /////////////////////////////////////////////////////////// - /// /// - /// Test utilities /// - /// /// - /////////////////////////////////////////////////////////// - - fn build_client_app() -> App { - let mut client_app = App::new(); - client_app - .add_plugin(ScheduleRunnerPlugin::default()) - .add_plugin(QuinnetClientPlugin::default()) - .insert_resource(ClientTestData::default()) - .add_startup_system(start_simple_connection) - .add_system(handle_client_events); - client_app - } - - fn build_server_app() -> App { - let mut server_app = App::new(); - server_app - .add_plugin(ScheduleRunnerPlugin::default()) - .add_plugin(QuinnetServerPlugin::default()) - .insert_resource(ServerTestData::default()) - .add_startup_system(start_listening) - .add_system(handle_server_events); - server_app - } - - fn default_client_configuration(port: u16) -> ConnectionConfiguration { - ConnectionConfiguration::new(SERVER_HOST.to_string(), port, "0.0.0.0".to_string(), 0) - } - - fn start_simple_connection(mut client: ResMut<Client>, port: Res<Port>) { - client - .open_connection( - default_client_configuration(port.0), - CertificateVerificationMode::SkipVerification, - ) - .unwrap(); - } - - fn start_listening(mut server: ResMut<Server>, port: Res<Port>) { - server - .start_endpoint( - ServerConfigurationData::new( - SERVER_HOST.to_string(), - port.0, - "0.0.0.0".to_string(), - ), - CertificateRetrievalMode::GenerateSelfSigned, - ) - .unwrap(); - } - - fn handle_client_events( - mut connection_events: EventReader<client::connection::ConnectionEvent>, - mut cert_trust_update_events: EventReader<CertTrustUpdateEvent>, - mut cert_interaction_events: EventReader<CertInteractionEvent>, - mut cert_connection_abort_events: EventReader<CertConnectionAbortEvent>, - mut test_data: ResMut<ClientTestData>, - ) { - for _connected_event in connection_events.iter() { - test_data.connection_events_received += 1; - } - for trust_update in cert_trust_update_events.iter() { - test_data.cert_trust_update_events_received += 1; - test_data.last_trusted_cert_info = Some(trust_update.cert_info.clone()); - } - for cert_interaction in cert_interaction_events.iter() { - test_data.cert_interactions_received += 1; - test_data.last_cert_interactions_status = Some(cert_interaction.status.clone()); - test_data.last_cert_interactions_info = Some(cert_interaction.info.clone()); - - match cert_interaction.status { - CertVerificationStatus::UnknownCertificate => todo!(), - CertVerificationStatus::UntrustedCertificate => { - cert_interaction - .apply_cert_verifier_action(CertVerifierAction::AbortConnection) - .expect("Failed to apply cert verification action"); - } - CertVerificationStatus::TrustedCertificate => todo!(), - } - } - for connection_abort in cert_connection_abort_events.iter() { - test_data.cert_verif_connection_abort_events_received += 1; - test_data.last_abort_cert_status = Some(connection_abort.status.clone()); - test_data.last_abort_cert_info = Some(connection_abort.cert_info.clone()); - } - } - - fn handle_server_events( - mut connection_events: EventReader<server::ConnectionEvent>, - mut test_data: ResMut<ServerTestData>, - ) { - for connected_event in connection_events.iter() { - test_data.connection_events_received += 1; - test_data.last_connected_client_id = Some(connected_event.id); - } - } -} diff --git a/tests/certificates.rs b/tests/certificates.rs new file mode 100644 index 0000000..21daec7 --- /dev/null +++ b/tests/certificates.rs @@ -0,0 +1,303 @@ +use std::{fs, path::Path, thread::sleep, time::Duration}; + +use bevy::{app::ScheduleRunnerPlugin, prelude::App}; +use bevy_quinnet::{ + client::{ + self, + certificate::{CertVerificationStatus, CertificateVerificationMode}, + Client, QuinnetClientPlugin, DEFAULT_KNOWN_HOSTS_FILE, + }, + server::{ + certificate::CertificateRetrievalMode, QuinnetServerPlugin, Server, ServerConfigurationData, + }, +}; + +// https://github.com/rust-lang/rust/issues/46379 +pub use utils::*; + +mod utils; + +/////////////////////////////////////////////////////////// +/// /// +/// Test /// +/// /// +/////////////////////////////////////////////////////////// + +const TEST_CERT_FILE: &str = "assets/tests/cert.pem.test"; +const TEST_KEY_FILE: &str = "assets/tests/key.pem.test"; +const TEST_CERT_FINGERPRINT_B64: &str = "sieQJ9J6DIrQP37HAlUFk2hYhLZDY9G5OZQpqzkWlKo="; + +#[test] +fn trust_on_first_use() { + // TOFU With default parameters + // Server listens with a cert loaded from a file + // Client connects with empty cert store + // -> The server's certificate is treatead as Unknown by the client, which stores it and continues the connection + // Clients disconnects + // Client reconnects with the updated cert store + // -> The server's certificate is treatead as Trusted by the client, which continues the connection + // Clients disconnects + // Server reboots, and generates a new self-signed certificate + // Client reconnects with its cert store + // -> The server's certificate is treatead as Untrusted by the client, which requests a client action + // We receive the client action request and ask to abort the connection + + let port = 6004; // TODO Use port 0 and retrieve the port used by the server. + + if Path::new(DEFAULT_KNOWN_HOSTS_FILE).exists() { + fs::remove_file(DEFAULT_KNOWN_HOSTS_FILE) + .expect("Failed to remove default known hosts file"); + } + + let mut client_app = App::new(); + client_app + .add_plugin(ScheduleRunnerPlugin::default()) + .add_plugin(QuinnetClientPlugin::default()) + .insert_resource(ClientTestData::default()) + .add_system(handle_client_events); + + let mut server_app = App::new(); + server_app + .add_plugin(ScheduleRunnerPlugin::default()) + .add_plugin(QuinnetServerPlugin::default()) + .insert_resource(ServerTestData::default()) + .add_system(handle_server_events); + + // Startup + client_app.update(); + server_app.update(); + + // Server listens with a cert loaded from a file + { + let mut server = server_app.world.resource_mut::<Server>(); + let (server_cert, _) = server + .start_endpoint( + ServerConfigurationData::new(SERVER_HOST.to_string(), port, "0.0.0.0".to_string()), + CertificateRetrievalMode::LoadFromFile { + cert_file: TEST_CERT_FILE.to_string(), + key_file: TEST_KEY_FILE.to_string(), + }, + ) + .unwrap(); + assert_eq!( + TEST_CERT_FINGERPRINT_B64.to_string(), + server_cert.fingerprint.to_base64(), + "The loaded cert fingerprint should match the known test fingerprint" + ); + } + + // Client connects with empty cert store + { + let mut client = client_app.world.resource_mut::<Client>(); + client + .open_connection( + default_client_configuration(port), + CertificateVerificationMode::TrustOnFirstUse( + client::certificate::TrustOnFirstUseConfig { + ..Default::default() + }, + ), + ) + .unwrap(); + } + + // Let the async runtime connection connect. + sleep(Duration::from_secs_f32(0.1)); + + // Connection & event propagation + server_app.update(); + client_app.update(); + + // The server's certificate is treatead as Unknown by the client, which stores it and continues the connection + { + let mut client_test_data = client_app.world.resource_mut::<ClientTestData>(); + assert_eq!( + client_test_data.cert_trust_update_events_received, 1, + "The client should have received exactly 1 certificate trust update event" + ); + let cert_info = client_test_data + .last_trusted_cert_info + .as_mut() + .expect("A certificate trust update should have happened"); + assert_eq!( + cert_info.fingerprint.to_base64(), + TEST_CERT_FINGERPRINT_B64.to_string(), + "The certificate rceived by the client should match the known test certificate" + ); + assert!( + cert_info.known_fingerprint.is_none(), + "The client should not have any previous certificate fingerprint for this server" + ); + assert_eq!( + cert_info.server_name.to_string(), + SERVER_HOST.to_string(), + "The server name should match the one we configured" + ); + + let mut client = client_app.world.resource_mut::<Client>(); + assert!( + client.connection().is_connected(), + "The default connection should be connected to the server" + ); + + // Clients disconnects + // Client reconnects with the updated cert store + client + .close_all_connections() + .expect("Failed to close connections on the client"); + + client + .open_connection( + default_client_configuration(port), + CertificateVerificationMode::TrustOnFirstUse( + client::certificate::TrustOnFirstUseConfig { + ..Default::default() + }, + ), + ) + .unwrap(); + } + + // Let the async runtime connection connect. + sleep(Duration::from_secs_f32(0.1)); + + // Connection & event propagation + server_app.update(); + client_app.update(); + + { + assert!( + client_app + .world + .resource_mut::<Client>() + .connection() + .is_connected(), + "The default connection should be connected to the server" + ); + + let client_test_data = client_app.world.resource::<ClientTestData>(); + 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"); + + // Clients disconnects + client_app + .world + .resource_mut::<Client>() + .close_all_connections() + .expect("Failed to close connections on the client"); + } + + // Server reboots, and generates a new self-signed certificate + server_app + .world + .resource_mut::<Server>() + .stop_endpoint() + .unwrap(); + + // Let the endpoint fully stop. + sleep(Duration::from_secs_f32(0.1)); + + let (server_cert, _) = server_app + .world + .resource_mut::<Server>() + .start_endpoint( + ServerConfigurationData::new(SERVER_HOST.to_string(), port, "0.0.0.0".to_string()), + CertificateRetrievalMode::GenerateSelfSigned, + ) + .unwrap(); + + // Client reconnects with its cert store containing the previously store certificate fingerprint + { + let mut client = client_app.world.resource_mut::<Client>(); + client + .open_connection( + default_client_configuration(port), + CertificateVerificationMode::TrustOnFirstUse( + client::certificate::TrustOnFirstUseConfig { + ..Default::default() + }, + ), + ) + .unwrap(); + } + + // Let the async runtime connection connect. + sleep(Duration::from_secs_f32(0.1)); + + // Connection & event propagation: certificate interaction event + server_app.update(); + client_app.update(); + + // Let the async runtime process the certificate action & connection. + sleep(Duration::from_secs_f32(0.1)); + + // Connection abort event + client_app.update(); + + // The server's certificate is treatead as Untrusted by the client, which requests a client action + // We received the client action request and asked to abort the connection + { + let mut client_test_data = client_app.world.resource_mut::<ClientTestData>(); + assert_eq!( + client_test_data.cert_interactions_received, 1, + "The client should have received exactly 1 certificate interaction event" + ); + assert_eq!( + client_test_data.cert_verif_connection_abort_events_received, 1, + "The client should have received exactly 1 certificate connection abort event" + ); + + // Verify the cert info in the certificate interaction event + let interaction_cert_info = client_test_data + .last_cert_interactions_info + .as_mut() + .expect( + "A certificate interaction event should have happened during certificate verification", + ); + assert_eq!( + interaction_cert_info.fingerprint.to_base64(), + server_cert.fingerprint.to_base64(), + "The fingerprint received by the client should match the one generated by the server" + ); + // Verify the known fingerprint + assert_eq!( + interaction_cert_info + .known_fingerprint + .as_mut() + .expect("There should be a known fingerprint in the store") + .to_base64(), + TEST_CERT_FINGERPRINT_B64.to_string(), + "The previously known fingeprint for this server should be the test fingerprint" + ); + assert_eq!( + interaction_cert_info.server_name.to_string(), + SERVER_HOST.to_string(), + "The server name in the certificate interaction event should be the server we want to connect to" + ); + assert_eq!( + client_test_data.last_cert_interactions_status, + Some(CertVerificationStatus::UntrustedCertificate), + "The certificate verification status in the certificate interaction event should be `Untrusted`" + ); + + // Verify the cert info in the connection abort event + assert_eq!( + client_test_data.last_abort_cert_info, + client_test_data.last_cert_interactions_info, + "The certificate info in the connection abort event should match those of the certificate interaction event" + ); + assert_eq!( + client_test_data.last_abort_cert_status, + Some(CertVerificationStatus::UntrustedCertificate), + "The certificate verification status in the connection abort event should be `Untrusted`" + ); + + let client = client_app.world.resource::<Client>(); + assert!( + client.connection().is_connected() == false, + "The default connection should not be connected to the server" + ); + } + + // Leave the workspace clean + fs::remove_file(DEFAULT_KNOWN_HOSTS_FILE).expect("Failed to remove default known hosts file"); +} diff --git a/tests/channels.rs b/tests/channels.rs new file mode 100644 index 0000000..ebba9df --- /dev/null +++ b/tests/channels.rs @@ -0,0 +1,217 @@ +use bevy::prelude::App; + +use bevy_quinnet::{ + client::Client, + server::Server, + shared::{ + channel::{ChannelId, ChannelType}, + QuinnetError, + }, +}; + +// https://github.com/rust-lang/rust/issues/46379 +pub use utils::*; + +mod utils; + +/////////////////////////////////////////////////////////// +/// /// +/// Test /// +/// /// +/////////////////////////////////////////////////////////// + +#[test] +fn default_channel() { + let port = 6001; // TODO Use port 0 and retrieve the port used by the server. + let mut server_app: App = start_simple_server_app(port); + let mut client_app: App = start_simple_client_app(port); + + let client_default_channel = get_default_client_channel(&client_app); + let server_default_channel = get_default_server_channel(&server_app); + + for channel in vec![client_default_channel, server_default_channel] { + assert!( + matches!(channel, ChannelId::OrderedReliable(_)), + "Default channel should be an OrderedReliable channel" + ); + } + + close_client_channel(client_default_channel, &mut client_app); + close_server_channel(server_default_channel, &mut server_app); + + { + let mut server = server_app.world.resource_mut::<Server>(); + assert_eq!( + server.endpoint().get_default_channel(), + None, + "Default server channel should be None" + ); + + assert!( + matches!( + server + .endpoint_mut() + .broadcast_message(SharedMessage::TestMessage("".to_string())), + Err(QuinnetError::NoDefaultChannel) + ), + "Should not be able to send on default channel" + ); + } + { + let mut client = client_app.world.resource_mut::<Client>(); + assert_eq!( + client.connection().get_default_channel(), + None, + "Default client channel should be None" + ); + + assert!( + matches!( + client + .connection_mut() + .send_message(SharedMessage::TestMessage("".to_string())), + Err(QuinnetError::NoDefaultChannel) + ), + "Should not be able to send on default channel" + ); + } + + let client_channel = open_client_channel(ChannelType::OrderedReliable, &mut client_app); + let server_channel = open_server_channel(ChannelType::OrderedReliable, &mut server_app); + + let client_id = wait_for_client_connected(&mut client_app, &mut server_app); + + let mut msg_counter = 0; + send_and_test_client_message( + client_id, + client_channel, + &mut client_app, + &mut server_app, + &mut msg_counter, + ); + send_and_test_server_message( + client_id, + server_channel, + &mut server_app, + &mut client_app, + &mut msg_counter, + ); +} + +/////////////////////////////////////////////////////////// +/// /// +/// Test /// +/// /// +/////////////////////////////////////////////////////////// + +#[test] +fn single_instance_channels() { + let port = 6002; // TODO Use port 0 and retrieve the port used by the server. + let mut server_app: App = start_simple_server_app(port); + let mut client_app: App = start_simple_client_app(port); + + let client_id = wait_for_client_connected(&mut client_app, &mut server_app); + + let mut msg_counter = 0; + for (channel_type, channel_id) in vec![ + (ChannelType::UnorderedReliable, ChannelId::UnorderedReliable), + (ChannelType::Unreliable, ChannelId::Unreliable), + ] { + send_and_test_client_message( + client_id, + channel_id, + &mut client_app, + &mut server_app, + &mut msg_counter, + ); + close_client_channel(channel_id, &mut client_app); + open_client_channel(channel_type, &mut client_app); + send_and_test_client_message( + client_id, + channel_id, + &mut client_app, + &mut server_app, + &mut msg_counter, + ); + + send_and_test_server_message( + client_id, + channel_id, + &mut server_app, + &mut client_app, + &mut msg_counter, + ); + close_server_channel(channel_id, &mut server_app); + open_server_channel(channel_type, &mut server_app); + send_and_test_server_message( + client_id, + channel_id, + &mut server_app, + &mut client_app, + &mut msg_counter, + ); + } +} + +/////////////////////////////////////////////////////////// +/// /// +/// Test /// +/// /// +/////////////////////////////////////////////////////////// + +#[test] +fn multi_instance_channels() { + let port = 6003; // TODO Use port 0 and retrieve the port used by the server. + let mut server_app: App = start_simple_server_app(port); + let mut client_app_1: App = start_simple_client_app(port); + + let client_id_1 = wait_for_client_connected(&mut client_app_1, &mut server_app); + + let client_1_channel_1 = get_default_client_channel(&client_app_1); + let client_1_channel_2 = open_client_channel(ChannelType::OrderedReliable, &mut client_app_1); + + let server_channel_1 = get_default_server_channel(&server_app); + let server_channel_2 = open_server_channel(ChannelType::OrderedReliable, &mut server_app); + + let mut msg_counter = 0; + for channel in vec![client_1_channel_1, client_1_channel_2] { + send_and_test_client_message( + client_id_1, + channel, + &mut client_app_1, + &mut server_app, + &mut msg_counter, + ); + } + for channel in vec![server_channel_1, server_channel_2] { + send_and_test_server_message( + client_id_1, + channel, + &mut server_app, + &mut client_app_1, + &mut msg_counter, + ); + } + + let mut client_app_2 = start_simple_client_app(port); + let client_id_2 = wait_for_client_connected(&mut client_app_2, &mut server_app); + + for (client_id, mut client_app) in + vec![(client_id_1, client_app_1), (client_id_2, client_app_2)] + { + send_and_test_server_message( + client_id, + server_channel_1, + &mut server_app, + &mut client_app, + &mut msg_counter, + ); + send_and_test_server_message( + client_id, + server_channel_2, + &mut server_app, + &mut client_app, + &mut msg_counter, + ); + } +} diff --git a/tests/connection.rs b/tests/connection.rs new file mode 100644 index 0000000..ccf7826 --- /dev/null +++ b/tests/connection.rs @@ -0,0 +1,91 @@ +use std::{thread::sleep, time::Duration}; + +use bevy_quinnet::{client::Client, server::Server}; + +// https://github.com/rust-lang/rust/issues/46379 +pub use utils::*; + +mod utils; + +/////////////////////////////////////////////////////////// +/// /// +/// Test /// +/// /// +/////////////////////////////////////////////////////////// + +#[test] +fn connection_with_two_apps() { + let port = 6000; // TODO Use port 0 and retrieve the port used by the server. + + let mut client_app = start_simple_client_app(port); + let mut server_app = start_simple_server_app(port); + + { + let client = client_app.world.resource::<Client>(); + assert!( + client.get_connection().is_some(), + "The default connection should exist" + ); + let server = server_app.world.resource::<Server>(); + assert!(server.is_listening(), "The server should be listening"); + } + + let client_id = wait_for_client_connected(&mut client_app, &mut server_app); + + let sent_client_message = SharedMessage::TestMessage("Test message content".to_string()); + { + let server_test_data = server_app.world.resource::<ServerTestData>(); + assert_eq!(server_test_data.connection_events_received, 1); + + let client = client_app.world.resource::<Client>(); + let client_test_data = client_app.world.resource::<ClientTestData>(); + assert!( + client.connection().is_connected(), + "The default connection should be connected to the server" + ); + assert_eq!(client_test_data.connection_events_received, 1); + + client + .connection() + .send_message(sent_client_message.clone()) + .unwrap(); + } + + // Client->Server Message + sleep(Duration::from_secs_f32(0.1)); + server_app.update(); + + { + let client_message = server_app + .world + .resource_mut::<Server>() + .endpoint_mut() + .receive_message_from::<SharedMessage>(client_id) + .expect("Failed to receive client message") + .expect("There should be a client message"); + assert_eq!(client_message, sent_client_message); + } + + let sent_server_message = SharedMessage::TestMessage("Server response".to_string()); + { + let server = server_app.world.resource::<Server>(); + server + .endpoint() + .broadcast_message(sent_server_message.clone()) + .unwrap(); + } + + // Server->Client Message + sleep(Duration::from_secs_f32(0.1)); + client_app.update(); + + { + let mut client = client_app.world.resource_mut::<Client>(); + let server_message = client + .connection_mut() + .receive_message::<SharedMessage>() + .expect("Failed to receive server message") + .expect("There should be a server message"); + assert_eq!(server_message, sent_server_message); + } +} diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs new file mode 100644 index 0000000..24b0ac8 --- /dev/null +++ b/tests/utils/mod.rs @@ -0,0 +1,317 @@ +use std::{thread::sleep, time::Duration}; + +use bevy::{ + app::ScheduleRunnerPlugin, + prelude::{App, EventReader, Res, ResMut, Resource}, +}; +use bevy_quinnet::{ + client::{ + self, + certificate::{ + CertConnectionAbortEvent, CertInteractionEvent, CertTrustUpdateEvent, + CertVerificationInfo, CertVerificationStatus, CertVerifierAction, + CertificateVerificationMode, + }, + connection::ConnectionConfiguration, + Client, QuinnetClientPlugin, + }, + server::{ + self, certificate::CertificateRetrievalMode, QuinnetServerPlugin, Server, + ServerConfigurationData, + }, + shared::{ + channel::{ChannelId, ChannelType}, + ClientId, + }, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Resource, Debug, Clone, Default)] +pub struct ClientTestData { + pub connection_events_received: u64, + + pub cert_trust_update_events_received: u64, + pub last_trusted_cert_info: Option<CertVerificationInfo>, + + pub cert_interactions_received: u64, + pub last_cert_interactions_status: Option<CertVerificationStatus>, + pub last_cert_interactions_info: Option<CertVerificationInfo>, + + pub cert_verif_connection_abort_events_received: u64, + pub last_abort_cert_status: Option<CertVerificationStatus>, + pub last_abort_cert_info: Option<CertVerificationInfo>, +} + +#[derive(Resource, Debug, Clone, Default)] +pub struct ServerTestData { + pub connection_events_received: u64, + pub last_connected_client_id: Option<ClientId>, +} + +#[derive(Resource, Debug, Clone, Default)] +pub struct Port(u16); + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum SharedMessage { + TestMessage(String), +} + +pub const SERVER_HOST: &str = "127.0.0.1"; + +pub fn build_client_app() -> App { + let mut client_app = App::new(); + client_app + .add_plugin(ScheduleRunnerPlugin::default()) + .add_plugin(QuinnetClientPlugin::default()) + .insert_resource(ClientTestData::default()) + .add_startup_system(start_simple_connection) + .add_system(handle_client_events); + client_app +} + +pub fn build_server_app() -> App { + let mut server_app = App::new(); + server_app + .add_plugin(ScheduleRunnerPlugin::default()) + .add_plugin(QuinnetServerPlugin::default()) + .insert_resource(ServerTestData::default()) + .add_startup_system(start_listening) + .add_system(handle_server_events); + server_app +} + +pub fn default_client_configuration(port: u16) -> ConnectionConfiguration { + ConnectionConfiguration::new(SERVER_HOST.to_string(), port, "0.0.0.0".to_string(), 0) +} + +pub fn start_simple_connection(mut client: ResMut<Client>, port: Res<Port>) { + client + .open_connection( + default_client_configuration(port.0), + CertificateVerificationMode::SkipVerification, + ) + .unwrap(); +} + +pub fn start_listening(mut server: ResMut<Server>, port: Res<Port>) { + server + .start_endpoint( + ServerConfigurationData::new(SERVER_HOST.to_string(), port.0, "0.0.0.0".to_string()), + CertificateRetrievalMode::GenerateSelfSigned, + ) + .unwrap(); +} + +pub fn handle_client_events( + mut connection_events: EventReader<client::connection::ConnectionEvent>, + mut cert_trust_update_events: EventReader<CertTrustUpdateEvent>, + mut cert_interaction_events: EventReader<CertInteractionEvent>, + mut cert_connection_abort_events: EventReader<CertConnectionAbortEvent>, + mut test_data: ResMut<ClientTestData>, +) { + for _connected_event in connection_events.iter() { + test_data.connection_events_received += 1; + } + for trust_update in cert_trust_update_events.iter() { + test_data.cert_trust_update_events_received += 1; + test_data.last_trusted_cert_info = Some(trust_update.cert_info.clone()); + } + for cert_interaction in cert_interaction_events.iter() { + test_data.cert_interactions_received += 1; + test_data.last_cert_interactions_status = Some(cert_interaction.status.clone()); + test_data.last_cert_interactions_info = Some(cert_interaction.info.clone()); + + match cert_interaction.status { + CertVerificationStatus::UnknownCertificate => todo!(), + CertVerificationStatus::UntrustedCertificate => { + cert_interaction + .apply_cert_verifier_action(CertVerifierAction::AbortConnection) + .expect("Failed to apply cert verification action"); + } + CertVerificationStatus::TrustedCertificate => todo!(), + } + } + for connection_abort in cert_connection_abort_events.iter() { + test_data.cert_verif_connection_abort_events_received += 1; + test_data.last_abort_cert_status = Some(connection_abort.status.clone()); + test_data.last_abort_cert_info = Some(connection_abort.cert_info.clone()); + } +} + +pub fn handle_server_events( + mut connection_events: EventReader<server::ConnectionEvent>, + mut test_data: ResMut<ServerTestData>, +) { + for connected_event in connection_events.iter() { + test_data.connection_events_received += 1; + test_data.last_connected_client_id = Some(connected_event.id); + } +} + +pub fn start_simple_server_app(port: u16) -> App { + let mut server_app = build_server_app(); + server_app.insert_resource(Port(port)); + + // Startup + server_app.update(); + server_app +} + +pub fn start_simple_client_app(port: u16) -> App { + let mut client_app = build_client_app(); + client_app.insert_resource(Port(port)); + + // Startup + client_app.update(); + client_app +} + +pub fn wait_for_client_connected(client_app: &mut App, server_app: &mut App) -> ClientId { + loop { + sleep(Duration::from_secs_f32(0.05)); + client_app.update(); + if client_app + .world + .resource::<Client>() + .connection() + .is_connected() + { + break; + } + } + server_app.update(); + server_app + .world + .resource::<ServerTestData>() + .last_connected_client_id + .expect("A client should have connected") +} + +pub fn get_default_client_channel(app: &App) -> ChannelId { + let client = app.world.resource::<Client>(); + client + .connection() + .get_default_channel() + .expect("Expected some default channel") +} + +pub fn get_default_server_channel(app: &App) -> ChannelId { + let server = app.world.resource::<Server>(); + server + .endpoint() + .get_default_channel() + .expect("Expected some default channel") +} + +pub fn close_client_channel(channel_id: ChannelId, app: &mut App) { + let mut client = app.world.resource_mut::<Client>(); + client + .connection_mut() + .close_channel(channel_id) + .expect("Failed to close channel") +} + +pub fn close_server_channel(channel_id: ChannelId, app: &mut App) { + let mut server = app.world.resource_mut::<Server>(); + server + .endpoint_mut() + .close_channel(channel_id) + .expect("Failed to close channel") +} + +pub fn open_client_channel(channel_type: ChannelType, app: &mut App) -> ChannelId { + let mut client = app.world.resource_mut::<Client>(); + client + .connection_mut() + .open_channel(channel_type) + .expect("Failed to open channel") +} + +pub fn open_server_channel(channel_type: ChannelType, app: &mut App) -> ChannelId { + let mut server = app.world.resource_mut::<Server>(); + server + .endpoint_mut() + .open_channel(channel_type) + .expect("Failed to open channel") +} + +pub fn wait_for_client_message(client_id: ClientId, server_app: &mut App) -> SharedMessage { + let mut server = server_app.world.resource_mut::<Server>(); + + loop { + sleep(Duration::from_secs_f32(0.05)); + match server + .endpoint_mut() + .receive_message_from::<SharedMessage>(client_id) + { + Ok(Some(msg)) => return msg, + Ok(None) => (), + Err(_) => panic!("Deserialization should be correct"), + } + } +} + +pub fn wait_for_server_message(client_app: &mut App) -> SharedMessage { + let mut client = client_app.world.resource_mut::<Client>(); + + loop { + sleep(Duration::from_secs_f32(0.05)); + match client.connection_mut().receive_message::<SharedMessage>() { + Ok(Some(msg)) => return msg, + Ok(None) => (), + Err(_) => panic!("Deserialization should be correct"), + } + } +} + +pub fn send_and_test_client_message( + client_id: ClientId, + channel: ChannelId, + client_app: &mut App, + server_app: &mut App, + msg_counter: &mut u64, +) { + *msg_counter += 1; + let client_message = SharedMessage::TestMessage( + format!( + "Test message from client {}. Counter: {}", + client_id, msg_counter + ) + .to_string(), + ); + + let client = client_app.world.resource_mut::<Client>(); + client + .connection() + .send_message_on(channel, client_message.clone()) + .unwrap(); + + let server_received = wait_for_client_message(client_id, server_app); + assert_eq!(client_message, server_received); +} + +pub fn send_and_test_server_message( + client_id: ClientId, + channel: ChannelId, + server_app: &mut App, + client_app: &mut App, + msg_counter: &mut u64, +) { + *msg_counter += 1; + let server_message = SharedMessage::TestMessage( + format!( + "Test message from server to client {}. Counter: {}", + client_id, msg_counter + ) + .to_string(), + ); + + let mut server = server_app.world.resource_mut::<Server>(); + server + .endpoint_mut() + .send_message_on(client_id, channel, server_message.clone()) + .unwrap(); + + let client_received = wait_for_server_message(client_app); + assert_eq!(server_message, client_received); +} |