virtual-machine 0.0.0-rc1
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/.yarn/install-state.gz +0 -0
- package/.yarnrc.yml +1 -0
- package/Cargo.toml +48 -0
- package/build/cli.js +904 -0
- package/build/index.d.ts +127 -0
- package/build/index.js +723 -0
- package/build/index.mjs +17 -0
- package/build/riscv_vm-CYH5SWIJ.mjs +672 -0
- package/build.sh +26 -0
- package/cli.ts +251 -0
- package/index.ts +15 -0
- package/package.json +30 -0
- package/src/bus.rs +558 -0
- package/src/clint.rs +132 -0
- package/src/console.rs +83 -0
- package/src/cpu.rs +1913 -0
- package/src/csr.rs +67 -0
- package/src/decoder.rs +789 -0
- package/src/dram.rs +146 -0
- package/src/emulator.rs +603 -0
- package/src/lib.rs +270 -0
- package/src/main.rs +363 -0
- package/src/mmu.rs +331 -0
- package/src/net.rs +121 -0
- package/src/net_tap.rs +164 -0
- package/src/net_webtransport.rs +446 -0
- package/src/net_ws.rs +396 -0
- package/src/plic.rs +261 -0
- package/src/uart.rs +233 -0
- package/src/virtio.rs +1074 -0
- package/tsconfig.json +19 -0
- package/tsup/index.ts +80 -0
- package/tsup/tsup.cli.ts +8 -0
- package/tsup/tsup.core.cjs.ts +7 -0
- package/tsup/tsup.core.esm.ts +8 -0
|
@@ -0,0 +1,446 @@
|
|
|
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
|
+
use std::sync::Arc;
|
|
10
|
+
|
|
11
|
+
/// Message type prefix for control messages
|
|
12
|
+
const MSG_TYPE_CONTROL: u8 = 0x00;
|
|
13
|
+
/// Message type prefix for Ethernet data frames
|
|
14
|
+
const MSG_TYPE_DATA: u8 = 0x01;
|
|
15
|
+
|
|
16
|
+
/// Control message for registration
|
|
17
|
+
fn make_register_message(mac: &[u8; 6]) -> Vec<u8> {
|
|
18
|
+
let json = format!(
|
|
19
|
+
r#"{{"type":"Register","mac":[{},{},{},{},{},{}]}}"#,
|
|
20
|
+
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
|
|
21
|
+
);
|
|
22
|
+
let mut msg = Vec::with_capacity(1 + json.len());
|
|
23
|
+
msg.push(MSG_TYPE_CONTROL);
|
|
24
|
+
msg.extend(json.bytes());
|
|
25
|
+
msg
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Encode an Ethernet frame with the data prefix
|
|
29
|
+
fn encode_data_frame(ethernet_frame: &[u8]) -> Vec<u8> {
|
|
30
|
+
let mut frame = Vec::with_capacity(1 + ethernet_frame.len());
|
|
31
|
+
frame.push(MSG_TYPE_DATA);
|
|
32
|
+
frame.extend(ethernet_frame);
|
|
33
|
+
frame
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Decode a received message, stripping the type prefix for data frames
|
|
37
|
+
fn decode_message(data: &[u8]) -> Option<Vec<u8>> {
|
|
38
|
+
if data.is_empty() {
|
|
39
|
+
return None;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
match data[0] {
|
|
43
|
+
MSG_TYPE_DATA => {
|
|
44
|
+
// Return the Ethernet frame without the prefix
|
|
45
|
+
Some(data[1..].to_vec())
|
|
46
|
+
}
|
|
47
|
+
MSG_TYPE_CONTROL => {
|
|
48
|
+
// Control messages are handled internally, not passed to the VM
|
|
49
|
+
// Log assigned IP if present
|
|
50
|
+
if let Ok(json_str) = std::str::from_utf8(&data[1..]) {
|
|
51
|
+
if json_str.contains("\"type\":\"Assigned\"") {
|
|
52
|
+
log::info!("[WebTransport] Received IP assignment: {}", json_str);
|
|
53
|
+
} else if json_str.contains("\"type\":\"HeartbeatAck\"") {
|
|
54
|
+
log::trace!("[WebTransport] Heartbeat acknowledged");
|
|
55
|
+
} else if json_str.contains("\"type\":\"Error\"") {
|
|
56
|
+
log::error!("[WebTransport] Error from relay: {}", json_str);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
None
|
|
60
|
+
}
|
|
61
|
+
_ => {
|
|
62
|
+
log::warn!("[WebTransport] Unknown message type: {}", data[0]);
|
|
63
|
+
None
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
69
|
+
mod native {
|
|
70
|
+
use super::*;
|
|
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(®ister_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;
|