virtual-machine 0.0.4 → 0.0.14

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.
@@ -1,446 +0,0 @@
1
- //! WebTransport network backend with P2P relay protocol support.
2
- //!
3
- //! This backend tunnels Ethernet frames over WebTransport DATAGRAMs
4
- //! using the relay protocol:
5
- //! - 0x00 prefix: Control messages (JSON-encoded)
6
- //! - 0x01 prefix: Ethernet data frames
7
-
8
- use crate::net::NetworkBackend;
9
-
10
- /// Message type prefix for control messages
11
- const MSG_TYPE_CONTROL: u8 = 0x00;
12
- /// Message type prefix for Ethernet data frames
13
- const MSG_TYPE_DATA: u8 = 0x01;
14
-
15
- /// Control message for registration
16
- fn make_register_message(mac: &[u8; 6]) -> Vec<u8> {
17
- let json = format!(
18
- r#"{{"type":"Register","mac":[{},{},{},{},{},{}]}}"#,
19
- mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
20
- );
21
- let mut msg = Vec::with_capacity(1 + json.len());
22
- msg.push(MSG_TYPE_CONTROL);
23
- msg.extend(json.bytes());
24
- msg
25
- }
26
-
27
- /// Encode an Ethernet frame with the data prefix
28
- fn encode_data_frame(ethernet_frame: &[u8]) -> Vec<u8> {
29
- let mut frame = Vec::with_capacity(1 + ethernet_frame.len());
30
- frame.push(MSG_TYPE_DATA);
31
- frame.extend(ethernet_frame);
32
- frame
33
- }
34
-
35
- /// Decode a received message, stripping the type prefix for data frames
36
- fn decode_message(data: &[u8]) -> Option<Vec<u8>> {
37
- if data.is_empty() {
38
- return None;
39
- }
40
-
41
- match data[0] {
42
- MSG_TYPE_DATA => {
43
- // Return the Ethernet frame without the prefix
44
- Some(data[1..].to_vec())
45
- }
46
- MSG_TYPE_CONTROL => {
47
- // Control messages are handled internally, not passed to the VM
48
- // Log assigned IP if present
49
- if let Ok(json_str) = std::str::from_utf8(&data[1..]) {
50
- if json_str.contains("\"type\":\"Assigned\"") {
51
- log::info!("[WebTransport] Received IP assignment: {}", json_str);
52
- } else if json_str.contains("\"type\":\"HeartbeatAck\"") {
53
- log::trace!("[WebTransport] Heartbeat acknowledged");
54
- } else if json_str.contains("\"type\":\"Error\"") {
55
- log::error!("[WebTransport] Error from relay: {}", json_str);
56
- }
57
- }
58
- None
59
- }
60
- _ => {
61
- log::warn!("[WebTransport] Unknown message type: {}", data[0]);
62
- None
63
- }
64
- }
65
- }
66
-
67
- #[cfg(not(target_arch = "wasm32"))]
68
- mod native {
69
- use super::*;
70
- use std::sync::Arc;
71
- use std::sync::atomic::{AtomicBool, Ordering};
72
- use std::sync::mpsc::{channel, Receiver, Sender};
73
- use std::thread;
74
- use std::time::Duration;
75
- use tokio::runtime::Runtime;
76
- use wtransport::tls::Sha256Digest;
77
- use wtransport::ClientConfig;
78
- use wtransport::Endpoint;
79
-
80
- pub struct WebTransportBackend {
81
- tx_to_transport: Option<Sender<Vec<u8>>>,
82
- rx_from_transport: Option<Receiver<Vec<u8>>>,
83
- mac: [u8; 6],
84
- registered: Arc<AtomicBool>,
85
- }
86
-
87
- impl WebTransportBackend {
88
- pub fn new(url: &str, cert_hash: Option<String>) -> Self {
89
- let mut mac = [0x52, 0x54, 0x00, 0x00, 0x00, 0x00];
90
- // Generate MAC from URL hash
91
- let hash: u32 = url
92
- .bytes()
93
- .fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
94
- mac[3] = ((hash >> 16) & 0xff) as u8;
95
- mac[4] = ((hash >> 8) & 0xff) as u8;
96
- mac[5] = (hash & 0xff) as u8;
97
-
98
- let (tx_to_transport, rx_to_transport) = channel::<Vec<u8>>();
99
- let (tx_from_transport, rx_from_transport) = channel::<Vec<u8>>();
100
-
101
- let url = url.to_string();
102
- let mac_copy = mac;
103
- let registered = Arc::new(AtomicBool::new(false));
104
- let registered_clone = registered.clone();
105
-
106
- thread::spawn(move || {
107
- let rt = Runtime::new().unwrap();
108
- rt.block_on(async move {
109
- let config = if let Some(hash_str) = cert_hash {
110
- // Parse hex hash
111
- let bytes =
112
- hex::decode(hash_str.replace(":", "")).expect("Invalid hex hash");
113
- let array: [u8; 32] = bytes.try_into().expect("Invalid hash length");
114
- let digest = Sha256Digest::from(array);
115
- ClientConfig::builder()
116
- .with_bind_default()
117
- .with_server_certificate_hashes(vec![digest])
118
- .build()
119
- } else {
120
- ClientConfig::builder()
121
- .with_bind_default()
122
- .with_no_cert_validation()
123
- .build()
124
- };
125
-
126
- let endpoint = Endpoint::client(config).unwrap();
127
-
128
- log::info!("[WebTransport] Connecting to {}...", url);
129
- let connection = match endpoint.connect(url).await {
130
- Ok(conn) => conn,
131
- Err(e) => {
132
- log::error!("[WebTransport] Connection failed: {}", e);
133
- return;
134
- }
135
- };
136
- log::info!("[WebTransport] Connected!");
137
-
138
- // Send registration message
139
- let register_msg = make_register_message(&mac_copy);
140
- if let Err(e) = connection.send_datagram(register_msg) {
141
- log::error!("[WebTransport] Failed to send registration: {}", e);
142
- return;
143
- }
144
- log::info!("[WebTransport] Registration sent, MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
145
- mac_copy[0], mac_copy[1], mac_copy[2], mac_copy[3], mac_copy[4], mac_copy[5]);
146
-
147
- let connection = Arc::new(connection);
148
- let conn_send = connection.clone();
149
- let conn_recv = connection.clone();
150
-
151
- // Sender task - frames Ethernet data with protocol prefix
152
- tokio::spawn(async move {
153
- loop {
154
- if let Ok(data) = rx_to_transport.try_recv() {
155
- // Data from VM is already framed by send()
156
- if let Err(e) = conn_send.send_datagram(data) {
157
- log::error!("Failed to send datagram: {}", e);
158
- break;
159
- }
160
- } else {
161
- tokio::time::sleep(Duration::from_millis(1)).await;
162
- }
163
- }
164
- });
165
-
166
- // Receiver loop - decodes protocol and passes Ethernet frames to VM
167
- loop {
168
- match conn_recv.receive_datagram().await {
169
- Ok(datagram) => {
170
- let data = datagram.to_vec();
171
-
172
- // Check for Assigned message to confirm registration
173
- if !data.is_empty() && data[0] == MSG_TYPE_CONTROL {
174
- if let Ok(json_str) = std::str::from_utf8(&data[1..]) {
175
- if json_str.contains("\"type\":\"Assigned\"") {
176
- registered_clone.store(true, Ordering::SeqCst);
177
- log::info!("[WebTransport] Registered with relay: {}", json_str);
178
- }
179
- }
180
- }
181
-
182
- // Decode and forward Ethernet frames
183
- if let Some(ethernet_frame) = decode_message(&data) {
184
- let _ = tx_from_transport.send(ethernet_frame);
185
- }
186
- }
187
- Err(e) => {
188
- log::error!("Receive error: {}", e);
189
- break;
190
- }
191
- }
192
- }
193
- });
194
- });
195
-
196
- Self {
197
- tx_to_transport: Some(tx_to_transport),
198
- rx_from_transport: Some(rx_from_transport),
199
- mac,
200
- registered,
201
- }
202
- }
203
-
204
- /// Check if registered with the relay
205
- pub fn is_registered(&self) -> bool {
206
- self.registered.load(Ordering::SeqCst)
207
- }
208
- }
209
-
210
- impl NetworkBackend for WebTransportBackend {
211
- fn init(&mut self) -> Result<(), String> {
212
- Ok(())
213
- }
214
-
215
- fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
216
- if let Some(rx) = &self.rx_from_transport {
217
- match rx.try_recv() {
218
- Ok(data) => Ok(Some(data)),
219
- Err(std::sync::mpsc::TryRecvError::Empty) => Ok(None),
220
- Err(_) => Err("Disconnected".to_string()),
221
- }
222
- } else {
223
- Ok(None)
224
- }
225
- }
226
-
227
- fn send(&self, buf: &[u8]) -> Result<(), String> {
228
- if let Some(tx) = &self.tx_to_transport {
229
- // Frame the Ethernet data with the protocol prefix
230
- let framed = encode_data_frame(buf);
231
- tx.send(framed).map_err(|e| e.to_string())?;
232
- Ok(())
233
- } else {
234
- Err("Not connected".to_string())
235
- }
236
- }
237
-
238
- fn mac_address(&self) -> [u8; 6] {
239
- self.mac
240
- }
241
- }
242
- }
243
-
244
- #[cfg(target_arch = "wasm32")]
245
- mod wasm {
246
- use super::*;
247
- use js_sys::{Array, Uint8Array};
248
- use std::cell::RefCell;
249
- use std::collections::VecDeque;
250
- use std::rc::Rc;
251
- use wasm_bindgen::prelude::*;
252
- use wasm_bindgen::JsCast;
253
- use wasm_bindgen_futures::JsFuture;
254
- use web_sys::{
255
- ReadableStreamDefaultReader, WebTransport, WebTransportHash, WebTransportOptions,
256
- WritableStreamDefaultWriter,
257
- };
258
-
259
- pub struct WebTransportBackend {
260
- url: String,
261
- cert_hash: Option<String>,
262
- mac: [u8; 6],
263
- transport: Option<WebTransport>,
264
- writer: Option<WritableStreamDefaultWriter>,
265
- rx_queue: Rc<RefCell<VecDeque<Vec<u8>>>>,
266
- registered: Rc<RefCell<bool>>,
267
- }
268
-
269
- // WASM is single threaded
270
- unsafe impl Send for WebTransportBackend {}
271
-
272
- impl WebTransportBackend {
273
- pub fn new(url: &str, cert_hash: Option<String>) -> Self {
274
- let mut mac = [0x52, 0x54, 0x00, 0x00, 0x00, 0x00];
275
- let hash: u32 = url
276
- .bytes()
277
- .fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
278
- mac[3] = ((hash >> 16) & 0xff) as u8;
279
- mac[4] = ((hash >> 8) & 0xff) as u8;
280
- mac[5] = (hash & 0xff) as u8;
281
-
282
- Self {
283
- url: url.to_string(),
284
- cert_hash,
285
- mac,
286
- transport: None,
287
- writer: None,
288
- rx_queue: Rc::new(RefCell::new(VecDeque::new())),
289
- registered: Rc::new(RefCell::new(false)),
290
- }
291
- }
292
-
293
- /// Check if registered with the relay
294
- pub fn is_registered(&self) -> bool {
295
- *self.registered.borrow()
296
- }
297
- }
298
-
299
- impl NetworkBackend for WebTransportBackend {
300
- fn init(&mut self) -> Result<(), String> {
301
- let options = WebTransportOptions::new();
302
-
303
- if let Some(hash_hex) = &self.cert_hash {
304
- let bytes = hex::decode(hash_hex.replace(":", "")).map_err(|e| e.to_string())?;
305
- let array = Uint8Array::from(&bytes[..]);
306
-
307
- let hash_obj = WebTransportHash::new();
308
- hash_obj.set_algorithm("sha-256");
309
- hash_obj.set_value(&array);
310
-
311
- let hashes = Array::new();
312
- hashes.push(&hash_obj);
313
- options.set_server_certificate_hashes(&hashes);
314
- }
315
-
316
- let transport = WebTransport::new_with_options(&self.url, &options)
317
- .map_err(|e| format!("Failed to create WebTransport: {:?}", e))?;
318
-
319
- let rx_queue = self.rx_queue.clone();
320
- let registered = self.registered.clone();
321
- let mac = self.mac;
322
-
323
- // Setup writer first so we can send registration
324
- let datagrams = transport.datagrams();
325
- let writable = datagrams.writable();
326
- let writer = writable.get_writer().map_err(|e| format!("{:?}", e))?;
327
-
328
- let writer_clone = writer.clone();
329
- let transport_clone = transport.clone();
330
- let ready_promise = transport.ready();
331
-
332
- wasm_bindgen_futures::spawn_local(async move {
333
- match JsFuture::from(ready_promise).await {
334
- Ok(_) => {
335
- log::info!("WebTransport ready!");
336
-
337
- // Send registration message
338
- let register_msg = make_register_message(&mac);
339
- let array = Uint8Array::from(&register_msg[..]);
340
- let promise = writer_clone.write_with_chunk(&array);
341
- if let Err(e) = JsFuture::from(promise).await {
342
- log::error!("Failed to send registration: {:?}", e);
343
- return;
344
- }
345
- log::info!(
346
- "Registration sent, MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
347
- mac[0],
348
- mac[1],
349
- mac[2],
350
- mac[3],
351
- mac[4],
352
- mac[5]
353
- );
354
-
355
- // Setup reader
356
- let datagrams = transport_clone.datagrams();
357
- let readable = datagrams.readable();
358
- let reader: ReadableStreamDefaultReader =
359
- readable.get_reader().unchecked_into();
360
-
361
- loop {
362
- match JsFuture::from(reader.read()).await {
363
- Ok(result) => {
364
- let done = js_sys::Reflect::get(
365
- &result,
366
- &JsValue::from_str("done"),
367
- )
368
- .unwrap()
369
- .as_bool()
370
- .unwrap();
371
- if done {
372
- log::info!("WebTransport reader done");
373
- break;
374
- }
375
- let value = js_sys::Reflect::get(
376
- &result,
377
- &JsValue::from_str("value"),
378
- )
379
- .unwrap();
380
- let array = Uint8Array::new(&value);
381
- let data = array.to_vec();
382
-
383
- // Check for Assigned message
384
- if !data.is_empty() && data[0] == MSG_TYPE_CONTROL {
385
- if let Ok(json_str) = std::str::from_utf8(&data[1..]) {
386
- if json_str.contains("\"type\":\"Assigned\"") {
387
- *registered.borrow_mut() = true;
388
- log::info!(
389
- "Registered with relay: {}",
390
- json_str
391
- );
392
- }
393
- }
394
- }
395
-
396
- // Decode and queue Ethernet frames
397
- if let Some(ethernet_frame) = decode_message(&data) {
398
- rx_queue.borrow_mut().push_back(ethernet_frame);
399
- }
400
- }
401
- Err(e) => {
402
- log::error!("Read error: {:?}", e);
403
- break;
404
- }
405
- }
406
- }
407
- }
408
- Err(e) => {
409
- log::error!("WebTransport failed to connect: {:?}", e);
410
- }
411
- }
412
- });
413
-
414
- self.transport = Some(transport);
415
- self.writer = Some(writer);
416
-
417
- Ok(())
418
- }
419
-
420
- fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
421
- Ok(self.rx_queue.borrow_mut().pop_front())
422
- }
423
-
424
- fn send(&self, buf: &[u8]) -> Result<(), String> {
425
- if let Some(writer) = &self.writer {
426
- // Frame the Ethernet data with the protocol prefix
427
- let framed = encode_data_frame(buf);
428
- let array = Uint8Array::from(&framed[..]);
429
- let _ = writer.write_with_chunk(&array);
430
- Ok(())
431
- } else {
432
- Err("Not connected".to_string())
433
- }
434
- }
435
-
436
- fn mac_address(&self) -> [u8; 6] {
437
- self.mac
438
- }
439
- }
440
- }
441
-
442
- #[cfg(not(target_arch = "wasm32"))]
443
- pub use native::WebTransportBackend;
444
-
445
- #[cfg(target_arch = "wasm32")]
446
- pub use wasm::WebTransportBackend;