summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: cafc9d41d755134f42851d01d76466e060ef01bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#[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<Certificate> = 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<PrivateKey> = 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<MetaMove>, 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");
}