Skip to main content

tonic/codec/
encode.rs

1/*
2 *
3 * Copyright 2025 gRPC authors.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 *
23 */
24
25use super::compression::{
26    CompressionEncoding, CompressionSettings, SingleMessageCompressionOverride, compress,
27};
28use super::{BufferSettings, DEFAULT_MAX_SEND_MESSAGE_SIZE, EncodeBuf, Encoder, HEADER_SIZE};
29use crate::Status;
30use bytes::{BufMut, Bytes, BytesMut};
31#[cfg(feature = "h2")]
32use h2::{Error as H2Error, Reason as H2Reason};
33use http::HeaderMap;
34use http_body::{Body, Frame};
35use pin_project::pin_project;
36use std::{
37    pin::Pin,
38    task::{Context, Poll, ready},
39};
40use tokio_stream::{Stream, StreamExt, adapters::Fuse};
41use tokio_util::sync::{CancellationToken, WaitForCancellationFutureOwned};
42
43/// Combinator for efficient encoding of messages into reasonably sized buffers.
44/// EncodedBytes encodes ready messages from its delegate stream into a BytesMut,
45/// splitting off and yielding a buffer when either:
46///  * The delegate stream polls as not ready, or
47///  * The encoded buffer surpasses YIELD_THRESHOLD.
48#[pin_project(project = EncodedBytesProj)]
49#[derive(Debug)]
50struct EncodedBytes<T, U> {
51    #[pin]
52    source: Fuse<U>,
53    encoder: T,
54    compression_encoding: Option<CompressionEncoding>,
55    max_message_size: Option<usize>,
56    buf: BytesMut,
57    uncompression_buf: BytesMut,
58    error: Option<Status>,
59}
60
61impl<T: Encoder, U: Stream> EncodedBytes<T, U> {
62    fn new(
63        encoder: T,
64        source: U,
65        compression_encoding: Option<CompressionEncoding>,
66        compression_override: SingleMessageCompressionOverride,
67        max_message_size: Option<usize>,
68    ) -> Self {
69        let buffer_settings = encoder.buffer_settings();
70        let buf = BytesMut::with_capacity(buffer_settings.buffer_size);
71
72        let compression_encoding =
73            if compression_override == SingleMessageCompressionOverride::Disable {
74                None
75            } else {
76                compression_encoding
77            };
78
79        let uncompression_buf = if compression_encoding.is_some() {
80            BytesMut::with_capacity(buffer_settings.buffer_size)
81        } else {
82            BytesMut::new()
83        };
84
85        Self {
86            source: source.fuse(),
87            encoder,
88            compression_encoding,
89            max_message_size,
90            buf,
91            uncompression_buf,
92            error: None,
93        }
94    }
95}
96
97impl<T, U> Stream for EncodedBytes<T, U>
98where
99    T: Encoder<Error = Status>,
100    U: Stream<Item = Result<T::Item, Status>>,
101{
102    type Item = Result<Bytes, Status>;
103
104    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
105        let EncodedBytesProj {
106            mut source,
107            encoder,
108            compression_encoding,
109            max_message_size,
110            buf,
111            uncompression_buf,
112            error,
113        } = self.project();
114        let buffer_settings = encoder.buffer_settings();
115
116        if let Some(status) = error.take() {
117            return Poll::Ready(Some(Err(status)));
118        }
119
120        loop {
121            match source.as_mut().poll_next(cx) {
122                Poll::Pending if buf.is_empty() => {
123                    return Poll::Pending;
124                }
125                Poll::Ready(None) if buf.is_empty() => {
126                    return Poll::Ready(None);
127                }
128                Poll::Pending | Poll::Ready(None) => {
129                    return Poll::Ready(Some(Ok(buf.split_to(buf.len()).freeze())));
130                }
131                Poll::Ready(Some(Ok(item))) => {
132                    if let Err(status) = encode_item(
133                        encoder,
134                        buf,
135                        uncompression_buf,
136                        *compression_encoding,
137                        *max_message_size,
138                        buffer_settings,
139                        item,
140                    ) {
141                        return Poll::Ready(Some(Err(status)));
142                    }
143
144                    if buf.len() >= buffer_settings.yield_threshold {
145                        return Poll::Ready(Some(Ok(buf.split_to(buf.len()).freeze())));
146                    }
147                }
148                Poll::Ready(Some(Err(status))) => {
149                    if buf.is_empty() {
150                        return Poll::Ready(Some(Err(status)));
151                    }
152                    *error = Some(status);
153                    return Poll::Ready(Some(Ok(buf.split_to(buf.len()).freeze())));
154                }
155            }
156        }
157    }
158}
159
160fn encode_item<T>(
161    encoder: &mut T,
162    buf: &mut BytesMut,
163    uncompression_buf: &mut BytesMut,
164    compression_encoding: Option<CompressionEncoding>,
165    max_message_size: Option<usize>,
166    buffer_settings: BufferSettings,
167    item: T::Item,
168) -> Result<(), Status>
169where
170    T: Encoder<Error = Status>,
171{
172    let offset = buf.len();
173
174    buf.reserve(HEADER_SIZE);
175    buf.put_slice(&[0u8; HEADER_SIZE]);
176
177    if let Some(encoding) = compression_encoding {
178        uncompression_buf.clear();
179
180        encoder
181            .encode(item, &mut EncodeBuf::new(uncompression_buf))
182            .map_err(|err| Status::internal(format!("Error encoding: {err}")))?;
183
184        let uncompressed_len = uncompression_buf.len();
185
186        compress(
187            CompressionSettings {
188                encoding,
189                buffer_growth_interval: buffer_settings.buffer_size,
190            },
191            uncompression_buf,
192            buf,
193            uncompressed_len,
194        )
195        .map_err(|err| Status::internal(format!("Error compressing: {err}")))?;
196    } else {
197        encoder
198            .encode(item, &mut EncodeBuf::new(buf))
199            .map_err(|err| Status::internal(format!("Error encoding: {err}")))?;
200    }
201
202    // now that we know length, we can write the header
203    finish_encoding(compression_encoding, max_message_size, &mut buf[offset..])
204}
205
206fn finish_encoding(
207    compression_encoding: Option<CompressionEncoding>,
208    max_message_size: Option<usize>,
209    buf: &mut [u8],
210) -> Result<(), Status> {
211    let len = buf.len() - HEADER_SIZE;
212    let limit = max_message_size.unwrap_or(DEFAULT_MAX_SEND_MESSAGE_SIZE);
213    if len > limit {
214        return Err(Status::out_of_range(format!(
215            "Error, encoded message length too large: found {len} bytes, the limit is: {limit} bytes"
216        )));
217    }
218
219    if len > u32::MAX as usize {
220        return Err(Status::resource_exhausted(format!(
221            "Cannot return body with more than 4GB of data but got {len} bytes"
222        )));
223    }
224    {
225        let mut buf = &mut buf[..HEADER_SIZE];
226        buf.put_u8(compression_encoding.is_some() as u8);
227        buf.put_u32(len as u32);
228    }
229
230    Ok(())
231}
232
233#[derive(Debug)]
234enum Role {
235    Client,
236    Server,
237}
238
239/// A specialized implementation of [Body] for encoding [Result<Bytes, Status>].
240#[pin_project]
241#[derive(Debug)]
242pub struct EncodeBody<T, U> {
243    #[pin]
244    inner: EncodedBytes<T, U>,
245    state: EncodeState,
246    #[pin]
247    cancellation_fut: Option<WaitForCancellationFutureOwned>,
248}
249
250#[derive(Debug)]
251struct EncodeState {
252    error: Option<Status>,
253    role: Role,
254    is_end_stream: bool,
255}
256
257impl<T: Encoder, U: Stream> EncodeBody<T, U> {
258    /// Turns a stream of grpc messages into [EncodeBody] which is used by grpc clients for
259    /// turning the messages into http frames for sending over the network.
260    pub fn new_client(
261        encoder: T,
262        source: U,
263        compression_encoding: Option<CompressionEncoding>,
264        max_message_size: Option<usize>,
265    ) -> Self {
266        Self::new_client_with_cancellation(
267            encoder,
268            source,
269            compression_encoding,
270            max_message_size,
271            None,
272        )
273    }
274
275    pub(crate) fn new_client_with_cancellation(
276        encoder: T,
277        source: U,
278        compression_encoding: Option<CompressionEncoding>,
279        max_message_size: Option<usize>,
280        cancellation_token: Option<CancellationToken>,
281    ) -> Self {
282        let cancellation_fut = cancellation_token.map(|c| c.cancelled_owned());
283        Self {
284            inner: EncodedBytes::new(
285                encoder,
286                source,
287                compression_encoding,
288                SingleMessageCompressionOverride::default(),
289                max_message_size,
290            ),
291            state: EncodeState {
292                error: None,
293                role: Role::Client,
294                is_end_stream: false,
295            },
296            cancellation_fut,
297        }
298    }
299
300    /// Turns a stream of grpc results (message or error status) into [EncodeBody] which is used by grpc
301    /// servers for turning the messages into http frames for sending over the network.
302    pub fn new_server(
303        encoder: T,
304        source: U,
305        compression_encoding: Option<CompressionEncoding>,
306        compression_override: SingleMessageCompressionOverride,
307        max_message_size: Option<usize>,
308    ) -> Self {
309        Self {
310            inner: EncodedBytes::new(
311                encoder,
312                source,
313                compression_encoding,
314                compression_override,
315                max_message_size,
316            ),
317            state: EncodeState {
318                error: None,
319                role: Role::Server,
320                is_end_stream: false,
321            },
322            cancellation_fut: None,
323        }
324    }
325}
326
327impl EncodeState {
328    fn trailers(&mut self) -> Option<Result<HeaderMap, Status>> {
329        match self.role {
330            Role::Client => None,
331            Role::Server => {
332                if self.is_end_stream {
333                    return None;
334                }
335
336                self.is_end_stream = true;
337                let status = if let Some(status) = self.error.take() {
338                    status
339                } else {
340                    Status::ok("")
341                };
342                Some(status.to_header_map())
343            }
344        }
345    }
346}
347
348impl<T, U> Body for EncodeBody<T, U>
349where
350    T: Encoder<Error = Status>,
351    U: Stream<Item = Result<T::Item, Status>>,
352{
353    type Data = Bytes;
354    type Error = Status;
355
356    fn is_end_stream(&self) -> bool {
357        self.state.is_end_stream
358    }
359
360    fn poll_frame(
361        self: Pin<&mut Self>,
362        cx: &mut Context<'_>,
363    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
364        let self_proj = self.project();
365
366        if let Some(cancellation_fut) = self_proj.cancellation_fut.as_pin_mut()
367            && let Poll::Ready(()) = cancellation_fut.poll(cx)
368        {
369            #[cfg(feature = "h2")]
370            let status = {
371                let mut status = Status::cancelled("client cancelled");
372                // h2 inspects the error's source chain to determine the RST
373                // code, so we set it here.
374                status.set_source(std::sync::Arc::new(H2Error::from(H2Reason::CANCEL)));
375                status
376            };
377
378            #[cfg(not(feature = "h2"))]
379            let status = Status::cancelled("client cancelled");
380            return Poll::Ready(Some(Err(status)));
381        }
382
383        match ready!(self_proj.inner.poll_next(cx)) {
384            Some(Ok(d)) => Some(Ok(Frame::data(d))).into(),
385            Some(Err(status)) => match self_proj.state.role {
386                Role::Client => Some(Err(status)).into(),
387                Role::Server => {
388                    self_proj.state.is_end_stream = true;
389                    Some(Ok(Frame::trailers(status.to_header_map()?))).into()
390                }
391            },
392            None => self_proj
393                .state
394                .trailers()
395                .map(|t| t.map(Frame::trailers))
396                .into(),
397        }
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404    use std::panic::catch_unwind;
405
406    struct PanickingEncoder;
407
408    impl Encoder for PanickingEncoder {
409        type Item = String;
410        type Error = Status;
411
412        fn encode(
413            &mut self,
414            _item: Self::Item,
415            _dst: &mut EncodeBuf<'_>,
416        ) -> Result<(), Self::Error> {
417            panic!("encoder deliberate panic for testing exception safety");
418        }
419    }
420
421    #[test]
422    fn encode_item_exception_safety_on_panic() {
423        let mut encoder = PanickingEncoder;
424        let mut buf = BytesMut::new();
425        let mut uncompression_buf = BytesMut::new();
426
427        let result = catch_unwind(std::panic::AssertUnwindSafe(|| {
428            encode_item(
429                &mut encoder,
430                &mut buf,
431                &mut uncompression_buf,
432                None,
433                None,
434                BufferSettings::default(),
435                "test".to_string(),
436            )
437        }));
438
439        assert!(result.is_err(), "Encoder panic should unwind correctly.");
440        // Buffer must only contain initialized bytes (5 zero bytes written by put_slice).
441        assert_eq!(
442            buf.len(),
443            HEADER_SIZE,
444            "Buffer length should reflect reserved header bytes."
445        );
446        assert_eq!(
447            &buf[..],
448            &[0u8; HEADER_SIZE],
449            "Buffer must contain only initialized zero bytes."
450        );
451    }
452}