diff --git a/client/src/dispatcher.rs b/client/src/dispatcher.rs index 9c1ebdb..f19bee5 100644 --- a/client/src/dispatcher.rs +++ b/client/src/dispatcher.rs @@ -77,6 +77,12 @@ impl Dispatcher { handler: LoginStatusRequestHandler::default(), })) } + Message::ServerResponse(ServerResponse::PeerAddressResponse( + response, + )) => Some(Box::new(DispatchedMessage { + message: response, + handler: PeerAddressResponseHandler::default(), + })), Message::ServerResponse(ServerResponse::PrivilegedUsersResponse( response, )) => Some(Box::new(DispatchedMessage { diff --git a/client/src/handlers/mod.rs b/client/src/handlers/mod.rs index aa6dbc6..5a69d8f 100644 --- a/client/src/handlers/mod.rs +++ b/client/src/handlers/mod.rs @@ -1,4 +1,5 @@ mod login_status_request_handler; +mod peer_address_response_handler; mod peer_connect_request_handler; mod privileged_users_response_handler; mod room_join_request_handler; @@ -10,6 +11,7 @@ mod room_message_response_handler; mod user_list_request_handler; pub use login_status_request_handler::LoginStatusRequestHandler; +pub use peer_address_response_handler::PeerAddressResponseHandler; pub use peer_connect_request_handler::PeerConnectRequestHandler; pub use privileged_users_response_handler::PrivilegedUsersResponseHandler; pub use room_join_request_handler::RoomJoinRequestHandler; diff --git a/client/src/handlers/peer_address_response_handler.rs b/client/src/handlers/peer_address_response_handler.rs new file mode 100644 index 0000000..67f7331 --- /dev/null +++ b/client/src/handlers/peer_address_response_handler.rs @@ -0,0 +1,127 @@ +use std::net::{SocketAddr, SocketAddrV4}; + +use anyhow::bail; +use solstice_proto::server::PeerAddressResponse; + +use crate::context::Context; +use crate::message_handler::MessageHandler; +use crate::peer::PeerState; + +#[derive(Debug, Default)] +pub struct PeerAddressResponseHandler; + +impl MessageHandler for PeerAddressResponseHandler { + type Message = PeerAddressResponse; + + fn run( + self, + context: &mut Context, + response: &PeerAddressResponse, + ) -> anyhow::Result<()> { + match context.state.peers.peers.get_mut(&response.user_name) { + Some(state @ &mut PeerState::FetchingAddress) => { + *state = PeerState::Opening { + address: SocketAddr::V4(SocketAddrV4::new( + response.ip, + response.port, + )), + }; + } + entry => bail!("unexpected peer state entry: {:?}", entry), + }; + + // TODO: try opening a connection to the peer. Queue an event that will be + // handled later if that fails. + // + // Question: should dispatcher_tx be added to Context? Otherwise, how would + // the asynchronous open task notify the context it is done? If we add it, + // then how does the dispatcher notice it should stop running? + + Ok(()) + } + + fn name() -> String { + "PeerAddressResponseHandler".to_string() + } +} + +#[cfg(test)] +mod tests { + use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + + use solstice_proto::server::PeerAddressResponse; + + use crate::context::{ContextBundle, ContextOptions}; + use crate::message_handler::MessageHandler; + use crate::peer::PeerState; + + use super::PeerAddressResponseHandler; + + #[test] + fn run_missing_state() { + let mut options = ContextOptions::default(); + options.initial_state.peers.peers.insert( + "aisha".to_string(), + PeerState::Opening { + address: "1.2.3.4:42".parse().unwrap(), + }, + ); + + let mut bundle = ContextBundle::new(options); + + let response = PeerAddressResponse { + user_name: "aisha".to_string(), + ip: Ipv4Addr::new(1, 2, 3, 4), + port: 42, + }; + + PeerAddressResponseHandler::default() + .run(&mut bundle.context, &response) + .unwrap_err(); + } + + #[test] + fn run_wrong_state() { + let mut bundle = ContextBundle::default(); + + let response = PeerAddressResponse { + user_name: "aisha".to_string(), + ip: Ipv4Addr::new(1, 2, 3, 4), + port: 42, + }; + + PeerAddressResponseHandler::default() + .run(&mut bundle.context, &response) + .unwrap_err(); + } + + #[test] + fn run_success() { + let mut options = ContextOptions::default(); + options + .initial_state + .peers + .peers + .insert("aisha".to_string(), PeerState::FetchingAddress); + + let mut bundle = ContextBundle::new(options); + + let response = PeerAddressResponse { + user_name: "aisha".to_string(), + ip: Ipv4Addr::new(1, 2, 3, 4), + port: 42, + }; + + PeerAddressResponseHandler::default() + .run(&mut bundle.context, &response) + .unwrap(); + + match bundle.context.state.peers.peers.get("aisha") { + Some(PeerState::Opening { address }) => assert_eq!( + address, + &SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 42)) + ), + entry => panic!("unexpected peer state entry: {:?}", entry), + }; + } +} diff --git a/client/src/handlers/peer_connect_request_handler.rs b/client/src/handlers/peer_connect_request_handler.rs index 1175dbd..4fd16c4 100644 --- a/client/src/handlers/peer_connect_request_handler.rs +++ b/client/src/handlers/peer_connect_request_handler.rs @@ -66,7 +66,7 @@ mod tests { match bundle.context.state.peers.peers.get("aisha") { Some(PeerState::FetchingAddress) => (), - None => panic!("Missing state"), + entry => panic!("Unexpected entry: {:?}", entry), } drop(bundle.context.server_request_tx); diff --git a/client/src/peer.rs b/client/src/peer.rs index bf88e54..8614cfe 100644 --- a/client/src/peer.rs +++ b/client/src/peer.rs @@ -1,6 +1,7 @@ //! This module provides abstractions for interacting with a set of peers. use std::collections::HashMap; +use std::net::SocketAddr; // Peer states: // @@ -44,10 +45,8 @@ use std::collections::HashMap; #[derive(Debug)] pub enum PeerState { FetchingAddress, + Opening { address: SocketAddr }, /* - Opening { - address: SocketAddr, - }, Waiting { address: SocketAddr, token: u32, diff --git a/proto/src/server/response.rs b/proto/src/server/response.rs index 2f4a4ea..cec294e 100644 --- a/proto/src/server/response.rs +++ b/proto/src/server/response.rs @@ -441,9 +441,9 @@ impl ValueDecode for ParentSpeedRatioResponse { #[derive(Clone, Debug, Eq, PartialEq)] pub struct PeerAddressResponse { - user_name: String, - ip: net::Ipv4Addr, - port: u16, + pub user_name: String, + pub ip: net::Ipv4Addr, + pub port: u16, } impl From for ServerResponse {