Skip to main content

tonic_web/
call.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 std::fmt;
26use std::pin::Pin;
27use std::task::{Context, Poll, ready};
28
29use base64::Engine as _;
30use bytes::{Buf, BufMut, Bytes, BytesMut};
31use http::{HeaderMap, HeaderName, HeaderValue, header};
32use http_body::{Body, Frame, SizeHint};
33use pin_project::pin_project;
34use tokio_stream::Stream;
35use tonic::Status;
36
37use self::content_types::*;
38
39// A grpc header is u8 (flag) + u32 (msg len)
40const GRPC_HEADER_SIZE: usize = 1 + 4;
41
42pub(crate) mod content_types {
43    use http::{HeaderMap, header::CONTENT_TYPE};
44
45    pub(crate) const GRPC_WEB: &str = "application/grpc-web";
46    pub(crate) const GRPC_WEB_PROTO: &str = "application/grpc-web+proto";
47    pub(crate) const GRPC_WEB_TEXT: &str = "application/grpc-web-text";
48    pub(crate) const GRPC_WEB_TEXT_PROTO: &str = "application/grpc-web-text+proto";
49
50    pub(crate) fn is_grpc_web(headers: &HeaderMap) -> bool {
51        matches!(
52            content_type(headers),
53            Some(GRPC_WEB) | Some(GRPC_WEB_PROTO) | Some(GRPC_WEB_TEXT) | Some(GRPC_WEB_TEXT_PROTO)
54        )
55    }
56
57    fn content_type(headers: &HeaderMap) -> Option<&str> {
58        headers.get(CONTENT_TYPE).and_then(|val| val.to_str().ok())
59    }
60}
61
62const BUFFER_SIZE: usize = 8 * 1024;
63
64const FRAME_HEADER_SIZE: usize = 5;
65
66// 8th (MSB) bit of the 1st gRPC frame byte
67// denotes an uncompressed trailer (as part of the body)
68const GRPC_WEB_TRAILERS_BIT: u8 = 0b10000000;
69
70#[derive(Copy, Clone, PartialEq, Debug)]
71enum Direction {
72    Decode,
73    Encode,
74    Empty,
75}
76
77#[derive(Copy, Clone, PartialEq, Debug)]
78pub(crate) enum Encoding {
79    Base64,
80    None,
81}
82
83/// HttpBody adapter for the grpc web based services.
84#[derive(Debug)]
85#[pin_project]
86pub struct GrpcWebCall<B> {
87    #[pin]
88    inner: B,
89    buf: BytesMut,
90    decoded: BytesMut,
91    direction: Direction,
92    encoding: Encoding,
93    client: bool,
94    trailers: Option<HeaderMap>,
95}
96
97impl<B: Default> Default for GrpcWebCall<B> {
98    fn default() -> Self {
99        Self {
100            inner: Default::default(),
101            buf: Default::default(),
102            decoded: Default::default(),
103            direction: Direction::Empty,
104            encoding: Encoding::None,
105            client: Default::default(),
106            trailers: Default::default(),
107        }
108    }
109}
110
111impl<B> GrpcWebCall<B> {
112    pub(crate) fn request(inner: B, encoding: Encoding) -> Self {
113        Self::new(inner, Direction::Decode, encoding)
114    }
115
116    pub(crate) fn response(inner: B, encoding: Encoding) -> Self {
117        Self::new(inner, Direction::Encode, encoding)
118    }
119
120    pub(crate) fn client_request(inner: B) -> Self {
121        Self::new_client(inner, Direction::Encode, Encoding::None)
122    }
123
124    pub(crate) fn client_response(inner: B) -> Self {
125        Self::new_client(inner, Direction::Decode, Encoding::None)
126    }
127
128    fn new_client(inner: B, direction: Direction, encoding: Encoding) -> Self {
129        GrpcWebCall {
130            inner,
131            buf: BytesMut::with_capacity(match (direction, encoding) {
132                (Direction::Encode, Encoding::Base64) => BUFFER_SIZE,
133                _ => 0,
134            }),
135            decoded: BytesMut::with_capacity(match direction {
136                Direction::Decode => BUFFER_SIZE,
137                _ => 0,
138            }),
139            direction,
140            encoding,
141            client: true,
142            trailers: None,
143        }
144    }
145
146    fn new(inner: B, direction: Direction, encoding: Encoding) -> Self {
147        GrpcWebCall {
148            inner,
149            buf: BytesMut::with_capacity(match (direction, encoding) {
150                (Direction::Encode, Encoding::Base64) => BUFFER_SIZE,
151                _ => 0,
152            }),
153            decoded: BytesMut::with_capacity(0),
154            direction,
155            encoding,
156            client: false,
157            trailers: None,
158        }
159    }
160
161    // This is to avoid passing a slice of bytes with a length that the base64
162    // decoder would consider invalid.
163    #[inline]
164    fn max_decodable(&self) -> usize {
165        (self.buf.len() / 4) * 4
166    }
167
168    fn decode_chunk(mut self: Pin<&mut Self>) -> Result<Option<Bytes>, Status> {
169        // not enough bytes to decode
170        if self.buf.is_empty() || self.buf.len() < 4 {
171            return Ok(None);
172        }
173
174        // Split `buf` at the largest index that is multiple of 4. Decode the
175        // returned `Bytes`, keeping the rest for the next attempt to decode.
176        let index = self.max_decodable();
177
178        crate::util::base64::STANDARD
179            .decode(self.as_mut().project().buf.split_to(index))
180            .map(|decoded| Some(Bytes::from(decoded)))
181            .map_err(internal_error)
182    }
183}
184
185impl<B> GrpcWebCall<B>
186where
187    B: Body,
188    B::Data: Buf,
189    B::Error: fmt::Display,
190{
191    // Poll body for data, decoding (e.g. via Base64 if necessary) and returning frames
192    // to the caller. If the caller is a client, it should look for trailers before
193    // returning these frames.
194    fn poll_decode(
195        mut self: Pin<&mut Self>,
196        cx: &mut Context<'_>,
197    ) -> Poll<Option<Result<Frame<Bytes>, Status>>> {
198        match self.encoding {
199            Encoding::Base64 => loop {
200                if let Some(bytes) = self.as_mut().decode_chunk()? {
201                    return Poll::Ready(Some(Ok(Frame::data(bytes))));
202                }
203
204                let this = self.as_mut().project();
205
206                match ready!(this.inner.poll_frame(cx)) {
207                    Some(Ok(frame)) if frame.is_data() => this
208                        .buf
209                        .put(frame.into_data().unwrap_or_else(|_| unreachable!())),
210                    Some(Ok(frame)) if frame.is_trailers() => {
211                        return Poll::Ready(Some(Err(internal_error(
212                            "malformed base64 request has unencoded trailers",
213                        ))));
214                    }
215                    Some(Ok(_)) => {
216                        return Poll::Ready(Some(Err(internal_error("unexpected frame type"))));
217                    }
218                    Some(Err(e)) => return Poll::Ready(Some(Err(internal_error(e)))),
219                    None => {
220                        return if this.buf.has_remaining() {
221                            Poll::Ready(Some(Err(internal_error("malformed base64 request"))))
222                        } else if let Some(trailers) = this.trailers.take() {
223                            Poll::Ready(Some(Ok(Frame::trailers(trailers))))
224                        } else {
225                            Poll::Ready(None)
226                        };
227                    }
228                }
229            },
230
231            Encoding::None => self
232                .project()
233                .inner
234                .poll_frame(cx)
235                .map_ok(|f| f.map_data(|mut d| d.copy_to_bytes(d.remaining())))
236                .map_err(internal_error),
237        }
238    }
239
240    fn poll_encode(
241        mut self: Pin<&mut Self>,
242        cx: &mut Context<'_>,
243    ) -> Poll<Option<Result<Frame<Bytes>, Status>>> {
244        let this = self.as_mut().project();
245
246        match ready!(this.inner.poll_frame(cx)) {
247            Some(Ok(frame)) if frame.is_data() => {
248                let mut data = frame.into_data().unwrap_or_else(|_| unreachable!());
249                let mut res = data.copy_to_bytes(data.remaining());
250
251                if *this.encoding == Encoding::Base64 {
252                    res = crate::util::base64::STANDARD.encode(res).into();
253                }
254
255                Poll::Ready(Some(Ok(Frame::data(res))))
256            }
257            Some(Ok(frame)) if frame.is_trailers() => {
258                let trailers = frame.into_trailers().unwrap_or_else(|_| unreachable!());
259                let mut res = make_trailers_frame(trailers);
260
261                if *this.encoding == Encoding::Base64 {
262                    res = crate::util::base64::STANDARD.encode(res).into();
263                }
264
265                Poll::Ready(Some(Ok(Frame::data(res))))
266            }
267            Some(Ok(_)) => Poll::Ready(Some(Err(internal_error("unexpected frame type")))),
268            Some(Err(e)) => Poll::Ready(Some(Err(internal_error(e)))),
269            None => Poll::Ready(None),
270        }
271    }
272}
273
274impl<B> Body for GrpcWebCall<B>
275where
276    B: Body,
277    B::Error: fmt::Display,
278{
279    type Data = Bytes;
280    type Error = Status;
281
282    fn poll_frame(
283        mut self: Pin<&mut Self>,
284        cx: &mut Context<'_>,
285    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
286        if self.client && self.direction == Direction::Decode {
287            let mut me = self.as_mut();
288
289            loop {
290                match ready!(me.as_mut().poll_decode(cx)) {
291                    Some(Ok(incoming_buf)) if incoming_buf.is_data() => {
292                        me.as_mut()
293                            .project()
294                            .decoded
295                            .put(incoming_buf.into_data().unwrap());
296                    }
297                    Some(Ok(incoming_buf)) if incoming_buf.is_trailers() => {
298                        let trailers = incoming_buf.into_trailers().unwrap();
299                        match me.as_mut().project().trailers {
300                            Some(current_trailers) => {
301                                current_trailers.extend(trailers);
302                            }
303                            None => {
304                                me.as_mut().project().trailers.replace(trailers);
305                            }
306                        }
307                        continue;
308                    }
309                    Some(Ok(_)) => unreachable!("unexpected frame type"),
310                    None => {} // No more data to decode, time to look for trailers
311                    Some(Err(e)) => return Poll::Ready(Some(Err(e))),
312                };
313
314                // Hold the incoming, decoded data until we have a full message
315                // or trailers to return.
316                let buf = me.as_mut().project().decoded;
317
318                return match find_trailers(&buf[..])? {
319                    FindTrailers::Trailer(len) => {
320                        // Extract up to len of where the trailers are at
321                        let msg_buf = buf.copy_to_bytes(len);
322                        match decode_trailers_frame(buf.split().freeze()) {
323                            Ok(Some(trailers)) => {
324                                me.as_mut().project().trailers.replace(trailers);
325                            }
326                            Err(e) => return Poll::Ready(Some(Err(e))),
327                            _ => {}
328                        }
329
330                        if msg_buf.has_remaining() {
331                            Poll::Ready(Some(Ok(Frame::data(msg_buf))))
332                        } else if let Some(trailers) = me.as_mut().project().trailers.take() {
333                            Poll::Ready(Some(Ok(Frame::trailers(trailers))))
334                        } else {
335                            Poll::Ready(None)
336                        }
337                    }
338                    FindTrailers::IncompleteBuf => continue,
339                    FindTrailers::Done(len) => Poll::Ready(match len {
340                        0 => None,
341                        _ => Some(Ok(Frame::data(buf.split_to(len).freeze()))),
342                    }),
343                };
344            }
345        }
346
347        match self.direction {
348            Direction::Decode => self.poll_decode(cx),
349            Direction::Encode => self.poll_encode(cx),
350            Direction::Empty => Poll::Ready(None),
351        }
352    }
353
354    fn is_end_stream(&self) -> bool {
355        self.inner.is_end_stream()
356    }
357
358    fn size_hint(&self) -> SizeHint {
359        self.inner.size_hint()
360    }
361}
362
363impl<B> Stream for GrpcWebCall<B>
364where
365    B: Body,
366    B::Error: fmt::Display,
367{
368    type Item = Result<Frame<Bytes>, Status>;
369
370    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
371        self.poll_frame(cx)
372    }
373}
374
375impl Encoding {
376    pub(crate) fn from_content_type(headers: &HeaderMap) -> Encoding {
377        Self::from_header(headers.get(header::CONTENT_TYPE))
378    }
379
380    pub(crate) fn from_accept(headers: &HeaderMap) -> Encoding {
381        Self::from_header(headers.get(header::ACCEPT))
382    }
383
384    pub(crate) fn to_content_type(self) -> &'static str {
385        match self {
386            Encoding::Base64 => GRPC_WEB_TEXT_PROTO,
387            Encoding::None => GRPC_WEB_PROTO,
388        }
389    }
390
391    fn from_header(value: Option<&HeaderValue>) -> Encoding {
392        match value.and_then(|val| val.to_str().ok()) {
393            Some(GRPC_WEB_TEXT_PROTO) | Some(GRPC_WEB_TEXT) => Encoding::Base64,
394            _ => Encoding::None,
395        }
396    }
397}
398
399fn internal_error(e: impl std::fmt::Display) -> Status {
400    Status::internal(format!("tonic-web: {e}"))
401}
402
403// Key-value pairs encoded as a HTTP/1 headers block (without the terminating newline)
404fn encode_trailers(trailers: HeaderMap) -> Vec<u8> {
405    trailers.iter().fold(Vec::new(), |mut acc, (key, value)| {
406        acc.put_slice(key.as_ref());
407        acc.push(b':');
408        acc.put_slice(value.as_bytes());
409        acc.put_slice(b"\r\n");
410        acc
411    })
412}
413
414fn decode_trailers_frame(mut buf: Bytes) -> Result<Option<HeaderMap>, Status> {
415    if buf.remaining() < GRPC_HEADER_SIZE {
416        return Ok(None);
417    }
418
419    buf.get_u8();
420    buf.get_u32();
421
422    let mut map = HeaderMap::new();
423    let mut temp_buf = buf.clone();
424
425    let mut trailers = Vec::new();
426    let mut cursor_pos = 0;
427
428    for (i, b) in buf.iter().enumerate() {
429        // if we are at a trailer delimiter (\r\n)
430        if b == &b'\r' && buf.get(i + 1) == Some(&b'\n') {
431            // read the bytes of the trailer passed so far
432            let trailer = temp_buf.copy_to_bytes(i - cursor_pos);
433            // increment cursor beyond the delimiter
434            cursor_pos = i + 2;
435            trailers.push(trailer);
436            if temp_buf.has_remaining() {
437                // advance buf beyond the delimiters
438                temp_buf.get_u8();
439                temp_buf.get_u8();
440            }
441        }
442    }
443
444    for trailer in trailers {
445        let Some((key, value)) = trailer
446            .iter()
447            .position(|b| *b == b':')
448            .map(|pos| trailer.split_at(pos))
449        else {
450            return Err(Status::internal("trailers couldn't parse key/value"));
451        };
452
453        // Skip the ':' separator and trim leading OWS (spaces/tabs) from the value
454        let value = &value[1..]; // skip ':'
455        let value = trim_ascii_start(value);
456
457        let header_key = HeaderName::try_from(key)
458            .map_err(|e| Status::internal(format!("Unable to parse HeaderName: {e}")))?;
459        let header_value = HeaderValue::try_from(value)
460            .map_err(|e| Status::internal(format!("Unable to parse HeaderValue: {e}")))?;
461        map.insert(header_key, header_value);
462    }
463
464    Ok(Some(map))
465}
466
467fn trim_ascii_start(bytes: &[u8]) -> &[u8] {
468    let start = bytes
469        .iter()
470        .position(|b| !b.is_ascii_whitespace())
471        .unwrap_or(bytes.len());
472    &bytes[start..]
473}
474
475fn make_trailers_frame(trailers: HeaderMap) -> Bytes {
476    let trailers = encode_trailers(trailers);
477    let len = trailers.len();
478    assert!(len <= u32::MAX as usize);
479
480    let mut frame = BytesMut::with_capacity(len + FRAME_HEADER_SIZE);
481    frame.put_u8(GRPC_WEB_TRAILERS_BIT);
482    frame.put_u32(len as u32);
483    frame.put_slice(&trailers);
484
485    frame.freeze()
486}
487
488/// Search some buffer for grpc-web trailers headers and return
489/// its location in the original buf. If `None` is returned we did
490/// not find a trailers in this buffer either because its incomplete
491/// or the buffer just contained grpc message frames.
492fn find_trailers(buf: &[u8]) -> Result<FindTrailers, Status> {
493    let mut len = 0;
494    let mut temp_buf = buf;
495
496    loop {
497        // To check each frame, there must be at least GRPC_HEADER_SIZE
498        // amount of bytes available otherwise the buffer is incomplete.
499        if temp_buf.is_empty() || temp_buf.len() < GRPC_HEADER_SIZE {
500            return Ok(FindTrailers::Done(len));
501        }
502
503        let header = temp_buf.get_u8();
504
505        if header == GRPC_WEB_TRAILERS_BIT {
506            return Ok(FindTrailers::Trailer(len));
507        }
508
509        if !(header == 0 || header == 1) {
510            return Err(Status::internal(format!(
511                "Invalid header bit {header} expected 0 or 1"
512            )));
513        }
514
515        let msg_len = temp_buf.get_u32();
516
517        len += msg_len as usize + 4 + 1;
518
519        // If the msg len of a non-grpc-web trailer frame is larger than
520        // the overall buffer we know within that buffer there are no trailers.
521        if len > buf.len() {
522            return Ok(FindTrailers::IncompleteBuf);
523        }
524
525        temp_buf = &buf[len..];
526    }
527}
528
529#[derive(Debug, PartialEq, Eq)]
530enum FindTrailers {
531    Trailer(usize),
532    IncompleteBuf,
533    Done(usize),
534}
535
536#[cfg(test)]
537mod tests {
538    use tonic::Code;
539
540    use super::*;
541
542    #[test]
543    fn encoding_constructors() {
544        let cases = &[
545            (GRPC_WEB, Encoding::None),
546            (GRPC_WEB_PROTO, Encoding::None),
547            (GRPC_WEB_TEXT, Encoding::Base64),
548            (GRPC_WEB_TEXT_PROTO, Encoding::Base64),
549            ("foo", Encoding::None),
550        ];
551
552        let mut headers = HeaderMap::new();
553
554        for case in cases {
555            headers.insert(header::CONTENT_TYPE, case.0.parse().unwrap());
556            headers.insert(header::ACCEPT, case.0.parse().unwrap());
557
558            assert_eq!(Encoding::from_content_type(&headers), case.1, "{}", case.0);
559            assert_eq!(Encoding::from_accept(&headers), case.1, "{}", case.0);
560        }
561    }
562
563    #[test]
564    fn decode_trailers() {
565        let mut headers = HeaderMap::new();
566        headers.insert(Status::GRPC_STATUS, 0.into());
567        headers.insert(
568            Status::GRPC_MESSAGE,
569            "this is a message".try_into().unwrap(),
570        );
571
572        let trailers = make_trailers_frame(headers.clone());
573
574        let map = decode_trailers_frame(trailers).unwrap().unwrap();
575
576        assert_eq!(headers, map);
577    }
578
579    #[test]
580    fn find_trailers_non_buffered() {
581        // Byte version of this:
582        // b"\x80\0\0\0\x0fgrpc-status:0\r\n"
583        let buf = [
584            128, 0, 0, 0, 15, 103, 114, 112, 99, 45, 115, 116, 97, 116, 117, 115, 58, 48, 13, 10,
585        ];
586
587        let out = find_trailers(&buf[..]).unwrap();
588
589        assert_eq!(out, FindTrailers::Trailer(0));
590    }
591
592    #[test]
593    fn find_trailers_buffered() {
594        // Byte version of this:
595        // b"\0\0\0\0L\n$975738af-1a17-4aea-b887-ed0bbced6093\x1a$da609e9b-f470-4cc0-a691-3fd6a005a436\x80\0\0\0\x0fgrpc-status:0\r\n"
596        let buf = [
597            0, 0, 0, 0, 76, 10, 36, 57, 55, 53, 55, 51, 56, 97, 102, 45, 49, 97, 49, 55, 45, 52,
598            97, 101, 97, 45, 98, 56, 56, 55, 45, 101, 100, 48, 98, 98, 99, 101, 100, 54, 48, 57,
599            51, 26, 36, 100, 97, 54, 48, 57, 101, 57, 98, 45, 102, 52, 55, 48, 45, 52, 99, 99, 48,
600            45, 97, 54, 57, 49, 45, 51, 102, 100, 54, 97, 48, 48, 53, 97, 52, 51, 54, 128, 0, 0, 0,
601            15, 103, 114, 112, 99, 45, 115, 116, 97, 116, 117, 115, 58, 48, 13, 10,
602        ];
603
604        let out = find_trailers(&buf[..]).unwrap();
605
606        assert_eq!(out, FindTrailers::Trailer(81));
607
608        let trailers = decode_trailers_frame(Bytes::copy_from_slice(&buf[81..]))
609            .unwrap()
610            .unwrap();
611        let status = trailers.get(Status::GRPC_STATUS).unwrap();
612        assert_eq!(status.to_str().unwrap(), "0")
613    }
614
615    #[test]
616    fn find_trailers_buffered_incomplete_message() {
617        let buf = vec![
618            0, 0, 0, 9, 238, 10, 233, 19, 18, 230, 19, 10, 9, 10, 1, 120, 26, 4, 84, 69, 88, 84,
619            18, 60, 10, 58, 10, 56, 3, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 116, 104, 105, 115, 32,
620            118, 97, 108, 117, 101, 32, 119, 97, 115, 32, 119, 114, 105, 116, 116, 101, 110, 32,
621            118, 105, 97, 32, 119, 114, 105, 116, 101, 32, 100, 101, 108, 101, 103, 97, 116, 105,
622            111, 110, 33, 18, 62, 10, 60, 10, 58, 3, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 116, 104,
623            105, 115, 32, 118, 97, 108, 117, 101, 32, 119, 97, 115, 32, 119, 114, 105, 116, 116,
624            101, 110, 32, 98, 121, 32, 97, 110, 32, 101, 109, 98, 101, 100, 100, 101, 100, 32, 114,
625            101, 112, 108, 105, 99, 97, 33, 18, 62, 10, 60, 10, 58, 3, 0, 0, 0, 46, 0, 0, 0, 0, 0,
626            0, 0, 116, 104, 105, 115, 32, 118, 97, 108, 117, 101, 32, 119, 97, 115, 32, 119, 114,
627            105, 116, 116, 101, 110, 32, 98, 121, 32, 97, 110, 32, 101, 109, 98, 101, 100, 100,
628            101, 100, 32, 114, 101, 112, 108, 105, 99, 97, 33, 18, 62, 10, 60, 10, 58, 3, 0, 0, 0,
629            46, 0, 0, 0, 0, 0, 0, 0, 116, 104, 105, 115, 32, 118, 97, 108, 117, 101, 32, 119, 97,
630            115, 32, 119, 114, 105, 116, 116, 101, 110, 32, 98, 121, 32, 97, 110, 32, 101, 109, 98,
631            101, 100, 100, 101, 100, 32, 114, 101, 112, 108, 105, 99, 97, 33, 18, 62, 10, 60, 10,
632            58, 3, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 116, 104, 105, 115, 32, 118, 97, 108, 117,
633            101, 32, 119, 97, 115, 32, 119, 114, 105, 116, 116, 101, 110, 32, 98, 121, 32, 97, 110,
634            32, 101, 109, 98, 101, 100, 100, 101, 100, 32, 114, 101, 112, 108, 105, 99, 97, 33, 18,
635            62, 10, 60, 10, 58, 3, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 116, 104, 105, 115, 32, 118,
636            97, 108, 117, 101, 32, 119, 97, 115, 32, 119, 114, 105, 116, 116, 101, 110, 32, 98,
637            121, 32, 97, 110, 32, 101, 109, 98, 101, 100, 100, 101, 100, 32, 114, 101, 112, 108,
638            105, 99, 97, 33, 18, 62, 10, 60, 10, 58, 3, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 116, 104,
639            105, 115, 32, 118, 97, 108, 117, 101, 32, 119, 97, 115, 32, 119, 114, 105, 116, 116,
640            101, 110, 32, 98, 121, 32, 97, 110, 32, 101, 109, 98, 101, 100, 100, 101, 100, 32, 114,
641            101, 112, 108, 105, 99, 97, 33, 18, 62, 10, 60, 10, 58, 3, 0, 0, 0, 46, 0, 0, 0, 0, 0,
642            0, 0, 116, 104, 105, 115, 32, 118, 97, 108, 117, 101, 32, 119, 97, 115, 32, 119, 114,
643            105, 116, 116, 101, 110, 32, 98, 121, 32,
644        ];
645
646        let out = find_trailers(&buf[..]).unwrap();
647
648        assert_eq!(out, FindTrailers::IncompleteBuf);
649    }
650
651    #[test]
652    #[ignore]
653    fn find_trailers_buffered_incomplete_buf_bug() {
654        let buf = std::fs::read("tests/incomplete-buf-bug.bin").unwrap();
655        let out = find_trailers(&buf[..]).unwrap_err();
656
657        assert_eq!(out.code(), Code::Internal);
658    }
659
660    #[test]
661    fn decode_multiple_trailers() {
662        let buf = b"\x80\0\0\0\x0fgrpc-status:0\r\ngrpc-message:\r\na:1\r\nb:2\r\n";
663
664        let trailers = decode_trailers_frame(Bytes::copy_from_slice(&buf[..]))
665            .unwrap()
666            .unwrap();
667
668        let mut expected = HeaderMap::new();
669        expected.insert(Status::GRPC_STATUS, "0".parse().unwrap());
670        expected.insert(Status::GRPC_MESSAGE, "".parse().unwrap());
671        expected.insert("a", "1".parse().unwrap());
672        expected.insert("b", "2".parse().unwrap());
673
674        assert_eq!(trailers, expected);
675    }
676
677    #[test]
678    fn decode_trailers_with_space_after_colon() {
679        let buf = b"\x80\0\0\0\x0fgrpc-status: 0\r\ngrpc-message: \r\n";
680
681        let trailers = decode_trailers_frame(Bytes::copy_from_slice(&buf[..]))
682            .unwrap()
683            .unwrap();
684
685        let mut expected = HeaderMap::new();
686        expected.insert(Status::GRPC_STATUS, "0".parse().unwrap());
687        expected.insert(Status::GRPC_MESSAGE, "".parse().unwrap());
688
689        assert_eq!(trailers, expected);
690    }
691
692    #[test]
693    fn decode_trailers_space_after_colon() {
694        // connect-rpc and standard HTTP use "key: value" (space after colon)
695        let trailers_bytes = b"grpc-status: 0\r\ngrpc-message: this is a message\r\n";
696        let len = trailers_bytes.len();
697
698        let mut frame = BytesMut::new();
699        frame.put_u8(GRPC_WEB_TRAILERS_BIT);
700        frame.put_u32(len as u32);
701        frame.put_slice(&trailers_bytes[..]);
702
703        let map = decode_trailers_frame(frame.freeze()).unwrap().unwrap();
704
705        let mut expected = HeaderMap::new();
706        expected.insert(Status::GRPC_STATUS, HeaderValue::from_static("0"));
707        expected.insert(
708            Status::GRPC_MESSAGE,
709            HeaderValue::from_static("this is a message"),
710        );
711
712        assert_eq!(map, expected);
713    }
714
715    #[test]
716    fn decode_trailers_value_with_colons() {
717        let trailers_bytes = b"grpc-status: 0\r\ngrpc-message: error: something: went wrong\r\n";
718        let len = trailers_bytes.len();
719
720        let mut frame = BytesMut::new();
721        frame.put_u8(GRPC_WEB_TRAILERS_BIT);
722        frame.put_u32(len as u32);
723        frame.put_slice(&trailers_bytes[..]);
724
725        let map = decode_trailers_frame(frame.freeze()).unwrap().unwrap();
726
727        assert_eq!(map.get("grpc-status").unwrap(), "0");
728        assert_eq!(
729            map.get("grpc-message").unwrap(),
730            "error: something: went wrong"
731        );
732    }
733}