diff --git a/oden-js/src/context.rs b/oden-js/src/context.rs index a25eef51..5eb6901a 100644 --- a/oden-js/src/context.rs +++ b/oden-js/src/context.rs @@ -1,6 +1,6 @@ use crate::{ callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Promise, - Result, Runtime, TryIntoValue, Value, ValueRef, ValueResult, + Result, Runtime, Value, ValueRef, ValueResult, }; use bitflags::bitflags; use oden_js_sys as sys; @@ -177,21 +177,6 @@ impl ContextRef { self.check_exception(unsafe { sys::JS_NewObject(self.ctx) }) } - /// Construct a new value of type object with the specified properties. - pub fn new_object_props(&self, props: [(K, V); N]) -> ValueResult - where - K: AsRef, - V: TryIntoValue, - { - let mut obj = self.new_object()?; - for (k, v) in props.into_iter() { - let k: &str = k.as_ref(); - let v = v.try_into_value(self)?; - obj.set_property(self, k, &v)?; - } - Ok(obj) - } - /// Construct a new value from a boolean. pub fn new_bool(&self, value: T) -> ValueResult where @@ -264,19 +249,6 @@ impl ContextRef { self.check_exception(unsafe { sys::JS_NewArray(self.ctx) }) } - /// Construct a new array value with the given contents - pub fn new_array_values(&self, values: [V; N]) -> ValueResult - where - V: TryIntoValue, - { - let mut arr = self.new_array()?; - for (i, v) in values.into_iter().enumerate() { - let v = v.try_into_value(self)?; - arr.set_index(self, i.try_into().unwrap(), &v)?; - } - Ok(arr) - } - /// Construct a new value from a string. pub fn new_string(&self, value: &str) -> ValueResult { let c_value = CString::new(value)?; @@ -441,31 +413,6 @@ impl ContextRef { ) }) } - - /// Parse the specified JSON as data. `filename` is used for reporting - /// errors. - pub fn parse_json(&self, data: &str, filename: &str) -> Result { - let c_data = CString::new(data)?; - let c_filename = CString::new(filename)?; - - self.check_exception(unsafe { - sys::JS_ParseJSON(self.ctx, c_data.as_ptr(), data.len(), c_filename.as_ptr()) - }) - } - - /// Convert the value to a JSON string. - pub fn json_stringify(&self, value: &ValueRef) -> Result { - self.check_exception(unsafe { - let undef = sys::JSValue { - u: sys::JSValueUnion { - ptr: std::ptr::null_mut(), - }, - tag: sys::JS_TAG_UNDEFINED as i64, - }; - - sys::JS_JSONStringify(self.ctx, value.val, undef, undef) - }) - } } #[derive(Debug)] diff --git a/oden-js/src/value.rs b/oden-js/src/value.rs index 0ef8c8d3..55530347 100644 --- a/oden-js/src/value.rs +++ b/oden-js/src/value.rs @@ -481,12 +481,6 @@ impl Clone for Value { } } -impl AsRef for Value { - fn as_ref(&self) -> &ValueRef { - self - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/script/debugger.rs b/src/script/debugger.rs index 214e8b52..4f5a5c3a 100644 --- a/src/script/debugger.rs +++ b/src/script/debugger.rs @@ -1,31 +1,114 @@ -use base64::Engine; -use bytes::buf::BufMut; -use oden_js::{Context, Runtime, ValueRef}; -use sha1::{Digest, Sha1}; use std::collections::HashMap; use std::io; -use std::io::{BufRead, BufReader, Read, Write}; +use std::io::{BufRead, BufReader, Write}; use std::net::{TcpListener, TcpStream}; use std::thread; -use thiserror::Error; pub struct Debugger { _thread: thread::JoinHandle<()>, } -#[allow(dead_code)] -pub fn start_debugger() -> Debugger { - let thread = thread::spawn(move || { - if let Err(e) = debugger_listener() { - eprintln!("ERROR STARTING DEBUGGER: {:?}", e); - } - }); - - Debugger { _thread: thread } +fn write_http_response( + stream: &mut TcpStream, + code: u16, + phrase: &str, + content_type: &str, + data: &[u8], +) -> io::Result<()> { + let length = data.len(); + let buffer = format!( + "HTTP/1.1 {code} {phrase}\r\n\ + content-length: {length}\r\n\ + content-type: {content_type}\r\n\ + \r\n" + ); + stream.write_all(buffer.as_bytes())?; + stream.write_all(data) } -pub fn debugger_listener() -> Result<()> { - let listener = TcpListener::bind("127.0.0.1:9229")?; +fn write_http_error(stream: &mut TcpStream, code: u16, phrase: &str) -> io::Result<()> { + write_http_response(stream, code, phrase, "text/plain", phrase.as_bytes()) +} + +fn write_http_ok(stream: &mut TcpStream, content_type: &str, data: &[u8]) -> io::Result<()> { + write_http_response(stream, 200, "OK", content_type, data) +} + +fn handle_connection(mut stream: TcpStream) -> io::Result<()> { + let mut buf_reader = BufReader::new(&mut stream); + + let mut buffer = String::new(); + buf_reader.read_line(&mut buffer)?; + + let parts: Vec<_> = buffer.trim().split(" ").collect(); + if parts.len() != 3 { + eprintln!("Invalid request line: {buffer}"); + write_http_error(&mut stream, 400, "Invalid request")?; + return Ok(()); + } + + let method = parts[0]; + let path = parts[1]; + eprintln!("Debugger: {method} {path}"); + + let mut headers = HashMap::new(); + let mut header_line_buffer = String::new(); + loop { + header_line_buffer.clear(); + buf_reader.read_line(&mut header_line_buffer)?; + let header_line = header_line_buffer.trim(); + if header_line.is_empty() { + break; + } + + let sep_idx = match header_line.find(":") { + Some(idx) => idx, + None => { + write_http_error(&mut stream, 400, "Invalid request")?; + return Ok(()); + } + }; + let header = &header_line[0..sep_idx]; + let value = &header_line[sep_idx + 1..]; + eprintln!("HEADER: {header} {value}"); + headers.insert(header.trim().to_string(), value.trim().to_string()); + } + + if method == "GET" { + if path == "/json/version" { + // NOTE: Deno spits out a V8 version here but we're not running v8. + write_http_ok( + &mut stream, + "application/json", + "{\"Browser\": \"Oden/0.0.1\", \"Protocol-Version\": \"1.3\"}".as_bytes(), + )?; + } else if path == "/json" || path == "/json/list" { + // [ + // { + // "description": "deno", + // "devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/ws/01b9bd23-8810-43ed-86f7-5feef5d120fa&experiments=true&v8only=true", + // "faviconUrl": "https://deno.land/favicon.ico", + // "id": "01b9bd23-8810-43ed-86f7-5feef5d120fa", + // "title": "deno - main [pid: 40483]", + // "type": "node", + // "url": "file:///Users/doty/src/ioniq/status.ts", + // "webSocketDebuggerUrl": "ws://127.0.0.1:9229/ws/01b9bd23-8810-43ed-86f7-5feef5d120fa" + // } + // ] + } else if path == "/ws/dd5cfe85-f0b1-4241-a643-8ed81e436188" { + // I don't feel like making a new guid for each thing. + // Websocket upgrade and then debugger messages. + } else { + write_http_error(&mut stream, 404, "Not Found")?; + } + } else { + write_http_error(&mut stream, 404, "Not Found")?; + } + Ok(()) +} + +pub fn debugger_listener() -> io::Result<()> { + let listener = TcpListener::bind("127.0.0.1:0")?; let port = listener.local_addr()?.port(); println!("Debugger listening on http://127.0.0.1:{port}"); for stream in listener.incoming() { @@ -37,561 +120,20 @@ pub fn debugger_listener() -> Result<()> { } }; - DebuggerConnection::start(stream); + // TODO: Thread pool extraction here. + if let Err(e) = handle_connection(stream) { + eprintln!("Error handling incoming connection: {e}"); + } } Ok(()) } -struct DebuggerConnection { - stream: TcpStream, - context: Context, -} - -impl DebuggerConnection { - fn start(stream: TcpStream) { - thread::spawn(move || { - let mut connection = DebuggerConnection { - stream, - context: Context::new(Runtime::new()), - }; - loop { - if let Err(e) = connection.handle_connection() { - eprintln!("Closing connection: {e}"); - break; - } - } - }); - } - - // ============================================================================ - // Dumb HTTP stuff - // ============================================================================ - fn handle_connection(&mut self) -> Result<()> { - let port = self.stream.local_addr()?.port(); - let id = "dd5cfe85-f0b1-4241-a643-8ed81e436188"; - - let mut buf_reader = BufReader::new(&mut self.stream); - - let mut buffer = String::new(); - while buffer.trim().is_empty() { - buf_reader.read_line(&mut buffer)?; - } - - let parts: Vec<_> = buffer.trim().split(" ").collect(); - if parts.len() != 3 { - eprintln!("Invalid request line: {buffer}"); - self.write_http_error(400, "Invalid request")?; - return Ok(()); - } - - let method = parts[0]; - let path = parts[1]; - eprintln!("Debugger: {method} {path}"); - - let mut headers = HashMap::new(); - let mut header_line_buffer = String::new(); - loop { - header_line_buffer.clear(); - buf_reader.read_line(&mut header_line_buffer)?; - let header_line = header_line_buffer.trim(); - if header_line.is_empty() { - break; - } - - let sep_idx = match header_line.find(":") { - Some(idx) => idx, - None => { - self.write_http_error(400, "Invalid request")?; - return Ok(()); - } - }; - let header = &header_line[0..sep_idx]; - let value = &header_line[sep_idx + 1..]; - // eprintln!("HEADER: {header} {value}"); - headers.insert(header.trim().to_lowercase(), value.trim().to_string()); - } - - if method == "GET" { - if path == "/json/version" { - // NOTE: Deno spits out a V8 version here but we're not running v8. - self.write_http_ok( - "application/json", - r#" -{ - "Browser": "Oden/0.0.1", - "Protocol-Version": "1.3" -}"# - .as_bytes(), - )?; - } else if path == "/json" || path == "/json/list" { - // TODO: title should reflect the game being loaded - // TODO: url should go to the main .ts file - // TODO: faciconUrl? - let result = format!( - r#" -[{{ - "description": "oden", - "devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/ws/01b9bd23-8810-43ed-86f7-5feef5d120fa&experiments=true&v8only=true", - "id": "{id}", - "title": "oden game", - "url": "oden://game", - "webSocketDebuggerUrl": "ws://127.0.0.1:{port}/ws/{id}" -}}] -"# - ); - self.write_http_ok("application/json", result.as_bytes())?; - } else if path == "/ws/dd5cfe85-f0b1-4241-a643-8ed81e436188" { - // I don't feel like making a new guid for each thing. - // Websocket upgrade and then debugger messages. - - let mut key = match headers.get("sec-websocket-key") { - Some(v) => v.clone(), - None => { - self.write_http_error(400, "Invalid request")?; - return Ok(()); - } - }; - key.push_str("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); - let mut hasher = Sha1::new(); - hasher.update(key.as_bytes()); - let result = base64::engine::general_purpose::STANDARD.encode(hasher.finalize()); - let response = format!( - "HTTP/1.1 101 Switching Protocols\r\n\ - Upgrade: websocket\r\n\ - Connection: Upgrade\r\n\ - Sec-Websocket-Accept: {result}\r\n\ - \r\n" - ); - self.stream.write_all(response.as_bytes())?; - - self.handle_websocket_connection()?; - } else { - self.write_http_error(404, "Not Found")?; - } - } else { - self.write_http_error(404, "Not Found")?; - } - Ok(()) - } - - fn write_http_response( - &mut self, - code: u16, - phrase: &str, - content_type: &str, - data: &[u8], - ) -> Result<()> { - let length = data.len(); - let buffer = format!( - "HTTP/1.1 {code} {phrase}\r\n\ - content-length: {length}\r\n\ - content-type: {content_type}\r\n\ - \r\n" - ); - self.stream.write_all(buffer.as_bytes())?; - self.stream.write_all(data)?; - Ok(()) - } - - fn write_http_error(&mut self, code: u16, phrase: &str) -> Result<()> { - self.write_http_response(code, phrase, "text/plain", phrase.as_bytes()) - } - - fn write_http_ok(&mut self, content_type: &str, data: &[u8]) -> Result<()> { - self.write_http_response(200, "OK", content_type, data) - } - - // ============================================================================ - // Dumb Websocket stuff - // ============================================================================ - fn handle_websocket_connection(&mut self) -> Result<()> { - // BLARG: https://datatracker.ietf.org/doc/html/rfc6455 - - let mut payload: Vec = Vec::new(); - let mut opcode: WebsocketOp = WebsocketOp::Continue; - - loop { - let frame = WebsocketFrameHeader::read(&mut self.stream)?; - eprintln!("FRAME: {:?}", frame); - let mask = match frame.mask { - Some(m) => m, - None => { - eprintln!("Client sent unmasked frame"); - return Ok(()); // TODO: _Fail the WebSocket Connection_ - } - }; - - // OK what are we dealing with here? Check the opcode before we decide - // where to read the data. - if frame.opcode.is_control() { - // This is a control frame, handle it on its own. - if frame.length > 125 { - eprintln!("Control frame too big ({})", frame.length); - return Ok(()); // TODO: _Fail the WebSocket Connection_ - } - let mut buffer: [u8; 125] = [0; 125]; - self.stream.read_exact(&mut buffer[0..frame.length])?; - - if frame.opcode == WebsocketOp::Close { - self.send_websocket_message(frame.opcode, &buffer[..frame.length])?; - return Ok(()); - } else if frame.opcode == WebsocketOp::Ping { - self.send_websocket_message(WebsocketOp::Pong, &buffer[..frame.length])?; - } else if frame.opcode == WebsocketOp::Pong { - // got a pong - } - } else { - // This is a user data frame, it goes into `payload`. - let last_tail = payload.len(); - if last_tail + frame.length > 1 * 1024 * 1024 { - eprintln!("Message too big ({})", frame.length); - return Ok(()); // TODO: _Fail the WebSocket Connection_ - } - payload.resize(last_tail + frame.length, 0); - let slice = payload.as_mut_slice(); - let mut slice = &mut slice[last_tail..]; - // eprintln!("Reading {} bytes", slice.len()); - self.stream.read_exact(&mut slice)?; - for i in 0..frame.length { - slice[i] = slice[i] ^ mask[i % 4]; - } - // eprintln!("Done"); - - if opcode == WebsocketOp::Continue { - opcode = frame.opcode; - } - - if frame.fin { - // Dispatch. - // eprintln!("Dispatching: {:?} {}", opcode, payload.len()); - self.handle_websocket_message(opcode, &payload)?; - payload.clear(); - opcode = WebsocketOp::Continue; - } - } - } - } - - fn send_websocket_message(&mut self, opcode: WebsocketOp, payload: &[u8]) -> Result<()> { - WebsocketFrameHeader { - fin: true, - opcode, - length: payload.len(), - mask: None, - } - .write(&mut self.stream)?; - self.stream.write_all(payload)?; - Ok(()) - } - - fn send_websocket_close(&mut self, close_code: u16, reason: &str) -> Result<()> { - if reason.len() > 123 { - return Err("reason doesn't fit in close packet".into()); - } - - let mut body: [u8; 125] = [0; 125]; - let mut dst = &mut body[..]; - dst.put_u16(close_code); - dst.put_slice(reason.as_bytes()); - let remaining = dst.remaining_mut(); - - let body_len = body.len() - remaining; - self.send_websocket_message(WebsocketOp::Close, &body[0..body_len]) - } - - // ============================================================================ - // Actual debugger stuff? - // ============================================================================ - fn handle_websocket_message(&mut self, _op: WebsocketOp, data: &[u8]) -> Result<()> { - let text = String::from_utf8_lossy(data); - eprintln!("Received: {text}"); - - let message = match self.context.parse_json(&text, "") { - Ok(v) => v, - Err(e) => { - eprintln!("error parsing json {e}"); - self.send_websocket_close(1007, "invalid json")?; - return Err("invalid json".into()); - } - }; - - // Oh no. - let _ = message.get_property(&self.context, "sessionId")?; - let id = message.get_property(&self.context, "id")?; - let method = { - let method = message.get_property(&self.context, "method")?; - method.to_string(&self.context)? - }; - let _ = message.get_property(&self.context, "params")?; - - match method.as_str() { - "Profiler.enable" => { - self.send_json_response(&id, self.context.new_object()?.as_ref())?; - } - - "Runtime.enable" => { - self.send_json_event( - "Runtime.executionContextCreated", - self.context - .new_object_props([( - "context", - self.context.new_object_props([ - ("id", self.context.new_i32(7)?), - ("origin", self.context.new_string("something")?), - ("name", self.context.new_string("main")?), - ("uniqueId", self.context.new_string("aaa-aaa-aaa")?), - ])?, - )])? - .as_ref(), - )?; - self.send_json_response(&id, self.context.new_object()?.as_ref())?; - } - - "Runtime.evaluate" => { - // OHHHHHHHHHH - eprintln!("****** EVALUATE *******"); - self.send_debugger_error(&id, -32601, "method does not exist")? - } - - "Runtime.runIfWaitingForDebugger" => { - self.send_json_response(&id, self.context.new_object()?.as_ref())?; - } - - "Debugger.enable" => { - self.send_json_response(&id, self.context.new_object()?.as_ref())?; - } - - "Debugger.setAsyncCallStackDepth" => { - // TODO: Do I need to respect this? - self.send_json_response(&id, self.context.new_object()?.as_ref())?; - } - - "Debugger.setBlackboxPatterns" => { - // TODO: Do I need to respect this? - self.send_json_response(&id, self.context.new_object()?.as_ref())?; - } - - "Debugger.setPauseOnExceptions" => { - // TODO: Respect this. - self.send_json_response(&id, self.context.new_object()?.as_ref())?; - } - - _ => { - eprintln!("Unsupported method: {method}"); - self.send_debugger_error(&id, -32601, "method does not exist")? - } - } - - Ok(()) - } - - fn send_debugger_error(&mut self, id: &ValueRef, code: i32, msg: &str) -> Result<()> { - self.send_json_message( - self.context - .new_object_props([ - ("id", id), - ( - "error", - self.context - .new_object_props([ - ("code", self.context.new_i32(code)?), - ("message", self.context.new_string(msg)?), - ])? - .as_ref(), - ), - ])? - .as_ref(), - ) - } - - fn send_json_event(&mut self, event: &str, params: &ValueRef) -> Result<()> { - self.send_json_message( - self.context - .new_object_props([ - ("method", self.context.new_string(event)?.as_ref()), - ("params", params), - ])? - .as_ref(), - ) - } - - fn send_json_response(&mut self, id: &ValueRef, result: &ValueRef) -> Result<()> { - let response = self - .context - .new_object_props([("id", id), ("result", result)])?; - self.send_json_message(&response) - } - - fn send_json_message(&mut self, value: &ValueRef) -> Result<()> { - let txt = self - .context - .json_stringify(&value)? - .to_string(&self.context)?; - eprintln!("Send: {txt}"); - self.send_websocket_message(WebsocketOp::Text, txt.as_bytes()) - } -} - -#[derive(Debug)] -struct WebsocketFrameHeader { - pub fin: bool, - pub opcode: WebsocketOp, - pub length: usize, - pub mask: Option<[u8; 4]>, -} - -impl WebsocketFrameHeader { - fn read(stream: &mut TcpStream) -> Result { - let mut buffer: [u8; 2] = [0, 0]; - stream.read_exact(&mut buffer)?; - if (buffer[0] & 0x70) != 0 { - eprintln!("Frame has non-zero RSV* ({:x})", buffer[0]); - return Err("unknown extensions".into()); - } - let has_mask = (buffer[1] & 0x80) != 0; - - let fin = (buffer[0] & 0x80) != 0; - let opcode = buffer[0] & 0x0F; - let mut length: u64 = (buffer[1] & !0x80).into(); - // eprintln!("Decoded length {} from {}", length, buffer[1]); - if length == 126 { - let mut len_buffer: [u8; 2] = [0, 0]; - stream.read_exact(&mut len_buffer)?; - length = u16::from_be_bytes(len_buffer).into(); - } else if length == 127 { - let mut len_buffer: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; - stream.read_exact(&mut len_buffer)?; - length = u64::from_be_bytes(len_buffer); - } - if length > 1 * 1024 * 1024 { - eprintln!("Frame too big ({length})"); - return Err(io::Error::new(io::ErrorKind::InvalidData, "frame too big").into()); - } - - let mask = if has_mask { - let mut mask: [u8; 4] = [0, 0, 0, 0]; - stream.read_exact(&mut mask)?; - Some(mask) - } else { - None - }; - - let length: usize = length.try_into().unwrap(); - Ok(WebsocketFrameHeader { - fin, - opcode: opcode.try_into()?, - length, - mask, - }) - } - - fn write(&self, stream: &mut TcpStream) -> Result<()> { - let mut header: [u8; 16] = [0; 16]; - let mut dst = &mut header[..]; - - let byte: u8 = self.opcode.into(); - dst.put_u8(byte | if self.fin { 0x80 } else { 0x00 }); - - let mask = if self.mask.is_some() { 0x80 } else { 0x00 }; - if self.length < 126 { - let len: u8 = self.length.try_into().unwrap(); - dst.put_u8(len | mask); - } else if self.length <= u16::max_value().into() { - dst.put_u8(126 | mask); - dst.put_u16(self.length.try_into().unwrap()); - } else { - dst.put_u8(127 | mask); - dst.put_u64(self.length.try_into().unwrap()); - } - if let Some(bytes) = self.mask { - dst.put_slice(&bytes); - } - let remaining = dst.remaining_mut(); - - let header_len = header.len() - remaining; - stream.write_all(&header[0..header_len])?; - Ok(()) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -enum WebsocketOp { - Continue, - Text, - Binary, - Close, - Ping, - Pong, -} - -impl WebsocketOp { - fn is_control(&self) -> bool { - let byte: u8 = (*self).into(); - byte & 0x08 != 0 - } -} - -impl TryFrom for WebsocketOp { - type Error = DebuggerError; - - fn try_from(value: u8) -> Result { - match value { - 0x0 => Ok(WebsocketOp::Continue), - 0x1 => Ok(WebsocketOp::Text), - 0x2 => Ok(WebsocketOp::Binary), - 0x8 => Ok(WebsocketOp::Close), - 0x9 => Ok(WebsocketOp::Ping), - 0xA => Ok(WebsocketOp::Pong), - _ => Err(format!("unknown opcode {value}").into()), - } - } -} - -impl From for u8 { - fn from(value: WebsocketOp) -> Self { - match value { - WebsocketOp::Continue => 0x0, - WebsocketOp::Text => 0x1, - WebsocketOp::Binary => 0x2, - WebsocketOp::Close => 0x8, - WebsocketOp::Ping => 0x9, - WebsocketOp::Pong => 0xA, - } - } -} - -#[derive(Debug, Error)] -pub enum DebuggerError { - #[error("an unknown error has occurred: {0}")] - MiscError(String), - #[error("a javascript error has occurred: {0}")] - JsError(oden_js::Error), - #[error("an io error has occurred: {0}")] - IoError(io::Error), -} - -type Result = core::result::Result; - -impl From<&str> for DebuggerError { - fn from(value: &str) -> Self { - DebuggerError::MiscError(value.to_string()) - } -} - -impl From for DebuggerError { - fn from(value: String) -> Self { - DebuggerError::MiscError(value) - } -} - -impl From for DebuggerError { - fn from(value: oden_js::Error) -> Self { - DebuggerError::JsError(value) - } -} - -impl From for DebuggerError { - fn from(value: io::Error) -> Self { - DebuggerError::IoError(value) - } +#[allow(dead_code)] +pub fn start_debugger() -> Debugger { + let thread = thread::spawn(move || { + if let Err(e) = debugger_listener() { + eprintln!("ERROR STARTING DEBUGGER: {:?}", e); + } + }); + Debugger { _thread: thread } }