Skip to main content

engineioxide_core/payload/
mod.rs

1//! Payload encoder and decoder for polling transport.
2
3use crate::{Packet, PacketParseError, ProtocolVersion, packet::PacketBuf};
4
5use bytes::Bytes;
6use futures_util::Stream;
7use http::HeaderValue;
8
9mod buf;
10mod decoder;
11mod encoder;
12
13const PACKET_SEPARATOR_V4: u8 = b'\x1e';
14#[cfg(feature = "v3")]
15const STRING_PACKET_SEPARATOR_V3: u8 = b':';
16#[cfg(feature = "v3")]
17const BINARY_PACKET_SEPARATOR_V3: u8 = 0xff;
18#[cfg(feature = "v3")]
19const STRING_PACKET_IDENTIFIER_V3: u8 = 0x00;
20#[cfg(feature = "v3")]
21const BINARY_PACKET_IDENTIFIER_V3: u8 = 0x01;
22#[cfg(feature = "v3")]
23const BINARY_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/octet-stream");
24
25/// Decode a payload into a stream of packets.
26pub fn decoder(
27    body: impl http_body::Body<Error = impl std::fmt::Debug> + Unpin,
28    #[allow(unused)] content_type: Option<&HeaderValue>,
29    #[allow(unused)] protocol: ProtocolVersion,
30    max_payload: u64,
31) -> impl Stream<Item = Result<Packet, PacketParseError>> {
32    #[cfg(feature = "tracing")]
33    tracing::debug!(?content_type, %protocol, "decoding payload");
34
35    #[cfg(feature = "v3")]
36    {
37        use futures_util::future::Either;
38
39        let is_binary = content_type == Some(&BINARY_CONTENT_TYPE);
40        match protocol {
41            ProtocolVersion::V4 => Either::Left(decoder::v4_decoder(body, max_payload)),
42            ProtocolVersion::V3 if is_binary => {
43                Either::Right(Either::Left(decoder::v3_binary_decoder(body, max_payload)))
44            }
45            ProtocolVersion::V3 => {
46                Either::Right(Either::Right(decoder::v3_string_decoder(body, max_payload)))
47            }
48        }
49    }
50
51    #[cfg(not(feature = "v3"))]
52    {
53        decoder::v4_decoder(body, max_payload)
54    }
55}
56
57/// A payload to transmit to the client through http polling
58pub struct Payload {
59    /// The data of the payload.
60    pub data: Bytes,
61    /// Whether the payload contains binary data.
62    pub has_binary: bool,
63    /// A peeked packet buffer that could not be sent due to the max payload size.
64    pub peeked: Option<PacketBuf>,
65}
66impl Payload {
67    /// Creates a new payload with the given data and binary flag.
68    pub fn new(data: Bytes, has_binary: bool) -> Self {
69        Self {
70            data,
71            has_binary,
72            peeked: None,
73        }
74    }
75}
76
77/// Encodes a payload into a byte stream.
78pub async fn encoder(
79    rx: impl Stream<Item = PacketBuf>,
80    #[allow(unused)] protocol: ProtocolVersion,
81    #[allow(unused)] supports_binary: bool,
82    max_payload: u64,
83) -> Payload {
84    let mut rx = std::pin::pin!(peekable::Peekable::new(rx));
85
86    #[cfg(feature = "v3")]
87    let mut payload = match protocol {
88        ProtocolVersion::V4 => encoder::v4_encoder(rx.as_mut(), max_payload).await,
89        ProtocolVersion::V3 if supports_binary => {
90            encoder::v3_binary_encoder(rx.as_mut(), max_payload).await
91        }
92        ProtocolVersion::V3 => encoder::v3_string_encoder(rx.as_mut(), max_payload).await,
93    };
94
95    #[cfg(not(feature = "v3"))]
96    let mut payload = encoder::v4_encoder(rx.as_mut(), max_payload).await;
97
98    // Recover any packet that was peeked but rejected (max payload exceeded) so
99    // the caller can hand it back to the next encoding pass instead of losing it.
100    payload.peeked = rx.take_peeked();
101
102    payload
103}
104
105/// Encodes a single packet into a byte array.
106pub fn packet_encoder(
107    packet: Packet,
108    #[allow(unused)] protocol: ProtocolVersion,
109    #[allow(unused)] supports_binary: bool,
110) -> Bytes {
111    #[cfg(feature = "v3")]
112    match protocol {
113        ProtocolVersion::V4 => packet.into(),
114        ProtocolVersion::V3 if supports_binary && packet.is_binary() => {
115            use bytes::BytesMut;
116
117            let size_hint = packet.get_size_hint(!supports_binary) + 2;
118            let mut bytes = BytesMut::with_capacity(size_hint);
119            encoder::v3_bin_packet_encoder(packet, &mut bytes);
120            bytes.freeze()
121        }
122        ProtocolVersion::V3 => {
123            use bytes::BytesMut;
124
125            let size_hint = packet.get_size_hint(!supports_binary) + 2;
126            let mut bytes = BytesMut::with_capacity(size_hint);
127            encoder::v3_string_packet_encoder(packet, &mut bytes);
128            bytes.freeze()
129        }
130    }
131
132    #[cfg(not(feature = "v3"))]
133    packet.into()
134}
135
136mod peekable {
137    //! [`Peekable`] implementation taken from [`futures_util::stream::Peekable`] but
138    //! with an [`Peekable::take_peeked`] method to get back a potentially peeked item.
139    use std::{
140        pin::Pin,
141        task::{Context, Poll},
142    };
143
144    use futures_util::{Stream, StreamExt as _, stream::Fuse};
145    use pin_project_lite::pin_project;
146
147    pin_project! {
148        /// A `Stream` that implements a `peek` method.
149        ///
150        /// The `peek` method can be used to retrieve a reference
151        /// to the next `Stream::Item` if available. A subsequent
152        /// call to `poll` will return the owned item.
153        #[derive(Debug)]
154        #[must_use = "streams do nothing unless polled"]
155        pub struct Peekable<St: Stream> {
156            #[pin]
157            stream: Fuse<St>,
158            peeked: Option<St::Item>,
159        }
160    }
161
162    impl<St: Stream> Peekable<St> {
163        pub(super) fn new(stream: St) -> Self {
164            Self {
165                stream: stream.fuse(),
166                peeked: None,
167            }
168        }
169
170        /// Produces a future which retrieves a reference to the next item
171        /// in the stream, or `None` if the underlying stream terminates.
172        pub fn peek(self: Pin<&mut Self>) -> Peek<'_, St> {
173            Peek { inner: Some(self) }
174        }
175
176        /// Takes the peeked item out of the buffer, if any.
177        ///
178        /// Only the `peeked` field is moved out, so this is safe to call
179        /// through a pinned reference even when the underlying stream is `!Unpin`.
180        pub fn take_peeked(self: Pin<&mut Self>) -> Option<St::Item> {
181            self.project().peeked.take()
182        }
183
184        /// Peek retrieves a reference to the next item in the stream.
185        ///
186        /// This method polls the underlying stream and return either a reference
187        /// to the next item if the stream is ready or passes through any errors.
188        pub fn poll_peek(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<&St::Item>> {
189            let mut this = self.project();
190
191            Poll::Ready(loop {
192                if this.peeked.is_some() {
193                    break this.peeked.as_ref();
194                } else if let Some(item) = futures_util::ready!(this.stream.as_mut().poll_next(cx))
195                {
196                    *this.peeked = Some(item);
197                } else {
198                    break None;
199                }
200            })
201        }
202    }
203
204    impl<S: Stream> Stream for Peekable<S> {
205        type Item = S::Item;
206
207        fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
208            let this = self.project();
209            if let Some(item) = this.peeked.take() {
210                return Poll::Ready(Some(item));
211            }
212            this.stream.poll_next(cx)
213        }
214
215        fn size_hint(&self) -> (usize, Option<usize>) {
216            let peek_len = usize::from(self.peeked.is_some());
217            let (lower, upper) = self.stream.size_hint();
218            let lower = lower.saturating_add(peek_len);
219            let upper = match upper {
220                Some(x) => x.checked_add(peek_len),
221                None => None,
222            };
223            (lower, upper)
224        }
225    }
226
227    pin_project! {
228        /// Future for the [`Peekable::peek`](self::Peekable::peek) method.
229        #[must_use = "futures do nothing unless polled"]
230        pub struct Peek<'a, St: Stream> {
231            inner: Option<Pin<&'a mut Peekable<St>>>,
232        }
233    }
234
235    impl<'a, St> Future for Peek<'a, St>
236    where
237        St: Stream,
238    {
239        type Output = Option<&'a St::Item>;
240
241        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
242            let inner = self.project().inner;
243            if let Some(peekable) = inner {
244                futures_util::ready!(peekable.as_mut().poll_peek(cx));
245
246                inner.take().unwrap().poll_peek(cx)
247            } else {
248                panic!("Peek polled after completion")
249            }
250        }
251    }
252}