virtual-machine 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -1
- package/build/{chunk-GZ343GYI.mjs → chunk-V6I6APCK.mjs} +3 -101
- package/build/cli.js +4 -103
- package/build/index.d.ts +0 -9
- package/build/index.js +5 -103
- package/build/index.mjs +3 -3
- package/build/{riscv_vm-MHIWEZQY.mjs → riscv_vm-QIJGD3E2.mjs} +1 -1
- package/package.json +1 -1
- package/src/lib.rs +0 -21
- package/src/main.rs +130 -54
- package/src/net_webtransport.rs +1 -1
- package/src/uart.rs +0 -2
- package/build/chunk-LJUNPJTY.mjs +0 -670
- package/build/chunk-Q7TFYQ5G.mjs +0 -670
- package/build/riscv_vm-3CIEJ5K7.mjs +0 -12
- package/build/riscv_vm-VNML57ES.mjs +0 -12
- package/src/net_tap.rs +0 -164
- package/src/net_ws.rs +0 -396
package/src/net_tap.rs
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
//! TAP network backend for native (non-WASM) builds.
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides a network backend that uses a TAP interface
|
|
4
|
-
//! to communicate with the host network stack on Linux/macOS.
|
|
5
|
-
|
|
6
|
-
use crate::net::NetworkBackend;
|
|
7
|
-
use std::io::Write;
|
|
8
|
-
use std::os::unix::io::AsRawFd;
|
|
9
|
-
|
|
10
|
-
/// TAP network backend using the tun-tap crate.
|
|
11
|
-
pub struct TapBackend {
|
|
12
|
-
name: String,
|
|
13
|
-
iface: Option<tun_tap::Iface>,
|
|
14
|
-
mac: [u8; 6],
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
impl TapBackend {
|
|
18
|
-
/// Create a new TAP backend with the given interface name.
|
|
19
|
-
///
|
|
20
|
-
/// The interface will not be opened until `init()` is called.
|
|
21
|
-
/// Creating the TAP interface typically requires root privileges.
|
|
22
|
-
///
|
|
23
|
-
/// # Example
|
|
24
|
-
/// ```ignore
|
|
25
|
-
/// let mut tap = TapBackend::new("tap0");
|
|
26
|
-
/// tap.init()?;
|
|
27
|
-
/// ```
|
|
28
|
-
pub fn new(name: &str) -> Self {
|
|
29
|
-
Self {
|
|
30
|
-
name: name.to_string(),
|
|
31
|
-
iface: None,
|
|
32
|
-
// Default MAC - locally administered, unicast
|
|
33
|
-
mac: [0x52, 0x54, 0x00, 0x12, 0x34, 0x56],
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/// Create a TAP backend with a custom MAC address.
|
|
38
|
-
pub fn with_mac(name: &str, mac: [u8; 6]) -> Self {
|
|
39
|
-
Self {
|
|
40
|
-
name: name.to_string(),
|
|
41
|
-
iface: None,
|
|
42
|
-
mac,
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/// Set the interface to non-blocking mode.
|
|
47
|
-
fn set_nonblocking(&self) -> Result<(), String> {
|
|
48
|
-
if let Some(ref iface) = self.iface {
|
|
49
|
-
let fd = iface.as_raw_fd();
|
|
50
|
-
|
|
51
|
-
// Get current flags
|
|
52
|
-
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
|
|
53
|
-
if flags < 0 {
|
|
54
|
-
return Err(format!(
|
|
55
|
-
"Failed to get fd flags: {}",
|
|
56
|
-
std::io::Error::last_os_error()
|
|
57
|
-
));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Set non-blocking flag
|
|
61
|
-
let result = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
|
|
62
|
-
if result < 0 {
|
|
63
|
-
return Err(format!(
|
|
64
|
-
"Failed to set non-blocking mode: {}",
|
|
65
|
-
std::io::Error::last_os_error()
|
|
66
|
-
));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
Ok(())
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
impl NetworkBackend for TapBackend {
|
|
74
|
-
fn init(&mut self) -> Result<(), String> {
|
|
75
|
-
// Open the TAP interface
|
|
76
|
-
let iface = tun_tap::Iface::without_packet_info(&self.name, tun_tap::Mode::Tap)
|
|
77
|
-
.map_err(|e| format!("Failed to open TAP interface '{}': {}", self.name, e))?;
|
|
78
|
-
|
|
79
|
-
self.iface = Some(iface);
|
|
80
|
-
|
|
81
|
-
// Set non-blocking mode for recv polling
|
|
82
|
-
self.set_nonblocking()?;
|
|
83
|
-
|
|
84
|
-
log::info!("[TapBackend] Opened TAP interface '{}'", self.name);
|
|
85
|
-
Ok(())
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
|
|
89
|
-
let iface = self.iface.as_mut().ok_or("TAP interface not initialized")?;
|
|
90
|
-
|
|
91
|
-
// Buffer for maximum Ethernet frame size (MTU 1500 + headers)
|
|
92
|
-
let mut buf = vec![0u8; 1514];
|
|
93
|
-
|
|
94
|
-
match iface.recv(&mut buf) {
|
|
95
|
-
Ok(n) => {
|
|
96
|
-
buf.truncate(n);
|
|
97
|
-
log::trace!("[TapBackend] Received {} byte packet", n);
|
|
98
|
-
Ok(Some(buf))
|
|
99
|
-
}
|
|
100
|
-
Err(e) => {
|
|
101
|
-
// Check if it's a would-block error (no data available)
|
|
102
|
-
if e.kind() == std::io::ErrorKind::WouldBlock {
|
|
103
|
-
Ok(None)
|
|
104
|
-
} else {
|
|
105
|
-
Err(format!("TAP recv error: {}", e))
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
fn send(&self, buf: &[u8]) -> Result<(), String> {
|
|
112
|
-
let iface = self.iface.as_ref().ok_or("TAP interface not initialized")?;
|
|
113
|
-
|
|
114
|
-
// tun_tap::Iface doesn't implement Send on the write side,
|
|
115
|
-
// so we need to use the raw fd for writing. However, for simplicity
|
|
116
|
-
// we'll just write directly. Note: this may need adjustment for
|
|
117
|
-
// thread safety in the future.
|
|
118
|
-
let mut iface_clone = unsafe {
|
|
119
|
-
// This is safe because we're only writing and the fd is valid
|
|
120
|
-
std::fs::File::from_raw_fd(std::os::unix::io::AsRawFd::as_raw_fd(iface))
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
let result = iface_clone.write_all(buf);
|
|
124
|
-
|
|
125
|
-
// Don't drop the file - it doesn't own the fd
|
|
126
|
-
std::mem::forget(iface_clone);
|
|
127
|
-
|
|
128
|
-
result.map_err(|e| format!("TAP send error: {}", e))?;
|
|
129
|
-
log::trace!("[TapBackend] Sent {} byte packet", buf.len());
|
|
130
|
-
Ok(())
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
fn mac_address(&self) -> [u8; 6] {
|
|
134
|
-
self.mac
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Manual implementation to handle the Iface not being Send
|
|
139
|
-
unsafe impl Send for TapBackend {}
|
|
140
|
-
|
|
141
|
-
use std::os::unix::io::FromRawFd;
|
|
142
|
-
|
|
143
|
-
#[cfg(test)]
|
|
144
|
-
mod tests {
|
|
145
|
-
use super::*;
|
|
146
|
-
|
|
147
|
-
#[test]
|
|
148
|
-
fn test_tap_backend_creation() {
|
|
149
|
-
let tap = TapBackend::new("test0");
|
|
150
|
-
assert_eq!(tap.name, "test0");
|
|
151
|
-
assert!(tap.iface.is_none());
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
#[test]
|
|
155
|
-
fn test_tap_backend_custom_mac() {
|
|
156
|
-
let mac = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
|
|
157
|
-
let tap = TapBackend::with_mac("test0", mac);
|
|
158
|
-
assert_eq!(tap.mac_address(), mac);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Note: Actually opening a TAP interface requires root privileges,
|
|
162
|
-
// so we can't test init() in regular unit tests.
|
|
163
|
-
}
|
|
164
|
-
|
package/src/net_ws.rs
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
//! WebSocket network backend for cross-platform networking.
|
|
2
|
-
//!
|
|
3
|
-
//! This backend tunnels Ethernet frames over WebSocket, enabling
|
|
4
|
-
//! networking on platforms without TAP support (macOS, WASM/browser).
|
|
5
|
-
|
|
6
|
-
use crate::net::NetworkBackend;
|
|
7
|
-
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
|
|
8
|
-
use std::sync::{Arc, Mutex};
|
|
9
|
-
|
|
10
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
11
|
-
mod native {
|
|
12
|
-
use super::*;
|
|
13
|
-
use std::thread;
|
|
14
|
-
use tungstenite::{connect, Message, WebSocket};
|
|
15
|
-
use tungstenite::stream::MaybeTlsStream;
|
|
16
|
-
use std::net::TcpStream;
|
|
17
|
-
|
|
18
|
-
/// WebSocket backend for native platforms (macOS, Linux, Windows).
|
|
19
|
-
pub struct WsBackend {
|
|
20
|
-
url: String,
|
|
21
|
-
mac: [u8; 6],
|
|
22
|
-
tx_to_ws: Option<Sender<Vec<u8>>>,
|
|
23
|
-
rx_from_ws: Option<Receiver<Vec<u8>>>,
|
|
24
|
-
connected: Arc<Mutex<bool>>,
|
|
25
|
-
error_message: Arc<Mutex<Option<String>>>,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
impl WsBackend {
|
|
29
|
-
pub fn new(url: &str) -> Self {
|
|
30
|
-
// Generate a random-ish MAC based on URL hash
|
|
31
|
-
let mut mac = [0x52, 0x54, 0x00, 0x00, 0x00, 0x00];
|
|
32
|
-
let hash: u32 = url.bytes().fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
|
|
33
|
-
mac[3] = ((hash >> 16) & 0xff) as u8;
|
|
34
|
-
mac[4] = ((hash >> 8) & 0xff) as u8;
|
|
35
|
-
mac[5] = (hash & 0xff) as u8;
|
|
36
|
-
|
|
37
|
-
Self {
|
|
38
|
-
url: url.to_string(),
|
|
39
|
-
mac,
|
|
40
|
-
tx_to_ws: None,
|
|
41
|
-
rx_from_ws: None,
|
|
42
|
-
connected: Arc::new(Mutex::new(false)),
|
|
43
|
-
error_message: Arc::new(Mutex::new(None)),
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/// Check if the backend is currently connected.
|
|
48
|
-
pub fn is_connected(&self) -> bool {
|
|
49
|
-
*self.connected.lock().unwrap()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/// Get any error message from the connection.
|
|
53
|
-
pub fn error_message(&self) -> Option<String> {
|
|
54
|
-
self.error_message.lock().unwrap().clone()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/// Reader thread - reads from WebSocket and sends to channel
|
|
58
|
-
fn reader_thread(
|
|
59
|
-
mut socket: WebSocket<MaybeTlsStream<TcpStream>>,
|
|
60
|
-
tx_received: Sender<Vec<u8>>,
|
|
61
|
-
rx_to_send: Receiver<Vec<u8>>,
|
|
62
|
-
connected: Arc<Mutex<bool>>,
|
|
63
|
-
) {
|
|
64
|
-
// Set socket to blocking mode for reliable reads
|
|
65
|
-
if let MaybeTlsStream::Plain(ref stream) = *socket.get_ref() {
|
|
66
|
-
let _ = stream.set_nonblocking(false);
|
|
67
|
-
// Set a read timeout so we can also check for outgoing messages
|
|
68
|
-
let _ = stream.set_read_timeout(Some(std::time::Duration::from_millis(10)));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
loop {
|
|
72
|
-
// First, check if we have outgoing messages to send
|
|
73
|
-
loop {
|
|
74
|
-
match rx_to_send.try_recv() {
|
|
75
|
-
Ok(data) => {
|
|
76
|
-
log::trace!("[WsBackend] Sending {} bytes", data.len());
|
|
77
|
-
if let Err(e) = socket.send(Message::Binary(data.into())) {
|
|
78
|
-
log::warn!("[WsBackend] Send error: {}", e);
|
|
79
|
-
*connected.lock().unwrap() = false;
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
Err(TryRecvError::Empty) => break,
|
|
84
|
-
Err(TryRecvError::Disconnected) => {
|
|
85
|
-
log::info!("[WsBackend] Send channel closed, exiting");
|
|
86
|
-
*connected.lock().unwrap() = false;
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Flush any pending writes
|
|
93
|
-
if let Err(e) = socket.flush() {
|
|
94
|
-
if !matches!(e, tungstenite::Error::Io(ref io) if io.kind() == std::io::ErrorKind::WouldBlock) {
|
|
95
|
-
log::warn!("[WsBackend] Flush error: {}", e);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Try to read from WebSocket
|
|
100
|
-
match socket.read() {
|
|
101
|
-
Ok(Message::Binary(data)) => {
|
|
102
|
-
log::debug!("[WsBackend] Received {} bytes from relay", data.len());
|
|
103
|
-
if tx_received.send(data.into()).is_err() {
|
|
104
|
-
log::info!("[WsBackend] Receiver dropped, exiting");
|
|
105
|
-
*connected.lock().unwrap() = false;
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
Ok(Message::Close(_)) => {
|
|
110
|
-
log::info!("[WsBackend] WebSocket closed by server");
|
|
111
|
-
*connected.lock().unwrap() = false;
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
Ok(Message::Ping(data)) => {
|
|
115
|
-
// Respond to ping with pong
|
|
116
|
-
let _ = socket.send(Message::Pong(data));
|
|
117
|
-
}
|
|
118
|
-
Ok(_) => {} // Ignore text, pong
|
|
119
|
-
Err(tungstenite::Error::Io(ref e)) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
|
120
|
-
// Timeout - no data available, continue loop
|
|
121
|
-
}
|
|
122
|
-
Err(tungstenite::Error::Io(ref e)) if e.kind() == std::io::ErrorKind::TimedOut => {
|
|
123
|
-
// Timeout - no data available, continue loop
|
|
124
|
-
}
|
|
125
|
-
Err(e) => {
|
|
126
|
-
log::warn!("[WsBackend] Read error: {}", e);
|
|
127
|
-
*connected.lock().unwrap() = false;
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
impl NetworkBackend for WsBackend {
|
|
136
|
-
fn init(&mut self) -> Result<(), String> {
|
|
137
|
-
log::info!("[WsBackend] Connecting to {}", self.url);
|
|
138
|
-
|
|
139
|
-
let (socket, _response) = connect(&self.url)
|
|
140
|
-
.map_err(|e| {
|
|
141
|
-
let msg = format!("Failed to connect: {}", e);
|
|
142
|
-
log::error!("[WsBackend] {}", msg);
|
|
143
|
-
*self.error_message.lock().unwrap() = Some(msg.clone());
|
|
144
|
-
msg
|
|
145
|
-
})?;
|
|
146
|
-
|
|
147
|
-
log::info!("[WsBackend] Connected successfully!");
|
|
148
|
-
*self.connected.lock().unwrap() = true;
|
|
149
|
-
*self.error_message.lock().unwrap() = None;
|
|
150
|
-
|
|
151
|
-
let (tx_to_ws, rx_to_send) = channel();
|
|
152
|
-
let (tx_received, rx_from_ws) = channel();
|
|
153
|
-
|
|
154
|
-
self.tx_to_ws = Some(tx_to_ws);
|
|
155
|
-
self.rx_from_ws = Some(rx_from_ws);
|
|
156
|
-
|
|
157
|
-
let connected = self.connected.clone();
|
|
158
|
-
|
|
159
|
-
thread::spawn(move || {
|
|
160
|
-
Self::reader_thread(socket, tx_received, rx_to_send, connected);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
log::info!("[WsBackend] Initialized successfully");
|
|
164
|
-
Ok(())
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
|
|
168
|
-
if let Some(ref rx) = self.rx_from_ws {
|
|
169
|
-
match rx.try_recv() {
|
|
170
|
-
Ok(data) => {
|
|
171
|
-
log::trace!("[WsBackend] recv() returning {} bytes", data.len());
|
|
172
|
-
Ok(Some(data))
|
|
173
|
-
}
|
|
174
|
-
Err(TryRecvError::Empty) => Ok(None),
|
|
175
|
-
Err(TryRecvError::Disconnected) => {
|
|
176
|
-
Err("WebSocket disconnected".to_string())
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
Ok(None)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
fn send(&self, buf: &[u8]) -> Result<(), String> {
|
|
185
|
-
if !*self.connected.lock().unwrap() {
|
|
186
|
-
return Err("Not connected".to_string());
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if let Some(ref tx) = self.tx_to_ws {
|
|
190
|
-
tx.send(buf.to_vec()).map_err(|e| format!("Send failed: {}", e))
|
|
191
|
-
} else {
|
|
192
|
-
Err("WebSocket not initialized".to_string())
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
fn mac_address(&self) -> [u8; 6] {
|
|
197
|
-
self.mac
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
#[cfg(target_arch = "wasm32")]
|
|
203
|
-
mod wasm {
|
|
204
|
-
use super::*;
|
|
205
|
-
use wasm_bindgen::prelude::*;
|
|
206
|
-
use wasm_bindgen::JsCast;
|
|
207
|
-
use web_sys::{WebSocket, MessageEvent, ErrorEvent, CloseEvent, BinaryType};
|
|
208
|
-
use js_sys::{ArrayBuffer, Uint8Array};
|
|
209
|
-
use std::cell::RefCell;
|
|
210
|
-
use std::rc::Rc;
|
|
211
|
-
use std::collections::VecDeque;
|
|
212
|
-
|
|
213
|
-
/// Maximum number of packets to buffer before dropping
|
|
214
|
-
const MAX_RX_QUEUE_SIZE: usize = 256;
|
|
215
|
-
|
|
216
|
-
/// Connection state for tracking
|
|
217
|
-
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
218
|
-
pub enum ConnectionState {
|
|
219
|
-
Disconnected,
|
|
220
|
-
Connecting,
|
|
221
|
-
Connected,
|
|
222
|
-
Error,
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/// WebSocket backend for WASM/browser.
|
|
226
|
-
pub struct WsBackend {
|
|
227
|
-
url: String,
|
|
228
|
-
mac: [u8; 6],
|
|
229
|
-
ws: Option<WebSocket>,
|
|
230
|
-
rx_queue: Rc<RefCell<VecDeque<Vec<u8>>>>,
|
|
231
|
-
state: Rc<RefCell<ConnectionState>>,
|
|
232
|
-
error_message: Rc<RefCell<Option<String>>>,
|
|
233
|
-
packets_dropped: Rc<RefCell<u64>>,
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// WASM types are !Send by default, but we need Send for NetworkBackend.
|
|
237
|
-
// This is safe because WASM is single-threaded.
|
|
238
|
-
unsafe impl Send for WsBackend {}
|
|
239
|
-
|
|
240
|
-
impl WsBackend {
|
|
241
|
-
pub fn new(url: &str) -> Self {
|
|
242
|
-
let mut mac = [0x52, 0x54, 0x00, 0x00, 0x00, 0x00];
|
|
243
|
-
let hash: u32 = url.bytes().fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
|
|
244
|
-
mac[3] = ((hash >> 16) & 0xff) as u8;
|
|
245
|
-
mac[4] = ((hash >> 8) & 0xff) as u8;
|
|
246
|
-
mac[5] = (hash & 0xff) as u8;
|
|
247
|
-
|
|
248
|
-
Self {
|
|
249
|
-
url: url.to_string(),
|
|
250
|
-
mac,
|
|
251
|
-
ws: None,
|
|
252
|
-
rx_queue: Rc::new(RefCell::new(VecDeque::with_capacity(MAX_RX_QUEUE_SIZE))),
|
|
253
|
-
state: Rc::new(RefCell::new(ConnectionState::Disconnected)),
|
|
254
|
-
error_message: Rc::new(RefCell::new(None)),
|
|
255
|
-
packets_dropped: Rc::new(RefCell::new(0)),
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/// Check if the backend is currently connected.
|
|
260
|
-
pub fn is_connected(&self) -> bool {
|
|
261
|
-
*self.state.borrow() == ConnectionState::Connected
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/// Get the current connection state.
|
|
265
|
-
pub fn connection_state(&self) -> ConnectionState {
|
|
266
|
-
*self.state.borrow()
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/// Get any error message.
|
|
270
|
-
pub fn error_message(&self) -> Option<String> {
|
|
271
|
-
self.error_message.borrow().clone()
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/// Get the number of packets dropped due to buffer overflow.
|
|
275
|
-
pub fn packets_dropped(&self) -> u64 {
|
|
276
|
-
*self.packets_dropped.borrow()
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
impl NetworkBackend for WsBackend {
|
|
281
|
-
fn init(&mut self) -> Result<(), String> {
|
|
282
|
-
*self.state.borrow_mut() = ConnectionState::Connecting;
|
|
283
|
-
*self.error_message.borrow_mut() = None;
|
|
284
|
-
|
|
285
|
-
let ws = WebSocket::new(&self.url)
|
|
286
|
-
.map_err(|e| {
|
|
287
|
-
*self.state.borrow_mut() = ConnectionState::Error;
|
|
288
|
-
let msg = format!("Failed to create WebSocket: {:?}", e);
|
|
289
|
-
*self.error_message.borrow_mut() = Some(msg.clone());
|
|
290
|
-
msg
|
|
291
|
-
})?;
|
|
292
|
-
|
|
293
|
-
ws.set_binary_type(BinaryType::Arraybuffer);
|
|
294
|
-
|
|
295
|
-
// Set up message handler with buffer overflow protection
|
|
296
|
-
let rx_queue = self.rx_queue.clone();
|
|
297
|
-
let packets_dropped = self.packets_dropped.clone();
|
|
298
|
-
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
|
|
299
|
-
if let Ok(abuf) = e.data().dyn_into::<ArrayBuffer>() {
|
|
300
|
-
let array = Uint8Array::new(&abuf);
|
|
301
|
-
let mut data = vec![0u8; array.length() as usize];
|
|
302
|
-
array.copy_to(&mut data);
|
|
303
|
-
|
|
304
|
-
let mut queue = rx_queue.borrow_mut();
|
|
305
|
-
// Handle buffer overflow - drop oldest packets if queue is full
|
|
306
|
-
while queue.len() >= MAX_RX_QUEUE_SIZE {
|
|
307
|
-
queue.pop_front();
|
|
308
|
-
*packets_dropped.borrow_mut() += 1;
|
|
309
|
-
}
|
|
310
|
-
queue.push_back(data);
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
|
|
314
|
-
onmessage_callback.forget();
|
|
315
|
-
|
|
316
|
-
// Set up open handler
|
|
317
|
-
let state_open = self.state.clone();
|
|
318
|
-
let error_clear = self.error_message.clone();
|
|
319
|
-
let onopen_callback = Closure::<dyn FnMut()>::new(move || {
|
|
320
|
-
*state_open.borrow_mut() = ConnectionState::Connected;
|
|
321
|
-
*error_clear.borrow_mut() = None;
|
|
322
|
-
log::info!("[WsBackend] WebSocket connected!");
|
|
323
|
-
});
|
|
324
|
-
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
|
|
325
|
-
onopen_callback.forget();
|
|
326
|
-
|
|
327
|
-
// Set up error handler
|
|
328
|
-
let state_error = self.state.clone();
|
|
329
|
-
let error_message = self.error_message.clone();
|
|
330
|
-
let onerror_callback = Closure::<dyn FnMut(_)>::new(move |e: ErrorEvent| {
|
|
331
|
-
*state_error.borrow_mut() = ConnectionState::Error;
|
|
332
|
-
let msg = format!("WebSocket error: {}", e.message());
|
|
333
|
-
*error_message.borrow_mut() = Some(msg.clone());
|
|
334
|
-
log::error!("[WsBackend] {}", msg);
|
|
335
|
-
});
|
|
336
|
-
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
|
|
337
|
-
onerror_callback.forget();
|
|
338
|
-
|
|
339
|
-
// Set up close handler
|
|
340
|
-
let state_close = self.state.clone();
|
|
341
|
-
let onclose_callback = Closure::<dyn FnMut(_)>::new(move |e: CloseEvent| {
|
|
342
|
-
*state_close.borrow_mut() = ConnectionState::Disconnected;
|
|
343
|
-
log::info!("[WsBackend] WebSocket closed (code: {}, reason: {})",
|
|
344
|
-
e.code(), e.reason());
|
|
345
|
-
});
|
|
346
|
-
ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
|
347
|
-
onclose_callback.forget();
|
|
348
|
-
|
|
349
|
-
self.ws = Some(ws);
|
|
350
|
-
|
|
351
|
-
log::info!("[WsBackend] Initialized, connecting to {}", self.url);
|
|
352
|
-
Ok(())
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
|
|
356
|
-
// Check connection state
|
|
357
|
-
let state = *self.state.borrow();
|
|
358
|
-
if state == ConnectionState::Error {
|
|
359
|
-
if let Some(msg) = self.error_message.borrow().clone() {
|
|
360
|
-
return Err(msg);
|
|
361
|
-
}
|
|
362
|
-
return Err("WebSocket in error state".to_string());
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
Ok(self.rx_queue.borrow_mut().pop_front())
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
fn send(&self, buf: &[u8]) -> Result<(), String> {
|
|
369
|
-
let state = *self.state.borrow();
|
|
370
|
-
|
|
371
|
-
if state != ConnectionState::Connected {
|
|
372
|
-
// Silently drop if not connected (don't spam errors)
|
|
373
|
-
return Ok(());
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if let Some(ref ws) = self.ws {
|
|
377
|
-
let array = Uint8Array::from(buf);
|
|
378
|
-
ws.send_with_array_buffer(&array.buffer())
|
|
379
|
-
.map_err(|e| format!("Send failed: {:?}", e))
|
|
380
|
-
} else {
|
|
381
|
-
Err("WebSocket not initialized".to_string())
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
fn mac_address(&self) -> [u8; 6] {
|
|
386
|
-
self.mac
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Re-export the appropriate backend based on target
|
|
392
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
393
|
-
pub use native::WsBackend;
|
|
394
|
-
|
|
395
|
-
#[cfg(target_arch = "wasm32")]
|
|
396
|
-
pub use wasm::WsBackend;
|