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 = match web_sys_stream_utils::try_get_reader_byob(stream.clone()) {
306 Ok(reader) => web_sys_async_io::reader::Mode::Byob {
307 reader,
308 internal_buf: None,
309 },
310 Err(_) => web_sys_async_io::reader::Mode::Default {
311 reader: web_sys_stream_utils::get_reader(stream.clone()),
312 },
313 };
314 let reader = web_sys_async_io::Reader::new(reader);
315
316 RecvStream {
317 transport: Rc::clone(transport),
318 stream,
319 reader,
320 unlock_reader_on_drop: true,
321 }
322}
323
324fn wrap_send_stream(
326 transport: &Rc<web_wt_sys::WebTransport>,
327 stream: web_wt_sys::WebTransportSendStream,
328) -> SendStream {
329 let writer = stream.get_writer().unwrap();
330 let writer = web_sys_async_io::Writer::new(writer.into());
331 SendStream {
332 transport: Rc::clone(transport),
333 stream,
334 writer,
335 unlock_writer_on_drop: true,
336 }
337}
338
339fn wrap_bi_stream(
341 transport: &Rc<web_wt_sys::WebTransport>,
342 stream: web_wt_sys::WebTransportBidirectionalStream,
343) -> (SendStream, RecvStream) {
344 let writable = stream.writable();
345 let readable = stream.readable();
346
347 let send_stream = wrap_send_stream(transport, writable);
348 let recv_stream = wrap_recv_stream(transport, readable);
349
350 (send_stream, recv_stream)
351}
352
353impl xwt_core::session::stream::OpenBi for Session {
354 type Opening = xwt_core::utils::dummy::OpeningBiStream<Session>;
355
356 type Error = Error;
357
358 async fn open_bi(&self) -> Result<Self::Opening, Self::Error> {
359 let transport = self.transport_ref();
360 let value =
361 wasm_bindgen_futures::JsFuture::from(transport.create_bidirectional_stream()).await?;
362 let value = wrap_bi_stream(transport, value);
363 Ok(xwt_core::utils::dummy::OpeningBiStream(value))
364 }
365}
366
367impl xwt_core::session::stream::AcceptBi for Session {
368 type Error = Error;
369
370 async fn accept_bi(&self) -> Result<(Self::SendStream, Self::RecvStream), Self::Error> {
371 let transport = self.transport_ref();
372 let incoming: web_sys::ReadableStream = transport.incoming_bidirectional_streams();
373 let reader: JsValue = incoming.get_reader().into();
374 let reader: web_sys::ReadableStreamDefaultReader = reader.into();
375 let read_result = wasm_bindgen_futures::JsFuture::from(reader.read()).await?;
376 let read_result: web_wt_sys::ReadableStreamReadResult<
377 web_wt_sys::WebTransportBidirectionalStream,
378 > = read_result.unchecked_into();
379 if read_result.is_done() {
380 return Err(Error(JsError::new("xwt: accept bi reader is done").into()));
381 }
382 let Some(value) = read_result.get_value() else {
383 return Err(Error(
384 JsError::new("xwt: accept bi read result has no value").into(),
385 ));
386 };
387 let value = wrap_bi_stream(transport, value);
388 Ok(value)
389 }
390}
391
392impl xwt_core::session::stream::OpenUni for Session {
393 type Opening = xwt_core::utils::dummy::OpeningUniStream<Session>;
394 type Error = Error;
395
396 async fn open_uni(&self) -> Result<Self::Opening, Self::Error> {
397 let transport = self.transport_ref();
398 let value =
399 wasm_bindgen_futures::JsFuture::from(transport.create_unidirectional_stream()).await?;
400 let send_stream = wrap_send_stream(transport, value);
401 Ok(xwt_core::utils::dummy::OpeningUniStream(send_stream))
402 }
403}
404
405impl xwt_core::session::stream::AcceptUni for Session {
406 type Error = Error;
407
408 async fn accept_uni(&self) -> Result<Self::RecvStream, Self::Error> {
409 let transport = self.transport_ref();
410 let incoming: web_sys::ReadableStream = transport.incoming_unidirectional_streams();
411 let reader: JsValue = incoming.get_reader().into();
412 let reader: web_sys::ReadableStreamDefaultReader = reader.into();
413 let read_result = wasm_bindgen_futures::JsFuture::from(reader.read()).await?;
414 let read_result: web_wt_sys::ReadableStreamReadResult<
415 web_wt_sys::WebTransportReceiveStream,
416 > = read_result.unchecked_into();
417 if read_result.is_done() {
418 return Err(Error(JsError::new("xwt: accept uni reader is done").into()));
419 }
420 let Some(value) = read_result.get_value() else {
421 return Err(Error(
422 JsError::new("xwt: accept uni read result has no value").into(),
423 ));
424 };
425 let recv_stream = wrap_recv_stream(transport, value);
426 Ok(recv_stream)
427 }
428}
429
430impl tokio::io::AsyncWrite for SendStream {
431 fn poll_write(
432 mut self: std::pin::Pin<&mut Self>,
433 cx: &mut std::task::Context<'_>,
434 buf: &[u8],
435 ) -> std::task::Poll<Result<usize, std::io::Error>> {
436 std::pin::Pin::new(&mut self.writer).poll_write(cx, buf)
437 }
438
439 fn poll_flush(
440 mut self: std::pin::Pin<&mut Self>,
441 cx: &mut std::task::Context<'_>,
442 ) -> std::task::Poll<Result<(), std::io::Error>> {
443 std::pin::Pin::new(&mut self.writer).poll_flush(cx)
444 }
445
446 fn poll_shutdown(
447 mut self: std::pin::Pin<&mut Self>,
448 cx: &mut std::task::Context<'_>,
449 ) -> std::task::Poll<Result<(), std::io::Error>> {
450 std::pin::Pin::new(&mut self.writer).poll_shutdown(cx)
451 }
452}
453
454impl tokio::io::AsyncRead for RecvStream {
455 fn poll_read(
456 mut self: std::pin::Pin<&mut Self>,
457 cx: &mut std::task::Context<'_>,
458 buf: &mut tokio::io::ReadBuf<'_>,
459 ) -> std::task::Poll<std::io::Result<()>> {
460 std::pin::Pin::new(&mut self.reader).poll_read(cx, buf)
461 }
462}
463
464#[derive(Debug, thiserror::Error)]
466pub enum StreamWriteError {
467 #[error("zero size write buffer")]
469 ZeroSizeWriteBuffer,
470
471 #[error("write error: {0}")]
473 Write(Error),
474}
475
476impl xwt_core::stream::Write for SendStream {
477 type Error = StreamWriteError;
478
479 async fn write(&mut self, buf: &[u8]) -> Result<NonZeroUsize, Self::Error> {
480 let Some(buf_len) = NonZeroUsize::new(buf.len()) else {
481 return Err(StreamWriteError::ZeroSizeWriteBuffer);
482 };
483
484 web_sys_stream_utils::write(&self.writer.inner, buf)
485 .await
486 .map_err(|err| StreamWriteError::Write(err.into()))?;
487
488 Ok(buf_len)
489 }
490}
491
492impl xwt_core::stream::WriteAbort for SendStream {
493 type Error = Error;
494
495 async fn abort(self, error_code: xwt_core::stream::ErrorCode) -> Result<(), Self::Error> {
496 wasm_bindgen_futures::JsFuture::from(
497 self.writer.inner.abort_with_reason(&error_code.into()),
498 )
499 .await
500 .map(|val| {
501 debug_assert!(val.is_undefined());
502 })
503 .map_err(Error::from)
504 }
505}
506
507impl xwt_core::stream::WriteAborted for SendStream {
508 type Error = Error;
509
510 async fn aborted(self) -> Result<xwt_core::stream::ErrorCode, Self::Error> {
511 let result = wasm_bindgen_futures::JsFuture::from(self.writer.inner.closed()).await;
513 match result {
514 Ok(value) => {
515 debug_assert!(value.is_undefined());
516 Ok(0)
517 }
518 Err(value) => {
519 let error: web_wt_sys::WebTransportError = value.dyn_into().unwrap();
520 if error.source() != web_wt_sys::WebTransportErrorSource::Stream {
521 return Err(Error(error.into()));
522 }
523 let Some(code) = error.stream_error_code() else {
524 return Err(Error(error.into()));
525 };
526 Ok(code)
527 }
528 }
529 }
530}
531
532impl xwt_core::stream::Finish for SendStream {
533 type Error = Error;
534
535 async fn finish(self) -> Result<(), Self::Error> {
536 wasm_bindgen_futures::JsFuture::from(self.writer.inner.close())
537 .await
538 .map(|val| {
539 debug_assert!(val.is_undefined());
540 })
541 .map_err(Error::from)
542 }
543}
544
545impl xwt_core::stream::Finished for RecvStream {
546 type Error = Error;
547
548 async fn finished(self) -> Result<(), Self::Error> {
549 wasm_bindgen_futures::JsFuture::from(self.reader.inner.closed())
550 .await
551 .map(|val| {
552 debug_assert!(val.is_undefined());
553 })
554 .map_err(Error::from)
555 }
556}
557
558#[derive(Debug, thiserror::Error)]
560pub enum StreamReadError {
561 #[error("byob read consumed the buffer and didn't provide a new one")]
563 ByobReadConsumedBuffer,
564
565 #[error("read error: {0}")]
567 Read(Error),
568
569 #[error("stream closed")]
571 Closed,
572}
573
574impl From<web_sys_async_io::ReadError> for StreamReadError {
575 fn from(err: web_sys_async_io::ReadError) -> Self {
576 match err {
577 web_sys_async_io::ReadError::Read(err) => Self::Read(err.into()),
578 web_sys_async_io::ReadError::ByobReadConsumedBuffer => Self::ByobReadConsumedBuffer,
579 }
580 }
581}
582
583impl xwt_core::stream::Read for RecvStream {
584 type Error = StreamReadError;
585
586 async fn read(&mut self, buf: &mut [u8]) -> Result<NonZeroUsize, Self::Error> {
587 let len = self.reader.read_into(buf).await?;
588
589 NonZeroUsize::new(len).ok_or(StreamReadError::Closed)
592 }
593}
594
595impl xwt_core::stream::ReadAbort for RecvStream {
596 type Error = Error;
597
598 async fn abort(self, error_code: xwt_core::stream::ErrorCode) -> Result<(), Self::Error> {
599 wasm_bindgen_futures::JsFuture::from(
600 self.reader.inner.cancel_with_reason(&error_code.into()),
601 )
602 .await
603 .map(|_| ())
604 .map_err(Error::from)
605 }
606}
607
608impl xwt_core::stream::ReadAborted for RecvStream {
609 type Error = Error;
610
611 async fn aborted(self) -> Result<xwt_core::stream::ErrorCode, Self::Error> {
612 let result = wasm_bindgen_futures::JsFuture::from(self.reader.inner.closed()).await;
614 match result {
615 Ok(value) => {
616 debug_assert!(value.is_undefined());
617 Ok(0)
618 }
619 Err(value) => {
620 let error: web_wt_sys::WebTransportError = value.dyn_into().unwrap();
621 if error.source() != web_wt_sys::WebTransportErrorSource::Stream {
622 return Err(Error(error.into()));
623 }
624 let Some(code) = error.stream_error_code() else {
625 return Err(Error(error.into()));
626 };
627 Ok(code)
628 }
629 }
630 }
631}
632
633impl Datagrams {
634 pub async fn receive_with<R>(
642 &self,
643 max_read_size: Option<u32>,
644 f: impl FnOnce(&mut js_sys::Uint8Array) -> R,
645 ) -> Result<R, Error> {
646 let mut buffer_guard = self.read_buffer.lock().await;
647
648 match &self.readable_stream_reader {
649 DatagramsReader::Byob(reader) => {
650 let buffer = buffer_guard
651 .take()
652 .unwrap_or_else(|| js_sys::ArrayBuffer::new(self.read_buffer_size));
653 let view = if let Some(max_read_size) = max_read_size {
654 let desired_buffer_length = buffer.byte_length().min(max_read_size);
655 js_sys::Uint8Array::new_with_byte_offset_and_length(
656 &buffer,
657 0,
658 desired_buffer_length,
659 )
660 } else {
661 js_sys::Uint8Array::new(&buffer)
662 };
663
664 let maybe_view = web_sys_stream_utils::read_byob(reader, view).await?;
665 let Some(mut view) = maybe_view else {
666 return Err(wasm_bindgen::JsError::new("unexpected stream termination").into());
667 };
668
669 let result = f(&mut view);
670
671 *buffer_guard = Some(view.buffer());
672 Ok(result)
673 }
674 DatagramsReader::Default(reader) => {
675 let maybe_view = web_sys_stream_utils::read_uint8array(reader).await?;
676 let Some(view) = maybe_view else {
677 return Err(wasm_bindgen::JsError::new("unexpected stream termination").into());
678 };
679
680 let mut view = match max_read_size {
683 Some(max_read_size) if view.length() > max_read_size => {
684 view.subarray(0, max_read_size)
685 }
686 _ => view,
687 };
688
689 let result = f(&mut view);
690
691 Ok(result)
692 }
693 }
694 }
695}
696
697impl xwt_core::session::datagram::MaxSize for Session {
698 fn max_datagram_size(&self) -> Option<usize> {
699 let transport = self.transport_ref();
700 let max_datagram_size = transport.datagrams().max_datagram_size();
701 Some(usize::try_from(max_datagram_size).unwrap()) }
703}
704
705impl xwt_core::session::datagram::Receive for Session {
706 type Datagram = Vec<u8>;
707 type Error = Error;
708
709 async fn receive_datagram(&self) -> Result<Self::Datagram, Self::Error> {
710 self.datagrams
711 .receive_with(None, |buffer| buffer.to_vec())
712 .await
713 }
714}
715
716impl xwt_core::session::datagram::ReceiveInto for Session {
717 type Error = Error;
718
719 async fn receive_datagram_into(&self, buf: &mut [u8]) -> Result<usize, Self::Error> {
720 let max_read_size = buf.len().try_into().unwrap();
721 self.datagrams
722 .receive_with(Some(max_read_size), |buffer| {
723 let len = buffer.length() as usize;
724 buffer.copy_to(&mut buf[..len]);
725 len
726 })
727 .await
728 }
729}
730
731impl xwt_core::session::datagram::Send for Session {
732 type Error = Error;
733
734 async fn send_datagram<D>(&self, payload: D) -> Result<(), Self::Error>
735 where
736 D: AsRef<[u8]>,
737 {
738 web_sys_stream_utils::write(&self.datagrams.writable_stream_writer, payload.as_ref())
739 .await?;
740 Ok(())
741 }
742}