1#![cfg_attr(
2 target_family = "wasm",
3 doc = "The [`web_wt_sys`]-powered implementation of [`xwt_core`]."
4)]
5#![cfg_attr(
6 not(target_family = "wasm"),
7 doc = "The `web_wt_sys`-powered implementation of `xwt_core`."
8)]
9#![cfg(target_family = "wasm")]
10
11use std::{num::NonZeroUsize, rc::Rc};
12
13use wasm_bindgen::prelude::*;
14
15mod error;
16mod error_as_error_code;
17mod options;
18
19pub use web_sys;
20pub use web_wt_sys;
21pub use xwt_core as core;
22
23pub use {error::*, options::*};
24
25#[derive(Debug, Clone, Default)]
30pub struct Endpoint {
31 pub options: web_wt_sys::WebTransportOptions,
33}
34
35impl xwt_core::endpoint::Connect for Endpoint {
36 type Error = Error;
37 type Connecting = Connecting;
38
39 async fn connect(&self, url: &str) -> Result<Self::Connecting, Self::Error> {
40 let transport = web_wt_sys::WebTransport::new_with_options(url, &self.options)?;
41 Ok(Connecting { transport })
42 }
43}
44
45#[derive(Debug)]
48pub struct Connecting {
49 pub transport: web_wt_sys::WebTransport,
51}
52
53impl xwt_core::endpoint::connect::Connecting for Connecting {
54 type Session = Session;
55 type Error = Error;
56
57 async fn wait_connect(self) -> Result<Self::Session, Self::Error> {
58 let Connecting { transport } = self;
59 wasm_bindgen_futures::JsFuture::from(transport.ready()).await?;
60
61 Ok(Session::new(transport))
62 }
63}
64
65#[derive(Debug)]
70pub struct Session {
71 transport: Option<Rc<web_wt_sys::WebTransport>>,
73
74 pub datagrams: Datagrams,
76
77 pub close_on_drop: bool,
79}
80
81impl Session {
82 pub fn new(transport: web_wt_sys::WebTransport) -> Self {
84 let datagrams = Datagrams::from_transport(&transport);
85 Self {
86 transport: Some(Rc::new(transport)),
87 datagrams,
88 close_on_drop: true,
89 }
90 }
91
92 pub fn try_unwrap(mut self) -> Result<web_wt_sys::WebTransport, Self> {
95 let transport = self.transport.take().unwrap();
98
99 let unwrapped = match Rc::try_unwrap(transport) {
106 Ok(unwrapped) => unwrapped,
107 Err(transport) => {
108 let _ = self.transport.insert(transport);
109 return Err(self);
110 }
111 };
112
113 self.close_on_drop = false;
115
116 drop(self);
118
119 Ok(unwrapped)
121 }
122
123 pub const fn transport_ref(&self) -> &Rc<web_wt_sys::WebTransport> {
125 self.transport.as_ref().unwrap()
128 }
129}
130
131impl Drop for Session {
132 fn drop(&mut self) {
133 if self.close_on_drop {
134 self.transport_ref().close();
135 }
136 }
137}
138
139#[derive(Debug)]
147pub enum DatagramsReader {
148 Byob(web_sys::ReadableStreamByobReader),
150
151 Default(web_sys::ReadableStreamDefaultReader),
154}
155
156impl DatagramsReader {
157 pub fn for_stream(readable_stream: web_sys::ReadableStream) -> Self {
160 match web_sys_stream_utils::try_get_reader_byob(readable_stream.clone()) {
161 Ok(reader) => Self::Byob(reader),
162 Err(_) => Self::Default(web_sys_stream_utils::get_reader(readable_stream)),
163 }
164 }
165
166 pub fn release_lock(&self) {
168 match self {
169 Self::Byob(reader) => reader.release_lock(),
170 Self::Default(reader) => reader.release_lock(),
171 }
172 }
173}
174
175#[derive(Debug)]
178pub struct Datagrams {
179 pub readable_stream_reader: DatagramsReader,
181
182 pub writable_stream_writer: web_sys::WritableStreamDefaultWriter,
184
185 pub read_buffer_size: u32,
188
189 pub read_buffer: tokio::sync::Mutex<Option<js_sys::ArrayBuffer>>,
191
192 pub unlock_streams_on_drop: bool,
194}
195
196impl Datagrams {
197 pub fn from_transport(transport: &web_wt_sys::WebTransport) -> Self {
199 Self::from_transport_datagrams(&transport.datagrams())
200 }
201
202 pub fn from_transport_datagrams(
204 datagrams: &web_wt_sys::WebTransportDatagramDuplexStream,
205 ) -> Self {
206 let read_buffer_size = 65536; let readable_stream_reader = DatagramsReader::for_stream(datagrams.readable());
209 let writable: web_sys::WritableStream = if datagrams.has_create_writable() {
212 datagrams.create_writable().unwrap().into()
213 } else {
214 #[expect(deprecated)]
215 let writable = datagrams.writable();
216 writable
217 };
218 let writable_stream_writer = web_sys_stream_utils::get_writer(writable);
219
220 let read_buffer = js_sys::ArrayBuffer::new(read_buffer_size);
221 let read_buffer = tokio::sync::Mutex::new(Some(read_buffer));
222
223 Self {
224 readable_stream_reader,
225 writable_stream_writer,
226 read_buffer_size,
227 read_buffer,
228 unlock_streams_on_drop: true,
229 }
230 }
231}
232
233impl Drop for Datagrams {
234 fn drop(&mut self) {
235 if self.unlock_streams_on_drop {
236 self.readable_stream_reader.release_lock();
237 self.writable_stream_writer.release_lock();
238 }
239 }
240}
241
242impl xwt_core::session::stream::SendSpec for Session {
243 type SendStream = SendStream;
244}
245
246impl xwt_core::session::stream::RecvSpec for Session {
247 type RecvStream = RecvStream;
248}
249
250pub struct SendStream {
252 pub transport: Rc<web_wt_sys::WebTransport>,
254
255 pub stream: web_wt_sys::WebTransportSendStream,
257
258 pub writer: web_sys_async_io::Writer,
260
261 pub unlock_writer_on_drop: bool,
263}
264
265impl Drop for SendStream {
266 fn drop(&mut self) {
267 if self.unlock_writer_on_drop {
268 self.writer.inner.release_lock();
269 }
270 }
271}
272
273pub struct RecvStream {
275 pub transport: Rc<web_wt_sys::WebTransport>,
277
278 pub stream: web_wt_sys::WebTransportReceiveStream,
280
281 pub reader: web_sys_async_io::Reader,
283
284 pub unlock_reader_on_drop: bool,
286}
287
288impl Drop for RecvStream {
289 fn drop(&mut self) {
290 if self.unlock_reader_on_drop {
291 self.reader.inner.release_lock();
292 }
293 }
294}
295
296fn wrap_recv_stream(
298 transport: &Rc<web_wt_sys::WebTransport>,
299 stream: web_wt_sys::WebTransportReceiveStream,
300) -> RecvStream {
301 let reader = web_sys_stream_utils::get_reader_byob(stream.clone());
302 let reader: JsValue = reader.into();
303 let reader = reader.into();
304 let reader = web_sys_async_io::Reader::new(reader);
305
306 RecvStream {
307 transport: Rc::clone(transport),
308 stream,
309 reader,
310 unlock_reader_on_drop: true,
311 }
312}
313
314fn wrap_send_stream(
316 transport: &Rc<web_wt_sys::WebTransport>,
317 stream: web_wt_sys::WebTransportSendStream,
318) -> SendStream {
319 let writer = stream.get_writer().unwrap();
320 let writer = web_sys_async_io::Writer::new(writer.into());
321 SendStream {
322 transport: Rc::clone(transport),
323 stream,
324 writer,
325 unlock_writer_on_drop: true,
326 }
327}
328
329fn wrap_bi_stream(
331 transport: &Rc<web_wt_sys::WebTransport>,
332 stream: web_wt_sys::WebTransportBidirectionalStream,
333) -> (SendStream, RecvStream) {
334 let writeable = stream.writable();
335 let readable = stream.readable();
336
337 let send_stream = wrap_send_stream(transport, writeable);
338 let recv_stream = wrap_recv_stream(transport, readable);
339
340 (send_stream, recv_stream)
341}
342
343impl xwt_core::session::stream::OpenBi for Session {
344 type Opening = xwt_core::utils::dummy::OpeningBiStream<Session>;
345
346 type Error = Error;
347
348 async fn open_bi(&self) -> Result<Self::Opening, Self::Error> {
349 let transport = self.transport_ref();
350 let value =
351 wasm_bindgen_futures::JsFuture::from(transport.create_bidirectional_stream()).await?;
352 let value = wrap_bi_stream(transport, value);
353 Ok(xwt_core::utils::dummy::OpeningBiStream(value))
354 }
355}
356
357impl xwt_core::session::stream::AcceptBi for Session {
358 type Error = Error;
359
360 async fn accept_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
361 let transport = self.transport_ref();
362 let incoming: web_sys::ReadableStream = transport.incoming_bidirectional_streams();
363 let reader: JsValue = incoming.get_reader().into();
364 let reader: web_sys::ReadableStreamDefaultReader = reader.into();
365 let read_result = wasm_bindgen_futures::JsFuture::from(reader.read()).await?;
366 let read_result: web_wt_sys::ReadableStreamReadResult<
367 web_wt_sys::WebTransportBidirectionalStream,
368 > = read_result.unchecked_into();
369 if read_result.is_done() {
370 return Err(Error(JsError::new("xwt: accept bi reader is done").into()));
371 }
372 let Some(value) = read_result.get_value() else {
373 return Err(Error(
374 JsError::new("xwt: accept bi read result has no value").into(),
375 ));
376 };
377 let value = wrap_bi_stream(transport, value);
378 Ok(value)
379 }
380}
381
382impl xwt_core::session::stream::OpenUni for Session {
383 type Opening = xwt_core::utils::dummy::OpeningUniStream<Session>;
384 type Error = Error;
385
386 async fn open_uni(&self) -> Result<Self::Opening, Self::Error> {
387 let transport = self.transport_ref();
388 let value =
389 wasm_bindgen_futures::JsFuture::from(transport.create_unidirectional_stream()).await?;
390 let send_stream = wrap_send_stream(transport, value);
391 Ok(xwt_core::utils::dummy::OpeningUniStream(send_stream))
392 }
393}
394
395impl xwt_core::session::stream::AcceptUni for Session {
396 type Error = Error;
397
398 async fn accept_uni(&self) -> Result<Self::RecvStream, Self::Error> {
399 let transport = self.transport_ref();
400 let incoming: web_sys::ReadableStream = transport.incoming_unidirectional_streams();
401 let reader: JsValue = incoming.get_reader().into();
402 let reader: web_sys::ReadableStreamDefaultReader = reader.into();
403 let read_result = wasm_bindgen_futures::JsFuture::from(reader.read()).await?;
404 let read_result: web_wt_sys::ReadableStreamReadResult<
405 web_wt_sys::WebTransportReceiveStream,
406 > = read_result.unchecked_into();
407 if read_result.is_done() {
408 return Err(Error(JsError::new("xwt: accept uni reader is done").into()));
409 }
410 let Some(value) = read_result.get_value() else {
411 return Err(Error(
412 JsError::new("xwt: accept uni read result has no value").into(),
413 ));
414 };
415 let recv_stream = wrap_recv_stream(transport, value);
416 Ok(recv_stream)
417 }
418}
419
420impl tokio::io::AsyncWrite for SendStream {
421 fn poll_write(
422 mut self: std::pin::Pin<&mut Self>,
423 cx: &mut std::task::Context<'_>,
424 buf: &[u8],
425 ) -> std::task::Poll<Result<usize, std::io::Error>> {
426 std::pin::Pin::new(&mut self.writer).poll_write(cx, buf)
427 }
428
429 fn poll_flush(
430 mut self: std::pin::Pin<&mut Self>,
431 cx: &mut std::task::Context<'_>,
432 ) -> std::task::Poll<Result<(), std::io::Error>> {
433 std::pin::Pin::new(&mut self.writer).poll_flush(cx)
434 }
435
436 fn poll_shutdown(
437 mut self: std::pin::Pin<&mut Self>,
438 cx: &mut std::task::Context<'_>,
439 ) -> std::task::Poll<Result<(), std::io::Error>> {
440 std::pin::Pin::new(&mut self.writer).poll_shutdown(cx)
441 }
442}
443
444impl tokio::io::AsyncRead for RecvStream {
445 fn poll_read(
446 mut self: std::pin::Pin<&mut Self>,
447 cx: &mut std::task::Context<'_>,
448 buf: &mut tokio::io::ReadBuf<'_>,
449 ) -> std::task::Poll<std::io::Result<()>> {
450 std::pin::Pin::new(&mut self.reader).poll_read(cx, buf)
451 }
452}
453
454#[derive(Debug, thiserror::Error)]
456pub enum StreamWriteError {
457 #[error("zero size write buffer")]
459 ZeroSizeWriteBuffer,
460
461 #[error("write error: {0}")]
463 Write(Error),
464}
465
466impl xwt_core::stream::Write for SendStream {
467 type Error = StreamWriteError;
468
469 async fn write(&mut self, buf: &[u8]) -> Result<NonZeroUsize, Self::Error> {
470 let Some(buf_len) = NonZeroUsize::new(buf.len()) else {
471 return Err(StreamWriteError::ZeroSizeWriteBuffer);
472 };
473
474 web_sys_stream_utils::write(&self.writer.inner, buf)
475 .await
476 .map_err(|err| StreamWriteError::Write(err.into()))?;
477
478 Ok(buf_len)
479 }
480}
481
482impl xwt_core::stream::WriteAbort for SendStream {
483 type Error = Error;
484
485 async fn abort(self, error_code: xwt_core::stream::ErrorCode) -> Result<(), Self::Error> {
486 wasm_bindgen_futures::JsFuture::from(
487 self.writer.inner.abort_with_reason(&error_code.into()),
488 )
489 .await
490 .map(|val| {
491 debug_assert!(val.is_undefined());
492 })
493 .map_err(Error::from)
494 }
495}
496
497impl xwt_core::stream::WriteAborted for SendStream {
498 type Error = Error;
499
500 async fn aborted(self) -> Result<xwt_core::stream::ErrorCode, Self::Error> {
501 let result = wasm_bindgen_futures::JsFuture::from(self.writer.inner.closed()).await;
503 match result {
504 Ok(value) => {
505 debug_assert!(value.is_undefined());
506 Ok(0)
507 }
508 Err(value) => {
509 let error: web_wt_sys::WebTransportError = value.dyn_into().unwrap();
510 if error.source() != web_wt_sys::WebTransportErrorSource::Stream {
511 return Err(Error(error.into()));
512 }
513 let Some(code) = error.stream_error_code() else {
514 return Err(Error(error.into()));
515 };
516 Ok(code)
517 }
518 }
519 }
520}
521
522impl xwt_core::stream::Finish for SendStream {
523 type Error = Error;
524
525 async fn finish(self) -> Result<(), Self::Error> {
526 wasm_bindgen_futures::JsFuture::from(self.writer.inner.close())
527 .await
528 .map(|val| {
529 debug_assert!(val.is_undefined());
530 })
531 .map_err(Error::from)
532 }
533}
534
535impl xwt_core::stream::Finished for RecvStream {
536 type Error = Error;
537
538 async fn finished(self) -> Result<(), Self::Error> {
539 wasm_bindgen_futures::JsFuture::from(self.reader.inner.closed())
540 .await
541 .map(|val| {
542 debug_assert!(val.is_undefined());
543 })
544 .map_err(Error::from)
545 }
546}
547
548#[derive(Debug, thiserror::Error)]
550pub enum StreamReadError {
551 #[error("byob read consumed the buffer and didn't provide a new one")]
553 ByobReadConsumedBuffer,
554
555 #[error("read error: {0}")]
557 Read(Error),
558
559 #[error("stream closed")]
561 Closed,
562}
563
564impl xwt_core::stream::Read for RecvStream {
565 type Error = StreamReadError;
566
567 async fn read(&mut self, buf: &mut [u8]) -> Result<NonZeroUsize, Self::Error> {
568 let requested_size = buf.len().try_into().unwrap();
569 let internal_buf = self
570 .reader
571 .internal_buf
572 .take()
573 .filter(|internal_buf| {
574 let actual_size = internal_buf.byte_length();
575 debug_assert!(actual_size > 0);
576 actual_size >= requested_size
577 })
578 .unwrap_or_else(|| js_sys::ArrayBuffer::new(requested_size));
579 let internal_buf_view =
580 js_sys::Uint8Array::new_with_byte_offset_and_length(&internal_buf, 0, requested_size);
581 let maybe_internal_buf_view =
582 web_sys_stream_utils::read_byob(&self.reader.inner, internal_buf_view)
583 .await
584 .map_err(|err| StreamReadError::Read(err.into()))?;
585 let Some(internal_buf_view) = maybe_internal_buf_view else {
586 return Err(StreamReadError::ByobReadConsumedBuffer);
587 };
588
589 let len = internal_buf_view.byte_length().try_into().unwrap();
591
592 let Some(len) = NonZeroUsize::new(len) else {
595 return Err(StreamReadError::Closed);
596 };
597
598 internal_buf_view.copy_to(&mut buf[..len.get()]);
599
600 self.reader.internal_buf = Some(internal_buf_view.buffer());
601
602 Ok(len)
603 }
604}
605
606impl xwt_core::stream::ReadAbort for RecvStream {
607 type Error = Error;
608
609 async fn abort(self, error_code: xwt_core::stream::ErrorCode) -> Result<(), Self::Error> {
610 wasm_bindgen_futures::JsFuture::from(
611 self.reader.inner.cancel_with_reason(&error_code.into()),
612 )
613 .await
614 .map(|_| ())
615 .map_err(Error::from)
616 }
617}
618
619impl xwt_core::stream::ReadAborted for RecvStream {
620 type Error = Error;
621
622 async fn aborted(self) -> Result<xwt_core::stream::ErrorCode, Self::Error> {
623 let result = wasm_bindgen_futures::JsFuture::from(self.reader.inner.closed()).await;
625 match result {
626 Ok(value) => {
627 debug_assert!(value.is_undefined());
628 Ok(0)
629 }
630 Err(value) => {
631 let error: web_wt_sys::WebTransportError = value.dyn_into().unwrap();
632 if error.source() != web_wt_sys::WebTransportErrorSource::Stream {
633 return Err(Error(error.into()));
634 }
635 let Some(code) = error.stream_error_code() else {
636 return Err(Error(error.into()));
637 };
638 Ok(code)
639 }
640 }
641 }
642}
643
644impl Datagrams {
645 pub async fn receive_with<R>(
653 &self,
654 max_read_size: Option<u32>,
655 f: impl FnOnce(&mut js_sys::Uint8Array) -> R,
656 ) -> Result<R, Error> {
657 let mut buffer_guard = self.read_buffer.lock().await;
658
659 match &self.readable_stream_reader {
660 DatagramsReader::Byob(reader) => {
661 let buffer = buffer_guard
662 .take()
663 .unwrap_or_else(|| js_sys::ArrayBuffer::new(self.read_buffer_size));
664 let view = if let Some(max_read_size) = max_read_size {
665 let desired_buffer_length = buffer.byte_length().min(max_read_size);
666 js_sys::Uint8Array::new_with_byte_offset_and_length(
667 &buffer,
668 0,
669 desired_buffer_length,
670 )
671 } else {
672 js_sys::Uint8Array::new(&buffer)
673 };
674
675 let maybe_view = web_sys_stream_utils::read_byob(reader, view).await?;
676 let Some(mut view) = maybe_view else {
677 return Err(wasm_bindgen::JsError::new("unexpected stream termination").into());
678 };
679
680 let result = f(&mut view);
681
682 *buffer_guard = Some(view.buffer());
683 Ok(result)
684 }
685 DatagramsReader::Default(reader) => {
686 let maybe_view = web_sys_stream_utils::read_uint8array(reader).await?;
687 let Some(view) = maybe_view else {
688 return Err(wasm_bindgen::JsError::new("unexpected stream termination").into());
689 };
690
691 let mut view = match max_read_size {
694 Some(max_read_size) if view.length() > max_read_size => {
695 view.subarray(0, max_read_size)
696 }
697 _ => view,
698 };
699
700 let result = f(&mut view);
701
702 Ok(result)
703 }
704 }
705 }
706}
707
708impl xwt_core::session::datagram::MaxSize for Session {
709 fn max_datagram_size(&self) -> Option<usize> {
710 let transport = self.transport_ref();
711 let max_datagram_size = transport.datagrams().max_datagram_size();
712 Some(usize::try_from(max_datagram_size).unwrap()) }
714}
715
716impl xwt_core::session::datagram::Receive for Session {
717 type Datagram = Vec<u8>;
718 type Error = Error;
719
720 async fn receive_datagram(&self) -> Result<Self::Datagram, Self::Error> {
721 self.datagrams
722 .receive_with(None, |buffer| buffer.to_vec())
723 .await
724 }
725}
726
727impl xwt_core::session::datagram::ReceiveInto for Session {
728 type Error = Error;
729
730 async fn receive_datagram_into(&self, buf: &mut [u8]) -> Result<usize, Self::Error> {
731 let max_read_size = buf.len().try_into().unwrap();
732 self.datagrams
733 .receive_with(Some(max_read_size), |buffer| {
734 let len = buffer.length() as usize;
735 buffer.copy_to(&mut buf[..len]);
736 len
737 })
738 .await
739 }
740}
741
742impl xwt_core::session::datagram::Send for Session {
743 type Error = Error;
744
745 async fn send_datagram<D>(&self, payload: D) -> Result<(), Self::Error>
746 where
747 D: AsRef<[u8]>,
748 {
749 web_sys_stream_utils::write(&self.datagrams.writable_stream_writer, payload.as_ref())
750 .await?;
751 Ok(())
752 }
753}