[oden] More debugger! More more!
All the way through websockets and JSON RPC (kinda)
This commit is contained in:
parent
3ef1a8e806
commit
819a6dc1b6
1 changed files with 568 additions and 110 deletions
|
|
@ -1,49 +1,86 @@
|
||||||
|
use base64::Engine;
|
||||||
|
use bytes::buf::BufMut;
|
||||||
|
use oden_js::{Context, Runtime, ValueRef};
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{BufRead, BufReader, Write};
|
use std::io::{BufRead, BufReader, Read, Write};
|
||||||
use std::net::{TcpListener, TcpStream};
|
use std::net::{TcpListener, TcpStream};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub struct Debugger {
|
pub struct Debugger {
|
||||||
_thread: thread::JoinHandle<()>,
|
_thread: thread::JoinHandle<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_http_response(
|
#[allow(dead_code)]
|
||||||
stream: &mut TcpStream,
|
pub fn start_debugger() -> Debugger {
|
||||||
code: u16,
|
let thread = thread::spawn(move || {
|
||||||
phrase: &str,
|
if let Err(e) = debugger_listener() {
|
||||||
content_type: &str,
|
eprintln!("ERROR STARTING DEBUGGER: {:?}", e);
|
||||||
data: &[u8],
|
}
|
||||||
) -> io::Result<()> {
|
});
|
||||||
let length = data.len();
|
|
||||||
let buffer = format!(
|
Debugger { _thread: thread }
|
||||||
"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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_http_error(stream: &mut TcpStream, code: u16, phrase: &str) -> io::Result<()> {
|
pub fn debugger_listener() -> Result<()> {
|
||||||
write_http_response(stream, code, phrase, "text/plain", phrase.as_bytes())
|
let listener = TcpListener::bind("127.0.0.1:9229")?;
|
||||||
|
let port = listener.local_addr()?.port();
|
||||||
|
println!("Debugger listening on http://127.0.0.1:{port}");
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
let stream = match stream {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error accepting incoming connection: {:?}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DebuggerConnection::start(stream);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_http_ok(stream: &mut TcpStream, content_type: &str, data: &[u8]) -> io::Result<()> {
|
struct DebuggerConnection {
|
||||||
write_http_response(stream, 200, "OK", content_type, data)
|
stream: TcpStream,
|
||||||
|
context: Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_connection(mut stream: TcpStream) -> io::Result<()> {
|
impl DebuggerConnection {
|
||||||
let mut buf_reader = BufReader::new(&mut stream);
|
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();
|
let mut buffer = String::new();
|
||||||
|
while buffer.trim().is_empty() {
|
||||||
buf_reader.read_line(&mut buffer)?;
|
buf_reader.read_line(&mut buffer)?;
|
||||||
|
}
|
||||||
|
|
||||||
let parts: Vec<_> = buffer.trim().split(" ").collect();
|
let parts: Vec<_> = buffer.trim().split(" ").collect();
|
||||||
if parts.len() != 3 {
|
if parts.len() != 3 {
|
||||||
eprintln!("Invalid request line: {buffer}");
|
eprintln!("Invalid request line: {buffer}");
|
||||||
write_http_error(&mut stream, 400, "Invalid request")?;
|
self.write_http_error(400, "Invalid request")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,76 +101,497 @@ fn handle_connection(mut stream: TcpStream) -> io::Result<()> {
|
||||||
let sep_idx = match header_line.find(":") {
|
let sep_idx = match header_line.find(":") {
|
||||||
Some(idx) => idx,
|
Some(idx) => idx,
|
||||||
None => {
|
None => {
|
||||||
write_http_error(&mut stream, 400, "Invalid request")?;
|
self.write_http_error(400, "Invalid request")?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let header = &header_line[0..sep_idx];
|
let header = &header_line[0..sep_idx];
|
||||||
let value = &header_line[sep_idx + 1..];
|
let value = &header_line[sep_idx + 1..];
|
||||||
eprintln!("HEADER: {header} {value}");
|
// eprintln!("HEADER: {header} {value}");
|
||||||
headers.insert(header.trim().to_string(), value.trim().to_string());
|
headers.insert(header.trim().to_lowercase(), value.trim().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if method == "GET" {
|
if method == "GET" {
|
||||||
if path == "/json/version" {
|
if path == "/json/version" {
|
||||||
// NOTE: Deno spits out a V8 version here but we're not running v8.
|
// NOTE: Deno spits out a V8 version here but we're not running v8.
|
||||||
write_http_ok(
|
self.write_http_ok(
|
||||||
&mut stream,
|
|
||||||
"application/json",
|
"application/json",
|
||||||
"{\"Browser\": \"Oden/0.0.1\", \"Protocol-Version\": \"1.3\"}".as_bytes(),
|
r#"
|
||||||
|
{
|
||||||
|
"Browser": "Oden/0.0.1",
|
||||||
|
"Protocol-Version": "1.3"
|
||||||
|
}"#
|
||||||
|
.as_bytes(),
|
||||||
)?;
|
)?;
|
||||||
} else if path == "/json" || path == "/json/list" {
|
} else if path == "/json" || path == "/json/list" {
|
||||||
// [
|
// TODO: title should reflect the game being loaded
|
||||||
// {
|
// TODO: url should go to the main .ts file
|
||||||
// "description": "deno",
|
// TODO: faciconUrl?
|
||||||
// "devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/ws/01b9bd23-8810-43ed-86f7-5feef5d120fa&experiments=true&v8only=true",
|
let result = format!(
|
||||||
// "faviconUrl": "https://deno.land/favicon.ico",
|
r#"
|
||||||
// "id": "01b9bd23-8810-43ed-86f7-5feef5d120fa",
|
[{{
|
||||||
// "title": "deno - main [pid: 40483]",
|
"description": "oden",
|
||||||
// "type": "node",
|
"devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/ws/01b9bd23-8810-43ed-86f7-5feef5d120fa&experiments=true&v8only=true",
|
||||||
// "url": "file:///Users/doty/src/ioniq/status.ts",
|
"id": "{id}",
|
||||||
// "webSocketDebuggerUrl": "ws://127.0.0.1:9229/ws/01b9bd23-8810-43ed-86f7-5feef5d120fa"
|
"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" {
|
} else if path == "/ws/dd5cfe85-f0b1-4241-a643-8ed81e436188" {
|
||||||
// I don't feel like making a new guid for each thing.
|
// I don't feel like making a new guid for each thing.
|
||||||
// Websocket upgrade and then debugger messages.
|
// 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 {
|
} else {
|
||||||
write_http_error(&mut stream, 404, "Not Found")?;
|
self.write_http_error(404, "Not Found")?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
write_http_error(&mut stream, 404, "Not Found")?;
|
self.write_http_error(404, "Not Found")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debugger_listener() -> io::Result<()> {
|
fn write_http_response(
|
||||||
let listener = TcpListener::bind("127.0.0.1:0")?;
|
&mut self,
|
||||||
let port = listener.local_addr()?.port();
|
code: u16,
|
||||||
println!("Debugger listening on http://127.0.0.1:{port}");
|
phrase: &str,
|
||||||
for stream in listener.incoming() {
|
content_type: &str,
|
||||||
let stream = match stream {
|
data: &[u8],
|
||||||
Ok(s) => s,
|
) -> Result<()> {
|
||||||
Err(e) => {
|
let length = data.len();
|
||||||
eprintln!("Error accepting incoming connection: {:?}", e);
|
let buffer = format!(
|
||||||
continue;
|
"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<u8> = 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_
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Thread pool extraction here.
|
// OK what are we dealing with here? Check the opcode before we decide
|
||||||
if let Err(e) = handle_connection(stream) {
|
// where to read the data.
|
||||||
eprintln!("Error handling incoming connection: {e}");
|
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(())
|
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, "<deubgger message>") {
|
||||||
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[derive(Debug)]
|
||||||
pub fn start_debugger() -> Debugger {
|
struct WebsocketFrameHeader {
|
||||||
let thread = thread::spawn(move || {
|
pub fin: bool,
|
||||||
if let Err(e) = debugger_listener() {
|
pub opcode: WebsocketOp,
|
||||||
eprintln!("ERROR STARTING DEBUGGER: {:?}", e);
|
pub length: usize,
|
||||||
}
|
pub mask: Option<[u8; 4]>,
|
||||||
});
|
}
|
||||||
Debugger { _thread: thread }
|
|
||||||
|
impl WebsocketFrameHeader {
|
||||||
|
fn read(stream: &mut TcpStream) -> Result<WebsocketFrameHeader> {
|
||||||
|
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<u8> for WebsocketOp {
|
||||||
|
type Error = DebuggerError;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self> {
|
||||||
|
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<WebsocketOp> 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<T> = core::result::Result<T, DebuggerError>;
|
||||||
|
|
||||||
|
impl From<&str> for DebuggerError {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
DebuggerError::MiscError(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for DebuggerError {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
DebuggerError::MiscError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<oden_js::Error> for DebuggerError {
|
||||||
|
fn from(value: oden_js::Error) -> Self {
|
||||||
|
DebuggerError::JsError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for DebuggerError {
|
||||||
|
fn from(value: io::Error) -> Self {
|
||||||
|
DebuggerError::IoError(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue