diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 31286c6..c57f2a6 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -464,11 +464,14 @@ dependencies = [ "crossterm", "env_logger", "futures", + "hostname", + "hyper-rustls", "lazy_static", "log", "num", "num-derive", "num-traits", + "rustls", "sea_battle_backend", "serde_json", "serde_urlencoded", @@ -505,6 +508,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -891,6 +904,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.8" @@ -902,6 +926,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.8.0" @@ -920,6 +955,44 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + [[package]] name = "iana-time-zone" version = "0.1.51" @@ -1059,6 +1132,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "memchr" version = "2.5.0" @@ -1204,6 +1283,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "os_str_bytes" version = "6.3.0" @@ -1361,6 +1446,21 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1370,12 +1470,55 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +dependencies = [ + "base64", +] + [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1388,6 +1531,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sea_battle_backend" version = "0.1.0" @@ -1411,6 +1564,29 @@ dependencies = [ "uuid", ] +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.14" @@ -1571,6 +1747,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.10.0" @@ -1693,6 +1875,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-tungstenite" version = "0.17.2" @@ -1701,8 +1894,12 @@ checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" dependencies = [ "futures-util", "log", + "rustls", + "rustls-native-certs", "tokio", + "tokio-rustls", "tungstenite", + "webpki", ] [[package]] @@ -1719,6 +1916,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -1740,6 +1943,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "tui" version = "0.19.0" @@ -1766,10 +1975,12 @@ dependencies = [ "httparse", "log", "rand", + "rustls", "sha-1", "thiserror", "url", "utf-8", + "webpki", ] [[package]] @@ -1821,6 +2032,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -1853,6 +2070,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1913,6 +2140,26 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/rust/cli_player/Cargo.toml b/rust/cli_player/Cargo.toml index 75e7e03..74c371c 100644 --- a/rust/cli_player/Cargo.toml +++ b/rust/cli_player/Cargo.toml @@ -18,7 +18,10 @@ num = "0.4.0" num-traits = "0.2.15" num-derive = "0.3.3" textwrap = "0.15.1" -tokio-tungstenite = "0.17.2" +tokio-tungstenite = { version = "0.17.2", features = ["__rustls-tls", "rustls-tls-native-roots"] } serde_urlencoded = "0.7.1" futures = "0.3.23" serde_json = "1.0.85" +hostname = "0.3.1" +rustls = "0.20.6" +hyper-rustls = { version = "0.23.0", features = ["rustls-native-certs"] } \ No newline at end of file diff --git a/rust/cli_player/src/cli_args.rs b/rust/cli_player/src/cli_args.rs index ec84993..65268c8 100644 --- a/rust/cli_player/src/cli_args.rs +++ b/rust/cli_player/src/cli_args.rs @@ -13,15 +13,14 @@ pub enum TestDevScreen { #[derive(Parser, Debug)] pub struct CliArgs { - // TODO: switch default sever uri to real one when we get one /// Upstream server to use #[clap( short, long, value_parser, - default_value = "https://fixme.communiquons.org" + default_value = "https://seabattleapi.communiquons.org" )] - pub server_uri: String, + pub remote_server_uri: String, /// Local server listen address #[clap(short, long, default_value = "127.0.0.1:5679")] diff --git a/rust/cli_player/src/client.rs b/rust/cli_player/src/client.rs index e39db11..ea7b02f 100644 --- a/rust/cli_player/src/client.rs +++ b/rust/cli_player/src/client.rs @@ -2,12 +2,14 @@ use crate::cli_args::cli_args; use crate::server; use futures::stream::{SplitSink, SplitStream}; use futures::{SinkExt, StreamExt}; +use hyper_rustls::ConfigBuilderExt; use sea_battle_backend::data::GameRules; use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage}; -use sea_battle_backend::server::BotPlayQuery; +use sea_battle_backend::server::{BotPlayQuery, PlayRandomQuery}; use sea_battle_backend::utils::{boxed_error, Res}; -use std::sync::mpsc; +use std::fmt::Display; use std::sync::mpsc::TryRecvError; +use std::sync::{mpsc, Arc}; use tokio::net::TcpStream; use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; @@ -44,14 +46,41 @@ impl Client { .await } + /// Start to play against a random player + pub async fn start_random_play(player_name: D) -> Res { + Self::connect_url( + &cli_args().remote_server_uri, + &format!( + "/play/random?{}", + serde_urlencoded::to_string(&PlayRandomQuery { + player_name: player_name.to_string() + }) + .unwrap() + ), + ) + .await + } + /// Do connect to a server, returning async fn connect_url(server: &str, uri: &str) -> Res { - let mut url = server.replace("http", "ws"); - url.push_str(uri); - log::debug!("Connecting to {}", url); + let mut ws_url = server.replace("http", "ws"); + ws_url.push_str(uri); + log::debug!("Connecting to {}", ws_url); + + let (socket, _) = if ws_url.starts_with("wss") { + // Perform a connection over TLS + let config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_native_roots() + .with_no_client_auth(); + let connector = tokio_tungstenite::Connector::Rustls(Arc::new(config)); + + tokio_tungstenite::connect_async_tls_with_config(ws_url, None, Some(connector)).await? + } else { + // Perform an unsecure connection + tokio_tungstenite::connect_async(ws_url).await? + }; - // Connect to websocket & split streams - let (socket, _) = tokio_tungstenite::connect_async(url).await?; let (sink, mut stream) = socket.split(); // Receive server message on a separate task diff --git a/rust/cli_player/src/main.rs b/rust/cli_player/src/main.rs index 91e84da..2c53724 100644 --- a/rust/cli_player/src/main.rs +++ b/rust/cli_player/src/main.rs @@ -17,11 +17,13 @@ use cli_player::client::Client; use cli_player::server::start_server_if_missing; use cli_player::ui_screens::configure_game_rules::GameRulesConfigurationScreen; use cli_player::ui_screens::game_screen::GameScreen; +use cli_player::ui_screens::input_screen::InputScreen; use cli_player::ui_screens::popup_screen::PopupScreen; use cli_player::ui_screens::select_play_mode_screen::{SelectPlayModeResult, SelectPlayModeScreen}; use cli_player::ui_screens::*; use sea_battle_backend::data::GameRules; use sea_battle_backend::human_player_ws::ServerMessage; +use sea_battle_backend::utils::Res; /// Test code screens async fn run_dev( @@ -70,17 +72,53 @@ async fn run_dev( ))? } -async fn run_app(terminal: &mut Terminal) -> Result<(), Box> { +/// Ask the user to specify its username +fn query_username(terminal: &mut Terminal) -> Res { + let hostname = hostname::get()?.to_string_lossy().to_string(); + + let res = + InputScreen::new("Please specify the name to which other players should identify you:") + .set_title("Player name") + .set_value(&hostname) + .show(terminal)?; + + Ok(res.value().unwrap_or(hostname)) +} + +async fn run_app(terminal: &mut Terminal) -> Res { if let Some(d) = cli_args().dev_screen { return run_dev(terminal, d).await; } let mut rules = GameRules::default(); + let mut username = "".to_string(); + loop { - match SelectPlayModeScreen::default().show(terminal)? { - // TODO : Play against random player - ScreenResult::Ok(SelectPlayModeResult::PlayRandom) => todo!(), + let choice = SelectPlayModeScreen::default().show(terminal)?; + + if let ScreenResult::Ok(c) = choice { + if c.need_user_name() && username.is_empty() { + username = query_username(terminal)?; + } + } + + match choice { + ScreenResult::Ok(SelectPlayModeResult::PlayRandom) => { + PopupScreen::new("Connecting...").show_once(terminal)?; + + let client = Client::start_random_play(&username).await?; + PopupScreen::new("Waiting for opponent...").show_once(terminal)?; + + // Wait for the server to become ready + while !matches!( + client.recv_next_message().await?, + ServerMessage::OpponentConnected + ) {} + + // Display game screen + GameScreen::new(client).show(terminal).await?; + } // Play against bot ScreenResult::Ok(SelectPlayModeResult::PlayAgainstBot) => { diff --git a/rust/cli_player/src/ui_screens/input_screen.rs b/rust/cli_player/src/ui_screens/input_screen.rs index 7c83d8f..b85ff31 100644 --- a/rust/cli_player/src/ui_screens/input_screen.rs +++ b/rust/cli_player/src/ui_screens/input_screen.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use std::io; use std::time::{Duration, Instant}; use tui::style::*; @@ -49,6 +50,11 @@ impl<'a> InputScreen<'a> { self } + pub fn set_value(mut self, value: D) -> Self { + self.value = value.to_string(); + self + } + /// Get error contained in input fn error(&self) -> Option<&'static str> { if self.value.len() > self.max_len { diff --git a/rust/cli_player/src/ui_screens/mod.rs b/rust/cli_player/src/ui_screens/mod.rs index 906f190..98be9c5 100644 --- a/rust/cli_player/src/ui_screens/mod.rs +++ b/rust/cli_player/src/ui_screens/mod.rs @@ -17,6 +17,13 @@ pub enum ScreenResult { } impl ScreenResult { + pub fn value(self) -> Option { + match self { + ScreenResult::Ok(v) => Some(v), + ScreenResult::Canceled => None, + } + } + pub fn as_string(&self) -> String { format!("{:#?}", self) } diff --git a/rust/cli_player/src/ui_screens/select_play_mode_screen.rs b/rust/cli_player/src/ui_screens/select_play_mode_screen.rs index 257120d..e49ec81 100644 --- a/rust/cli_player/src/ui_screens/select_play_mode_screen.rs +++ b/rust/cli_player/src/ui_screens/select_play_mode_screen.rs @@ -20,6 +20,13 @@ pub enum SelectPlayModeResult { Exit, } +impl SelectPlayModeResult { + /// Specify whether a selected play mode requires a user name or not + pub fn need_user_name(&self) -> bool { + self != &SelectPlayModeResult::PlayAgainstBot && self != &SelectPlayModeResult::Exit + } +} + #[derive(Debug, Clone)] struct PlayModeDescription { name: &'static str,