Skip to main content

xwt_web/
lib.rs

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/// An endpoint for the xwt.
26///
27/// Internally holds the connection options and can create
28/// a new `WebTransport` object on the "web" side on a connection request.
29#[derive(Debug, Clone, Default)]
30pub struct Endpoint {
31    /// The options to use to create the `WebTransport`s.
32    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/// Connecting represents the transient connection state when
46/// the [`web_wt_sys::WebTransport`] has been created but is not ready yet.
47#[derive(Debug)]
48pub struct Connecting {
49    /// The WebTransport instance.
50    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/// Session holds the [`web_wt_sys::WebTransport`] and is responsible for
66/// providing access to the Web API of WebTransport in a way that is portable.
67/// It also holds handles to the datagram reader and writer, as well as
68/// the datagram reader state.
69#[derive(Debug)]
70pub struct Session {
71    /// The WebTransport instance.
72    transport: Option<Rc<web_wt_sys::WebTransport>>,
73
74    /// The datagrams state for this session.
75    pub datagrams: Datagrams,
76
77    /// Whether to close the session on drop.
78    pub close_on_drop: bool,
79}
80
81impl Session {
82    /// Construct a new session from a [`web_wt_sys::WebTransport`].
83    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    /// If possible, relieves the underlying [`web_wt_sys::WebTransport`] of
93    /// any `xwt-web`-held locks and dependencies and exposes it.
94    pub fn try_unwrap(mut self) -> Result<web_wt_sys::WebTransport, Self> {
95        // Take the transport out of the session; this state is not valid
96        // "publicly", only while we're in this fn.
97        let transport = self.transport.take().unwrap();
98
99        // We want to ensure there are no other references to
100        // the transport's [`Rc`], otherwise something must still be using it.
101        // If we can't unwrap it successfully - we reinsert the transport back
102        // into `self` and return it as an `Err` - it will thus remain
103        // operational to permit doing whatever work may have to be done
104        // further.
105        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        // Do not close the transport (we have taken it out anyway).
114        self.close_on_drop = false;
115
116        // Drop the session to release the datagram readers/writers.
117        drop(self);
118
119        // Return the unwrapped transport.
120        Ok(unwrapped)
121    }
122
123    /// Obtain a transport ref.
124    pub const fn transport_ref(&self) -> &Rc<web_wt_sys::WebTransport> {
125        // Trnasport should never be gone generally, only inside of
126        // the `try_unwrap`.
127        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/// The reader for the datagrams readable stream.
140///
141/// Per the WebTransport spec, the datagrams readable is a regular (non-byte)
142/// [`web_sys::ReadableStream`], which only supports default readers
143/// (this is what Firefox implements); Chrome, however, exposes it as
144/// a byte stream, allowing BYOB reads.
145/// We feature-detect BYOB support and fall back to a default reader.
146#[derive(Debug)]
147pub enum DatagramsReader {
148    /// A BYOB reader, used when the datagrams readable is a byte stream.
149    Byob(web_sys::ReadableStreamByobReader),
150
151    /// A default reader, used when the datagrams readable is not
152    /// a byte stream.
153    Default(web_sys::ReadableStreamDefaultReader),
154}
155
156impl DatagramsReader {
157    /// Acquire a reader for the given datagrams readable stream, preferring
158    /// a BYOB reader when the stream supports it.
159    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    /// Release the stream lock held by the reader.
167    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/// Datagrams hold the portions of the session that are responsible for working
176/// with the datagrams.
177#[derive(Debug)]
178pub struct Datagrams {
179    /// The datagram reader.
180    pub readable_stream_reader: DatagramsReader,
181
182    /// The datagram writer.
183    pub writable_stream_writer: web_sys::WritableStreamDefaultWriter,
184
185    /// The desired size of the datagram read buffer.
186    /// Used to allocate the datagram read buffer in case it gets lost.
187    pub read_buffer_size: u32,
188
189    /// The datagram read internal buffer.
190    pub read_buffer: tokio::sync::Mutex<Option<js_sys::ArrayBuffer>>,
191
192    /// Unlock the streams on drop.
193    pub unlock_streams_on_drop: bool,
194}
195
196impl Datagrams {
197    /// Create a datagrams state from the transport.
198    pub fn from_transport(transport: &web_wt_sys::WebTransport) -> Self {
199        Self::from_transport_datagrams(&transport.datagrams())
200    }
201
202    /// Create a datagrams state from the transport datagrams.
203    pub fn from_transport_datagrams(
204        datagrams: &web_wt_sys::WebTransportDatagramDuplexStream,
205    ) -> Self {
206        let read_buffer_size = 65536; // 65k buffers as per spec recommendation
207
208        let readable_stream_reader = DatagramsReader::for_stream(datagrams.readable());
209        // Feature-detect `createWritable`; fall back to the legacy `writable`
210        // attribute on browsers that do not implement it yet.
211        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
250/// Send the data into a WebTransport stream.
251pub struct SendStream {
252    /// The WebTransport instance.
253    pub transport: Rc<web_wt_sys::WebTransport>,
254
255    /// The handle to the stream to write to.
256    pub stream: web_wt_sys::WebTransportSendStream,
257
258    /// A writer to conduct the operation.
259    pub writer: web_sys_async_io::Writer,
260
261    /// Unlock the writer on drop.
262    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
273/// Recv the data from a WebTransport stream.
274pub struct RecvStream {
275    /// The WebTransport instance.
276    pub transport: Rc<web_wt_sys::WebTransport>,
277
278    /// The handle to the stream to read from.
279    pub stream: web_wt_sys::WebTransportReceiveStream,
280
281    /// A reader to conduct the operation.
282    pub reader: web_sys_async_io::Reader,
283
284    /// Unlock the reader on drop.
285    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
296/// Open a reader for the given stream and create a [`RecvStream`].
297fn wrap_recv_stream(
298    transport: &Rc<web_wt_sys::WebTransport>,
299    stream: web_wt_sys::WebTransportReceiveStream,
300) -> RecvStream {
301    // Per the WebTransport spec, the receive stream is a byte stream
302    // supporting BYOB reads (this is what Chrome and Firefox implement);
303    // Safari, however, does not implement it as a byte stream.
304    // We feature-detect BYOB support and fall back to a default reader.
305    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
324/// Open a writer for the given stream and create a [`SendStream`].
325fn 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
339/// Take a bidi stream and wrap a reader and writer for it.
340fn 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/// An error that can occur during the stream writes.
465#[derive(Debug, thiserror::Error)]
466pub enum StreamWriteError {
467    /// The write was called with a zero-size write buffer.
468    #[error("zero size write buffer")]
469    ZeroSizeWriteBuffer,
470
471    /// The write call thrown an exception.
472    #[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        // Hack our way through...
512        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/// An error that can occur while reading stream data.
559#[derive(Debug, thiserror::Error)]
560pub enum StreamReadError {
561    /// This is an odd case, which is still tbd what to do with.
562    #[error("byob read consumed the buffer and didn't provide a new one")]
563    ByobReadConsumedBuffer,
564
565    /// The `read_byob` call thrown an exception.
566    #[error("read error: {0}")]
567    Read(Error),
568
569    /// The stream was closed, and there is no more data to expect there.
570    #[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        // Detect when the read is aborted because the stream was closed
590        // without an error.
591        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        // Hack our way through...
613        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    /// Receive the datagram and handle the buffer with the given function.
635    ///
636    /// Cloning the buffer in the `f` will result in the undefined behaviour,
637    /// because it will create a second reference to an object that is intended
638    /// to be under a `mut ref`.
639    /// Although is would not teachnically be unsafe, it would violate
640    /// the borrow checker rules.
641    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                // A default reader always yields the whole datagram; honor
681                // the requested read size limit by truncating the view.
682                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()) // u32 should fit in a usize on WASM
702    }
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}