diff --git a/Cargo.lock b/Cargo.lock index 8072621..e0f98c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "bitflags" version = "1.3.2" @@ -1543,6 +1549,7 @@ name = "wstcp-client" version = "1.0.0" dependencies = [ "async-compression", + "base64", "bytes", "console_error_panic_hook", "either", @@ -1555,6 +1562,7 @@ dependencies = [ "js-sys", "penguin-mux-wasm", "pin-project-lite", + "rand", "ring", "tokio", "tokio-rustls", diff --git a/client/Cargo.toml b/client/Cargo.toml index 8476476..acdb819 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -31,6 +31,8 @@ either = "1.9.0" tokio-util = { version = "0.7.10", features = ["io"] } async-compression = { version = "0.4.5", features = ["tokio", "gzip", "brotli"] } fastwebsockets = { version = "0.6.0", features=[]} +rand = "0.8.5" +base64 = "0.21.7" [dependencies.getrandom] features = ["js"] diff --git a/client/build.sh b/client/build.sh index fa14638..e3e9234 100755 --- a/client/build.sh +++ b/client/build.sh @@ -6,14 +6,18 @@ rm -rf out/ || true mkdir out/ cargo build --target wasm32-unknown-unknown --release +echo "[ws] built rust" wasm-bindgen --weak-refs --no-typescript --target no-modules --out-dir out/ ../target/wasm32-unknown-unknown/release/wstcp_client.wasm +echo "[ws] bindgen finished" mv out/wstcp_client_bg.wasm out/wstcp_client_unoptimized.wasm -wasm-opt -O4 out/wstcp_client_unoptimized.wasm -o out/wstcp_client_bg.wasm +wasm-opt out/wstcp_client_unoptimized.wasm -o out/wstcp_client_bg.wasm +echo "[ws] optimized" AUTOGENERATED_SOURCE=$(<"out/wstcp_client.js") WASM_BASE64=$(base64 -w0 out/wstcp_client_bg.wasm) echo "${AUTOGENERATED_SOURCE//__wbg_init(input) \{/__wbg_init(input) \{input=\'data:application/wasm;base64,$WASM_BASE64\'}" > out/wstcp_client_bundled.js cp -r src/web/* out/ +echo "[ws] done!" (cd out; python3 -m http.server) diff --git a/client/disable-the-fucking-borrow-checker.mjs b/client/disable-the-fucking-borrow-checker.mjs new file mode 100644 index 0000000..f93d512 --- /dev/null +++ b/client/disable-the-fucking-borrow-checker.mjs @@ -0,0 +1,16 @@ +import fs from "fs"; +import path from "path"; +import binaryen from "binaryen"; +import { fileURLToPath } from 'url'; +const __filename = fileURLToPath(import.meta.url); + +const __dirname = path.dirname(__filename); +let fp = path.resolve(__dirname, './wat.wat'); +const originBuffer = fs.readFileSync(fp).toString(); + +// const wasm = binaryen.readBinary(originBuffer); +const wast = originBuffer + .replace(/\(br_if \$label\$1[\s\n]+?\(i32.eq\n[\s\S\n]+?i32.const -1\)[\s\n]+\)[\s\n]+\)/g, ''); +// const distBuffer = binaryen.parseText(wast).emitBinary(); + +fs.writeFileSync(fp, wast); diff --git a/client/src/lib.rs b/client/src/lib.rs index 8a8addc..8f73da1 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -4,13 +4,14 @@ mod utils; mod tokioio; mod wrappers; +use base64::{engine::general_purpose::STANDARD, Engine}; use fastwebsockets::{Frame, OpCode, Payload, Role, WebSocket}; -use tokio::io::AsyncWriteExt; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokioio::TokioIo; use utils::{ReplaceErr, UriExt}; use wrappers::{IncomingBody, WsStreamWrapper}; -use std::sync::Arc; +use std::{io::Read, ptr::null_mut, str::from_utf8, sync::Arc}; use async_compression::tokio::bufread as async_comp; use bytes::Bytes; @@ -114,6 +115,154 @@ async fn start() { utils::set_panic_hook(); } +#[wasm_bindgen] +pub struct WsWebSocket { + onopen: Function, + onclose: Function, + onerror: Function, + onmessage: Function, + ws: Option>, +} + +async fn wtf(iop: *mut WsTcpStream) { + let mut t = false; + unsafe { + let io = &mut *iop; + loop { + let r = io.read_u8().await; + + if let Ok(u) = r { + // log!("{}", u as char); + if t && u as char == '\r' { + let r = io.read_u8().await; + break; + } + if u as char == '\n' { + t = true; + } else { + t = false; + } + } else { + break; + } + } + } +} + +#[wasm_bindgen] +impl WsWebSocket { + #[wasm_bindgen(constructor)] + pub fn new( + onopen: Function, + onclose: Function, + onmessage: Function, + onerror: Function, + ) -> Result { + Ok(Self { + onopen, + onclose, + onerror, + onmessage, + ws: None, + }) + } + + #[wasm_bindgen] + pub async fn connect( + &mut self, + tcp: &mut WsTcp, + url: String, + protocols: Vec, + host: String, + ) -> Result<(), JsError> { + self.onopen.call0(&Object::default()); + let uri = url.parse::().replace_err("Failed to parse URL")?; + let mut io = tcp.get_http_io(&uri).await?; + + let r: [u8; 16] = rand::random(); + let key = STANDARD.encode(&r); + + io.write(b"GET / HTTP/1.1\r\n").await; + io.write(b"Sec-WebSocket-Version: 13\r\n").await; + io.write(format!("Sec-WebSocket-Key: {}\r\n", key).as_bytes()) + .await; + io.write(b"Connection: Upgrade\r\n").await; + io.write(b"Upgrade: websocket\r\n").await; + io.write(format!("Host: {}\r\n", host).as_bytes()).await; + io.write(b"\r\n").await; + + let iop: *mut WsTcpStream = &mut io; + wtf(iop).await; + + let mut ws = WebSocket::after_handshake(io, fastwebsockets::Role::Client); + ws.set_writev(false); + ws.set_auto_close(true); + ws.set_auto_pong(true); + + self.ws = Some(ws); + + Ok(()) + } + + #[wasm_bindgen] + pub fn ptr(&mut self) -> *mut WsWebSocket { + self + } + + #[wasm_bindgen] + pub async fn send(&mut self, payload: String) -> Result<(), JsError> { + let Some(ws) = self.ws.as_mut() else { + return Err(JsError::new("Tried to send() before handshake!")); + }; + ws.write_frame(Frame::new( + true, + OpCode::Text, + None, + Payload::Owned(payload.as_bytes().to_vec()), + )) + .await + .replace_err("Failed to send WsWebSocket payload")?; + Ok(()) + } + + #[wasm_bindgen] + pub async fn recv(&mut self) -> Result<(), JsError> { + let Some(ws) = self.ws.as_mut() else { + return Err(JsError::new("Tried to recv() before handshake!")); + }; + loop { + let Ok(frame) = ws.read_frame().await else { + break; + }; + + if frame.opcode == OpCode::Text { + if let Ok(str) = from_utf8(&frame.payload) { + self.onmessage + .call1(&JsValue::null(), &jval!(str)) + .replace_err("missing onmessage handler")?; + } + } else if frame.opcode == OpCode::Binary { + self.onmessage + .call1( + &JsValue::null(), + &jval!(Uint8Array::from(frame.payload.to_vec().as_slice())), + ) + .replace_err("missing onmessage handler")?; + } + } + self.onclose + .call0(&JsValue::null()) + .replace_err("missing onclose handler")?; + Ok(()) + } +} + +#[wasm_bindgen] +pub async fn send(pointer: *mut WsWebSocket, payload: String) -> Result<(), JsError> { + let tcp = unsafe { &mut *pointer }; + tcp.send(payload).await +} + #[wasm_bindgen] pub struct WsTcp { rustls_config: Arc, @@ -164,6 +313,10 @@ impl WsTcp { redirect_limit, }) } + #[wasm_bindgen] + pub fn ptr(&mut self) -> *mut WsTcp { + self as *mut Self + } async fn get_http_io(&self, url: &Uri) -> Result { let url_host = url.host().replace_err("URL must have a host")?; @@ -192,41 +345,6 @@ impl WsTcp { } } - #[wasm_bindgen] - pub async fn connect_ws( - &self, - url: String, - protocols: Vec, - onopen: Function, - onclose: Function, - onmessage: Function, - onerror: Function, - host: String, - ) -> Result { - onopen.call0(&Object::default()); - let uri = url.parse::().replace_err("Failed to parse URL")?; - let mut io = self.get_http_io(&uri).await?; - - let mut a = WebSocket::after_handshake(io, fastwebsockets::Role::Client); - a.set_writev(false); - a.set_auto_close(true); - a.set_auto_pong(true); - a.write_frame(Frame::new( - true, - OpCode::Text, - None, - Payload::Owned(b"aasdfdfhsdfhkadfhsdfkhjasfkajdfhaksjhfkadhfkashdfkhsd".to_vec()), - )) - .await; - - // .await - // .replace_err("Failed to connect to host")?; - - let closure = Closure::::new(move |data: JsValue| { - log!("WaeASDASd"); - }); - Ok(closure.into_js_value()) - } async fn send_req( &self, req: http::Request,