Skip to main content

grpc_core/
framing.rs

1//! Incremental gRPC framing with eight-byte-aligned message payloads.
2
3use crate::status::Status;
4
5/// Size of the compressed flag and big-endian message length on the wire.
6pub const FRAME_HEADER_SIZE: usize = 5;
7/// Default maximum encoded message size accepted by the decoder.
8pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 16 * 1024 * 1024;
9const LEADING_PADDING: usize = 3;
10const PAYLOAD_OFFSET: usize = LEADING_PADDING + FRAME_HEADER_SIZE;
11
12#[repr(C, align(8))]
13#[derive(Clone, Debug)]
14struct AlignedWord([u8; 8]);
15
16/// An owned gRPC message whose payload starts at an eight-byte-aligned address.
17///
18/// Three private padding bytes precede the five-byte gRPC header. Only
19/// [`Self::wire_bytes`] should be transmitted; the padding never goes on the wire.
20/// Alignment applies to the encoded payload, including compressed payloads.
21#[derive(Clone, Debug)]
22pub struct AlignedMessageBuf {
23    words: Vec<AlignedWord>,
24    payload_len: usize,
25}
26
27impl AlignedMessageBuf {
28    /// Copies a payload into aligned storage and constructs its wire prefix.
29    ///
30    /// # Errors
31    /// Returns resource exhausted if the payload exceeds the wire length limit
32    /// or storage cannot be allocated.
33    pub fn new(payload: &[u8], compressed: bool) -> Result<Self, Status> {
34        let mut message = Self::with_payload_len(payload.len(), compressed)?;
35        message.payload_mut().copy_from_slice(payload);
36        Ok(message)
37    }
38
39    fn with_payload_len(payload_len: usize, compressed: bool) -> Result<Self, Status> {
40        let wire_len = u32::try_from(payload_len)
41            .map_err(|_| Status::resource_exhausted("gRPC message length exceeds u32"))?;
42        let storage_len = payload_len
43            .checked_add(PAYLOAD_OFFSET)
44            .ok_or_else(|| Status::resource_exhausted("gRPC aligned storage size overflow"))?;
45        let word_count = storage_len.div_ceil(size_of::<AlignedWord>());
46        let mut words = Vec::new();
47        words
48            .try_reserve_exact(word_count)
49            .map_err(|_| Status::resource_exhausted("cannot allocate gRPC message storage"))?;
50        words.resize(word_count, AlignedWord([0; 8]));
51        let mut message = Self { words, payload_len };
52        let bytes = message.storage_mut();
53        bytes[LEADING_PADDING] = u8::from(compressed);
54        bytes[LEADING_PADDING + 1..PAYLOAD_OFFSET].copy_from_slice(&wire_len.to_be_bytes());
55        Ok(message)
56    }
57
58    fn storage(&self) -> &[u8] {
59        // SAFETY: Every aligned word contains eight initialized bytes and has no
60        // padding. The
61        // byte slice stays within the allocation and borrows it immutably.
62        unsafe {
63            std::slice::from_raw_parts(
64                self.words.as_ptr().cast::<u8>(),
65                self.words.len() * size_of::<AlignedWord>(),
66            )
67        }
68    }
69
70    fn storage_mut(&mut self) -> &mut [u8] {
71        // SAFETY: The exclusive borrow covers initialized byte arrays without
72        // padding. Every byte value is valid, and the slice cannot outlive it.
73        unsafe {
74            std::slice::from_raw_parts_mut(
75                self.words.as_mut_ptr().cast::<u8>(),
76                self.words.len() * size_of::<AlignedWord>(),
77            )
78        }
79    }
80
81    /// Returns the aligned message payload without its gRPC header.
82    pub fn payload(&self) -> &[u8] {
83        &self.storage()[PAYLOAD_OFFSET..PAYLOAD_OFFSET + self.payload_len]
84    }
85
86    /// Returns the aligned payload for in-place processing without changing its length.
87    pub fn payload_mut(&mut self) -> &mut [u8] {
88        let end = PAYLOAD_OFFSET + self.payload_len;
89        &mut self.storage_mut()[PAYLOAD_OFFSET..end]
90    }
91
92    /// Returns precisely the five-byte header followed by the payload.
93    pub fn wire_bytes(&self) -> &[u8] {
94        &self.storage()[LEADING_PADDING..PAYLOAD_OFFSET + self.payload_len]
95    }
96
97    /// Whether the gRPC compressed flag is set; decompression is a separate step.
98    pub fn is_compressed(&self) -> bool {
99        self.storage()[LEADING_PADDING] == 1
100    }
101
102    /// Returns the encoded payload length.
103    pub fn len(&self) -> usize {
104        self.payload_len
105    }
106
107    /// Whether the encoded payload is empty.
108    pub fn is_empty(&self) -> bool {
109        self.payload_len == 0
110    }
111}
112
113/// Decodes arbitrary fragmented or coalesced HTTP/2 DATA payloads.
114///
115/// Length limits are checked before allocating a message body. A failed decoder
116/// rejects further input until reset. An error from `push` discards any messages
117/// completed in that call; callers must terminate the affected RPC stream.
118#[derive(Debug)]
119pub struct LengthPrefixedFrame {
120    max_message_size: usize,
121    header: [u8; FRAME_HEADER_SIZE],
122    header_len: usize,
123    message: Option<AlignedMessageBuf>,
124    received: usize,
125    failure: Option<Status>,
126}
127
128impl Default for LengthPrefixedFrame {
129    fn default() -> Self {
130        Self::new(DEFAULT_MAX_MESSAGE_SIZE)
131    }
132}
133
134impl LengthPrefixedFrame {
135    /// Creates a decoder with a maximum encoded payload length in bytes.
136    pub fn new(max_message_size: usize) -> Self {
137        Self {
138            max_message_size,
139            header: [0; FRAME_HEADER_SIZE],
140            header_len: 0,
141            message: None,
142            received: 0,
143            failure: None,
144        }
145    }
146
147    /// Consumes one DATA chunk and returns every complete message in wire order.
148    ///
149    /// Empty chunks are valid. Partial headers and bodies remain buffered until
150    /// the next call. Compression negotiation and decompression belong to the caller.
151    ///
152    /// # Errors
153    /// Returns internal for an invalid compressed flag, resource exhausted for
154    /// oversized messages or allocation failure, or the previous decoder failure.
155    pub fn push(&mut self, mut chunk: &[u8]) -> Result<Vec<AlignedMessageBuf>, Status> {
156        if let Some(error) = &self.failure {
157            return Err(error.clone());
158        }
159        let mut messages = Vec::new();
160        while !chunk.is_empty() {
161            if self.message.is_none() {
162                let count = (FRAME_HEADER_SIZE - self.header_len).min(chunk.len());
163                self.header[self.header_len..self.header_len + count]
164                    .copy_from_slice(&chunk[..count]);
165                self.header_len += count;
166                chunk = &chunk[count..];
167                if self.header_len < FRAME_HEADER_SIZE {
168                    continue;
169                }
170                let result = self.start_message();
171                if let Err(error) = result {
172                    return Err(self.fail(error));
173                }
174            }
175            if let Some(message) = &mut self.message {
176                let count = (message.len() - self.received).min(chunk.len());
177                message.payload_mut()[self.received..self.received + count]
178                    .copy_from_slice(&chunk[..count]);
179                self.received += count;
180                chunk = &chunk[count..];
181                if self.received == message.len() {
182                    if messages.try_reserve(1).is_err() {
183                        return Err(self.fail(Status::resource_exhausted(
184                            "cannot allocate gRPC decoded message list",
185                        )));
186                    }
187                    if let Some(complete) = self.message.take() {
188                        messages.push(complete);
189                    }
190                    self.header_len = 0;
191                    self.received = 0;
192                }
193            }
194        }
195        Ok(messages)
196    }
197
198    fn start_message(&mut self) -> Result<(), Status> {
199        if self.header[0] > 1 {
200            return Err(Status::internal("invalid gRPC compressed flag"));
201        }
202        let length = u32::from_be_bytes([
203            self.header[1],
204            self.header[2],
205            self.header[3],
206            self.header[4],
207        ]);
208        let length = usize::try_from(length)
209            .map_err(|_| Status::resource_exhausted("gRPC message length exceeds platform size"))?;
210        if length > self.max_message_size {
211            return Err(Status::resource_exhausted(
212                "gRPC message exceeds maximum decoding size",
213            ));
214        }
215        self.message = Some(AlignedMessageBuf::with_payload_len(
216            length,
217            self.header[0] == 1,
218        )?);
219        Ok(())
220    }
221
222    fn fail(&mut self, error: Status) -> Status {
223        self.message = None;
224        self.failure = Some(error.clone());
225        error
226    }
227
228    /// Validates the end of a DATA stream, including an empty stream.
229    ///
230    /// # Errors
231    /// Returns internal for a truncated header or body, or the previous failure.
232    pub fn finish(&mut self) -> Result<(), Status> {
233        if let Some(error) = &self.failure {
234            return Err(error.clone());
235        }
236        if self.header_len != 0 || self.message.is_some() {
237            return Err(self.fail(Status::internal("truncated gRPC message frame")));
238        }
239        Ok(())
240    }
241
242    /// Discards buffered input and failure state before decoding a different stream.
243    pub fn reset(&mut self) {
244        self.header_len = 0;
245        self.message = None;
246        self.received = 0;
247        self.failure = None;
248    }
249}
250
251#[cfg(test)]
252mod unit {
253    use super::*;
254    use crate::status::Code;
255
256    fn assert_aligned(message: &AlignedMessageBuf, payload: &[u8], compressed: bool) {
257        assert_eq!(message.payload().as_ptr() as usize % 8, 0);
258        assert_eq!(message.payload(), payload);
259        assert_eq!(message.is_compressed(), compressed);
260        assert_eq!(
261            message.wire_bytes().len(),
262            FRAME_HEADER_SIZE + payload.len()
263        );
264        assert_eq!(message.wire_bytes()[0], u8::from(compressed));
265        assert_eq!(
266            &message.wire_bytes()[1..FRAME_HEADER_SIZE],
267            &(payload.len() as u32).to_be_bytes()
268        );
269    }
270
271    #[test]
272    fn scenario_alignment_survives_all_chunk_sizes_and_coalesced_messages() {
273        let payloads: [&[u8]; 4] = [b"", b"a", b"12345678", b"arbitrary payload length"];
274        let mut wire = Vec::new();
275        for (index, payload) in payloads.iter().enumerate() {
276            let message = AlignedMessageBuf::new(payload, index % 2 == 1).unwrap();
277            assert_aligned(&message, payload, index % 2 == 1);
278            assert_aligned(&message.clone(), payload, index % 2 == 1);
279            wire.extend_from_slice(message.wire_bytes());
280        }
281        for chunk_size in 1..=wire.len() {
282            let mut decoder = LengthPrefixedFrame::new(128);
283            let mut decoded = Vec::new();
284            for chunk in wire.chunks(chunk_size) {
285                decoded.extend(decoder.push(chunk).unwrap());
286                assert!(decoder.push(&[]).unwrap().is_empty());
287            }
288            decoder.finish().unwrap();
289            assert_eq!(decoded.len(), payloads.len());
290            for (index, (message, payload)) in decoded.iter().zip(payloads).enumerate() {
291                assert_aligned(message, payload, index % 2 == 1);
292            }
293        }
294    }
295
296    #[test]
297    fn scenario_every_single_split_preserves_alignment_and_wire_bytes() {
298        let original = AlignedMessageBuf::new(b"capnp aligned payload", false).unwrap();
299        for split in 0..=original.wire_bytes().len() {
300            let mut decoder = LengthPrefixedFrame::default();
301            let mut decoded = decoder.push(&original.wire_bytes()[..split]).unwrap();
302            decoded.extend(decoder.push(&original.wire_bytes()[split..]).unwrap());
303            decoder.finish().unwrap();
304            assert_eq!(decoded.len(), 1);
305            assert_aligned(&decoded[0], original.payload(), false);
306            assert_eq!(decoded[0].wire_bytes(), original.wire_bytes());
307        }
308    }
309
310    #[test]
311    fn scenario_corrupt_flags_fail_closed_until_reset() {
312        for flag in [2, 127, 255] {
313            let mut decoder = LengthPrefixedFrame::default();
314            let error = decoder.push(&[flag, 0, 0, 0, 0]).unwrap_err();
315            assert_eq!(error.code(), Code::Internal);
316            assert_eq!(decoder.push(&[]).unwrap_err().code(), Code::Internal);
317            assert_eq!(decoder.finish().unwrap_err().code(), Code::Internal);
318            decoder.reset();
319            assert_eq!(decoder.push(&[0; 5]).unwrap().len(), 1);
320            decoder.finish().unwrap();
321        }
322    }
323
324    #[test]
325    fn scenario_size_limit_rejects_header_without_receiving_body() {
326        let mut decoder = LengthPrefixedFrame::new(3);
327        assert!(decoder.push(&[0, 0, 0, 0]).unwrap().is_empty());
328        assert_eq!(
329            decoder.push(&[4]).unwrap_err().code(),
330            Code::ResourceExhausted
331        );
332        decoder.reset();
333        let message = AlignedMessageBuf::new(b"abc", false).unwrap();
334        assert_eq!(decoder.push(message.wire_bytes()).unwrap().len(), 1);
335        decoder.finish().unwrap();
336        decoder.reset();
337        assert_eq!(
338            decoder.push(&[0, 255, 255, 255, 255]).unwrap_err().code(),
339            Code::ResourceExhausted
340        );
341    }
342
343    #[test]
344    fn scenario_truncation_detects_every_partial_header_and_body() {
345        let message = AlignedMessageBuf::new(b"body", false).unwrap();
346        for end in 1..message.wire_bytes().len() {
347            let mut decoder = LengthPrefixedFrame::default();
348            assert!(
349                decoder
350                    .push(&message.wire_bytes()[..end])
351                    .unwrap()
352                    .is_empty()
353            );
354            assert_eq!(decoder.finish().unwrap_err().code(), Code::Internal);
355        }
356        LengthPrefixedFrame::default().finish().unwrap();
357    }
358
359    #[test]
360    fn scenario_zero_limit_accepts_empty_payload_only() {
361        let mut decoder = LengthPrefixedFrame::new(0);
362        let decoded = decoder.push(&[0; 10]).unwrap();
363        assert_eq!(decoded.len(), 2);
364        for message in decoded {
365            assert_aligned(&message, b"", false);
366        }
367        decoder.finish().unwrap();
368        assert_eq!(
369            decoder.push(&[0, 0, 0, 0, 1]).unwrap_err().code(),
370            Code::ResourceExhausted
371        );
372    }
373
374    #[test]
375    fn scenario_lifecycle_reset_discards_every_partial_frame() {
376        let abandoned = AlignedMessageBuf::new(b"abandoned", true).unwrap();
377        let replacement = AlignedMessageBuf::new(b"replacement", false).unwrap();
378        for end in 1..abandoned.wire_bytes().len() {
379            let mut decoder = LengthPrefixedFrame::default();
380            assert!(
381                decoder
382                    .push(&abandoned.wire_bytes()[..end])
383                    .unwrap()
384                    .is_empty()
385            );
386            decoder.reset();
387            let decoded = decoder.push(replacement.wire_bytes()).unwrap();
388            assert_eq!(decoded.len(), 1);
389            assert_aligned(&decoded[0], replacement.payload(), false);
390            decoder.finish().unwrap();
391        }
392    }
393
394    #[test]
395    fn scenario_failure_after_complete_message_rejects_entire_chunk() {
396        let valid = AlignedMessageBuf::new(b"valid", false).unwrap();
397        let mut chunk = valid.wire_bytes().to_vec();
398        chunk.extend_from_slice(&[2, 0, 0, 0, 0]);
399        let mut decoder = LengthPrefixedFrame::default();
400        let error = decoder.push(&chunk).unwrap_err();
401        assert_eq!(error.code(), Code::Internal);
402        assert_eq!(decoder.push(valid.wire_bytes()).unwrap_err(), error);
403        assert_eq!(decoder.finish().unwrap_err(), error);
404        decoder.reset();
405        let decoded = decoder.push(valid.wire_bytes()).unwrap();
406        assert_eq!(decoded.len(), 1);
407        assert_aligned(&decoded[0], valid.payload(), false);
408        decoder.finish().unwrap();
409    }
410}