This commit is contained in:
core 2023-04-09 11:25:42 -04:00
parent 01d8dceedc
commit 9060a0fa0b
Signed by: core
GPG Key ID: FDBF740DADDCEECF
12 changed files with 282 additions and 116 deletions

67
Cargo.lock generated
View File

@ -62,8 +62,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
name = "client" name = "client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_log",
"futures",
"js-sys", "js-sys",
"log",
"tokio",
"tokio-tungstenite",
"tungstenite",
"url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures",
"web-sys", "web-sys",
] ]
@ -78,6 +86,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "console_log"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
dependencies = [
"log",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.6" version = "0.2.6"
@ -130,6 +149,7 @@ checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor",
"futures-io", "futures-io",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
@ -152,12 +172,34 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.28" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.28" version = "0.3.28"
@ -176,9 +218,13 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io",
"futures-macro",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab", "slab",
@ -362,6 +408,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.6" version = "0.8.6"
@ -451,8 +503,11 @@ dependencies = [
name = "protocol" name = "protocol"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"futures",
"rmp-serde", "rmp-serde",
"serde", "serde",
"tokio-tungstenite",
"tungstenite",
] ]
[[package]] [[package]]
@ -878,6 +933,18 @@ dependencies = [
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.84" version = "0.2.84"

View File

@ -12,3 +12,11 @@ crate-type = ["cdylib"]
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
js-sys = "0.3" js-sys = "0.3"
web-sys = { version = "0.3", features = ["CanvasRenderingContext2d", "Document", "Element", "HtmlCanvasElement", "Window"]} web-sys = { version = "0.3", features = ["CanvasRenderingContext2d", "Document", "Element", "HtmlCanvasElement", "Window"]}
console_log = { version = "1", features = ["color"] }
log = "0.4"
tungstenite = { version = "0.18", default-features = false }
tokio-tungstenite = { version = "0.18" }
tokio = { version = "1.27", features = ["macros", "sync", "rt-multi-thread"] }
futures = { version = "0.3", default-features = false }
wasm-bindgen-futures = "0.4"
url = "2.3"

View File

@ -1,3 +1,7 @@
use std::error::Error;
use futures::StreamExt;
use log::{debug, error, info, Level};
use tokio_tungstenite::connect_async;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
@ -7,6 +11,34 @@ extern {
#[wasm_bindgen] #[wasm_bindgen]
pub fn send_chat(chat: &str) { pub fn send_chat(chat: &str) {
println!("sending chat: {}", chat); info!("sending chat: {}", chat);
} }
#[wasm_bindgen]
pub async fn rust_init(gateway: &str, username: &str) {
console_log::init_with_level(Level::Debug).unwrap();
info!("Logger setup successfully");
match init(gateway, username).await {
Ok(_) => (),
Err(e) => {
error!("Error initializing gateway client: {}", e);
return;
}
}
info!("Gateway client initialized successfully");
}
pub async fn init(gateway: &str, username: &str) -> Result<(), Box<dyn Error>> {
info!("FAST CONNECT: {}", gateway);
let gateway_url = url::Url::parse(gateway)?;
debug!("Gateway URL parsed");
let (ws_stream, _) = connect_async(gateway_url).await?;
debug!("Connected to gateway socket");
let (tx, rx) = ws_stream.split();
debug!("Split stream, handshaking with server");
Ok(())
}

View File

@ -8,3 +8,6 @@ edition = "2021"
[dependencies] [dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
rmp-serde = "1.1" rmp-serde = "1.1"
tungstenite = { version = "0.18", default-features = false }
tokio-tungstenite = { version = "0.18" }
futures = "0.3"

View File

@ -1,5 +1,8 @@
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
#[macro_use]
pub mod macros;
pub const PROTOCOL_VERSION: u32 = 1; pub const PROTOCOL_VERSION: u32 = 1;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]

54
protocol/src/macros.rs Normal file
View File

@ -0,0 +1,54 @@
use std::error::Error;
use std::io;
use futures::{AsyncRead, AsyncWrite, FutureExt, Stream, StreamExt};
use futures::stream::SplitStream;
use serde::{Deserialize, Serialize};
use tokio_tungstenite::WebSocketStream;
use tungstenite::Message;
#[macro_export]
macro_rules! send {
($writer:expr,$pkt:expr) => {
$writer.send($crate::macros::__generic_packet_to_message($pkt).unwrap())
};
}
#[macro_export]
macro_rules! recv {
($reader:expr) => {
{
if let Some(future_result) = $reader.next().now_or_never() {
if let Some(msg) = future_result {
match msg {
Ok(msg) => {
if msg.is_binary() {
match rmp_serde::from_slice(&msg.into_data()) {
Ok(d) => Ok(Some(d)),
Err(e) => {
log::error!("error deserializing message: {}", e);
Ok(None)
}
}
} else {
Ok(None)
}
},
Err(e) => {
log::error!("error receiving message: {}", e);
Ok(None)
}
}
} else {
log::error!("pipe closed");
Err("Pipe closed")
}
} else {
Ok(None)
}
}
}
}
pub fn __generic_packet_to_message<T: Serialize>(pkt: &T) -> Result<Message, rmp_serde::encode::Error> {
rmp_serde::to_vec(&pkt).map(Message::from)
}

View File

@ -0,0 +1,99 @@
use std::error::Error;
use std::net::SocketAddr;
use futures::stream::{SplitSink, SplitStream};
use futures::{FutureExt, SinkExt, StreamExt};
use hyper::upgrade::Upgraded;
use log::{error, info};
use tokio::sync::mpsc::Receiver;
use tokio_tungstenite::WebSocketStream;
use tungstenite::Message;
use protocol::{GoodbyeReason, MessageC2S, MessageS2C, PROTOCOL_VERSION, ps2c, recv, send, State};
use crate::handler::{ClientHandlerMessage, ClientManager};
pub async fn handle_client(mgr: ClientManager, remote_addr: SocketAddr, mut rx: Receiver<ClientHandlerMessage>, mut client_tx: SplitSink<WebSocketStream<Upgraded>, Message>, mut client_rx: SplitStream<WebSocketStream<Upgraded>>) -> Result<(), Box<dyn Error>> {
let mut state = State::Handshake;
loop {
if let Some(msg) = rx.recv().await {
match msg {
ClientHandlerMessage::Tick => {} // this intentionally does nothing
}
} else {
info!("channel closed, shutting down");
break;
}
if let Some(pkt) = recv!(client_rx)? {
match state {
State::Handshake => {
match pkt {
MessageC2S::Hello { version, requested_username, next_state } => {
if !matches!(next_state, State::Play) {
error!("client sent unexpected state {:?} (expected: Play)", next_state);
send!(client_tx, &MessageS2C::Goodbye {
reason: GoodbyeReason::UnexpectedNextState,
}).await?;
break;
}
// check version
if version != PROTOCOL_VERSION {
error!("client sent incompatible version {} (expected: {})", version, PROTOCOL_VERSION);
send!(client_tx, &MessageS2C::Goodbye {
reason: GoodbyeReason::UnsupportedProtocol {
supported: PROTOCOL_VERSION,
got: version,
},
}).await?;
break;
}
// determine if we can give them that username
{
if mgr.usernames.read().await.values().any(|u| *u == requested_username) {
error!("client requested username {} but it is in use", requested_username);
send!(client_tx, &MessageS2C::Goodbye {
reason: GoodbyeReason::UsernameTaken,
}).await?;
break;
}
}
// username is fine
{
mgr.usernames.write().await.insert(remote_addr, requested_username.clone());
}
send!(client_tx, &MessageS2C::Hello {
version,
given_username: requested_username,
next_state,
}).await?;
},
MessageC2S::Goodbye { reason } => {
info!("client sent goodbye: {:?}", reason);
break;
}
}
}
State::Play => {
match pkt {
MessageC2S::Hello { .. } => {
error!("client sent unexpected packet {:?} for state {:?}", pkt, state);
send!(client_tx, &MessageS2C::Goodbye {
reason: GoodbyeReason::UnexpectedPacket,
}).await?;
break;
}
MessageC2S::Goodbye { reason } => {
info!("client sent goodbye: {:?}", reason);
break;
}
}
}
}
}
}
Ok(())
}

View File

@ -1,8 +1,11 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use serde::Serialize;
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tungstenite::Message;
use protocol::State; use protocol::State;
#[derive(Clone)] #[derive(Clone)]

View File

@ -11,9 +11,9 @@ use log::{error, info};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use protocol::State; use protocol::State;
use crate::handler::{ClientHandler, ClientManager}; use crate::handler::{ClientHandler, ClientManager};
use crate::wsserver::handle_client; use crate::client_handler::handle_client;
pub mod wsserver; pub mod client_handler;
pub mod handler; pub mod handler;
pub mod timer; pub mod timer;
@ -32,6 +32,7 @@ async fn handle_request(mut request: Request<Body>, remote_addr: SocketAddr, mgr
match upgrade::on(&mut request).await { match upgrade::on(&mut request).await {
//if successfully upgraded //if successfully upgraded
Ok(upgraded) => { Ok(upgraded) => {
//create a websocket stream from the upgraded object //create a websocket stream from the upgraded object
let ws_stream = WebSocketStream::from_raw_socket( let ws_stream = WebSocketStream::from_raw_socket(
//pass the upgraded object //pass the upgraded object

View File

@ -1,106 +0,0 @@
use std::error::Error;
use std::net::SocketAddr;
use futures::stream::{SplitSink, SplitStream};
use futures::{SinkExt, StreamExt};
use hyper::upgrade::Upgraded;
use log::{error, info};
use tokio::sync::mpsc::Receiver;
use tokio_tungstenite::WebSocketStream;
use tungstenite::Message;
use protocol::{GoodbyeReason, MessageC2S, MessageS2C, PROTOCOL_VERSION, ps2c, State};
use crate::handler::{ClientHandler, ClientHandlerMessage, ClientManager};
pub async fn handle_client(mgr: ClientManager, remote_addr: SocketAddr, mut rx: Receiver<ClientHandlerMessage>, mut write: SplitSink<WebSocketStream<Upgraded>, Message>, mut read: SplitStream<WebSocketStream<Upgraded>>) -> Result<(), Box<dyn Error>> {
let mut state = State::Handshake;
loop {
if let Some(msg) = rx.recv().await {
match msg {
ClientHandlerMessage::Tick => {} // this intentionally does nothing
}
} else {
info!("channel closed, shutting down");
break;
}
if let Some(msg) = read.next().await {
let msg = msg?;
if msg.is_binary() {
// try to deserialize the msg
let pkt: MessageC2S = rmp_serde::from_slice(&msg.into_data())?;
match state {
State::Handshake => {
match pkt {
MessageC2S::Hello { version, requested_username, next_state } => {
if !matches!(next_state, State::Play) {
error!("client sent unexpected state {:?} (expected: Play)", next_state);
write.send(Message::from(ps2c(&MessageS2C::Goodbye {
reason: GoodbyeReason::UnexpectedNextState,
}))).await?;
break;
}
// check version
if version != PROTOCOL_VERSION {
error!("client sent incompatible version {} (expected: {})", version, PROTOCOL_VERSION);
write.send(Message::from(ps2c(&MessageS2C::Goodbye {
reason: GoodbyeReason::UnsupportedProtocol {
supported: PROTOCOL_VERSION,
got: version,
},
}))).await?;
break;
}
// determine if we can give them that username
{
if mgr.usernames.read().await.values().into_iter().any(|u| *u == requested_username) {
error!("client requested username {} but it is in use", requested_username);
write.send(Message::from(ps2c(&MessageS2C::Goodbye {
reason: GoodbyeReason::UsernameTaken,
}))).await?;
break;
}
}
// username is fine
{
mgr.usernames.write().await.insert(remote_addr, requested_username.clone());
}
write.send(Message::from(ps2c(&MessageS2C::Hello {
version,
given_username: requested_username,
next_state,
}))).await?;
},
MessageC2S::Goodbye { reason } => {
info!("client sent goodbye: {:?}", reason);
break;
}
}
}
State::Play => {
match pkt {
MessageC2S::Hello { .. } => {
error!("client sent unexpected packet {:?} for state {:?}", pkt, state);
write.send(Message::from(ps2c(&MessageS2C::Goodbye {
reason: GoodbyeReason::UnexpectedPacket,
}))).await?;
break;
}
MessageC2S::Goodbye { reason } => {
info!("client sent goodbye: {:?}", reason);
break;
}
}
}
}
}
}
}
Ok(())
}

View File

@ -9,10 +9,10 @@
<script type="module"> <script type="module">
// If you're getting build errors here | you need to run `just build_client_bundle` first, to compile client code // If you're getting build errors here | you need to run `just build_client_bundle` first, to compile client code
// v // v
import init, { greet } from "./dist/pkg"; import init from "./dist/client.js";
init().then(() => { init().then(() => {
greet("WebAssembly"); // wasm-pack code here
}) })
</script> </script>
</body> </body>

View File

@ -13,17 +13,19 @@
<div id="chats"> <div id="chats">
<p>hello: blsdkjf</p> <p>hello: blsdkjf</p>
</div> </div>
<input type="text" placeholder="chat text goes here" /> <input id="chat-value" type="text" placeholder="chat text goes here" />
<button id="chat-submit">submit</button> <button id="chat-submit">submit</button>
</div> </div>
<script type="module"> <script type="module">
// If you're getting build errors here | you need to run `just build_client_bundle` first, to compile client code // If you're getting build errors here | you need to run `just build_client_bundle` first, to compile client code
// v // v
import init, { send_chat } from "./dist/client.js"; import init, { rust_init, send_chat } from "./dist/client.js";
init().then(() => { init().then(() => {
rust_init();
document.getElementById("chat-submit").addEventListener("click", e => { document.getElementById("chat-submit").addEventListener("click", e => {
send_chat(e.target.value); send_chat(document.getElementById("chat-value").value);
}) })
}) })
</script> </script>