#[macro_use] extern crate anyhow; pub use anyhow::{ Result, Error }; use std::{ convert::Infallible, collections::HashMap, net::SocketAddr, sync::Arc, }; mod types; mod conn; mod minesweeper; use types::*; use hyper::{ Body, Request, Server }; use hyper::server::conn::{ AddrStream, AddrIncoming }; use hyper::service::{make_service_fn, service_fn, Service}; use tokio::sync::RwLock; use std::fs; use futures_util::{future, pin_mut, ready, stream::TryStreamExt, StreamExt, sink::SinkExt, TryFutureExt}; use hyper_tungstenite::{ tungstenite::protocol::Message, HyperWebsocket }; use tokio_rustls::{ rustls, Accept, server::TlsStream, TlsAcceptor }; use rustls::{ServerConnection, SupportedCipherSuite, ProtocolVersion, ServerConfig, Certificate, PrivateKey}; fn main() -> Result<()> { let conf = Config { cert_path: "./cert.pem".to_owned(), pkey_path: "./cert.rsa".to_owned(), page_path: "./page.html".to_owned(), socket_addr: ([0,0,0,0],31235).into(), }; let state = State { conf, peers: PeerMap::new(RwLock::new(HashMap::new())), }; let cert_pem = fs::read(&state.conf.cert_path)?; let pkey_pem = fs::read(&state.conf.pkey_path)?; // Build TLS configuration. let tls_cfg = { let certs: Vec = rustls_pemfile::certs(&mut &*cert_pem) .map(|mut certs| certs.drain(..).map(Certificate).collect())?; if certs.len() < 1 { return Err(anyhow!("No certificates found")); } let mut keys: Vec = rustls_pemfile::rsa_private_keys(&mut &*pkey_pem) .map(|mut keys| keys.drain(..).map(PrivateKey).collect())?; if keys.len() < 1 { return Err(anyhow!("No private keys found")); } let mut tls_cfg = ServerConfig::builder() .with_safe_defaults() .with_no_client_auth() .with_single_cert(certs, keys.remove(0))?; // Configure ALPN to accept HTTP/2, HTTP/1.1 in that order. tls_cfg.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; tls_cfg }; tokio_main(state, tls_cfg) } #[tokio::main] async fn tokio_main(state: State, tls_conf: ServerConfig) -> Result<()> { let server = main_server(state.clone(), tls_conf); println!("Serving on {}", state.conf.socket_addr); server.await?; Ok(()) } async fn main_server(state: State, tls_conf: ServerConfig) -> Result<()> { // Create a TCP listener, that uses TLS let listener = tokio::net::TcpListener::bind(&state.conf.socket_addr).await?; let local_addr = listener.local_addr()?; let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(tls_conf)); let http = hyper::server::conn::Http::new(); // Start the temporary single lobby let (cmd_tx, cmd_rx) = futures::channel::mpsc::unbounded(); let game_t = tokio::spawn(gameloop(cmd_rx, state.peers.clone())); loop { let (conn, remote_addr) = listener.accept().await?; let acceptor = acceptor.clone(); let http = http.clone(); let cloned_state = state.clone(); let cmd_tx = cmd_tx.clone(); let fut = async move { match acceptor.accept(conn).await { Ok(stream) => { let page = tokio::fs::read_to_string(&cloned_state.conf.page_path).await; if let Err(e) = page { return Err(anyhow!("couldn't read page file: {e}")); } let handler = conn::PerConnHandler { state: cloned_state, local_addr, remote_addr, game_cmd_tx: cmd_tx, page: page.unwrap(), }; if let Err(e) = http.serve_connection(stream, handler).await { eprintln!("hyper error for {remote_addr}: {e}"); } }, Err(e) => eprintln!("TLS error for {remote_addr}: {e}"), } Ok(()) }; tokio::spawn(fut); } } // If a move is made, broadcast new board, else just send current board async fn gameloop(mut move_rx: futures::channel::mpsc::UnboundedReceiver, peers: PeerMap) { use minesweeper::*; let mut game = Game::new(Board::new(75,35), (75*35)/8); let mut latest_player_name = None; while let Some(req) = move_rx.next().await { let done = game.phase == Phase::Die || game.phase == Phase::Win; match req { MetaMove::Move(m, o) => if !done { game = game.act(m); if game.phase == Phase::Win || game.phase == Phase::Die { game.board = game.board.grade(); } latest_player_name = peers.read().await.get(&o).map(|p| p.name.clone()); }, MetaMove::Dump => (), MetaMove::Reset => { game = Game::new(Board::new(75,35), (75*35)/8); }, } let mut reply = vec![Message::binary(game.board.render())]; let lpname = latest_player_name.as_ref().map(|s| s.as_str()).unwrap_or("unknown player"); match game.phase { Phase::Win => { reply.push(Message::text(format!("win {lpname}"))); }, Phase::Die => { reply.push(Message::text(format!("lose {lpname}"))); }, _ => (), } { let peers = peers.read().await; for (addr, p) in peers.iter() { for r in reply.iter() { if let Err(e) = p.tx.unbounded_send(r.clone()) { println!("couldn't send game update {r} to {addr}: {e}"); } } } } } } fn error(err: String) -> std::io::Error { std::io::Error::new(std::io::ErrorKind::Other, err) } async fn shutdown_signal() { tokio::signal::ctrl_c() .await .expect("failed to install CTRL+C signal handler"); }