1use 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
39const 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
66const 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#[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 #[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 if self.buf.is_empty() || self.buf.len() < 4 {
171 return Ok(None);
172 }
173
174 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 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 => {} Some(Err(e)) => return Poll::Ready(Some(Err(e))),
312 };
313
314 let buf = me.as_mut().project().decoded;
317
318 return match find_trailers(&buf[..])? {
319 FindTrailers::Trailer(len) => {
320 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
403fn 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 b == &b'\r' && buf.get(i + 1) == Some(&b'\n') {
431 let trailer = temp_buf.copy_to_bytes(i - cursor_pos);
433 cursor_pos = i + 2;
435 trailers.push(trailer);
436 if temp_buf.has_remaining() {
437 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 let value = &value[1..]; 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
488fn find_trailers(buf: &[u8]) -> Result<FindTrailers, Status> {
493 let mut len = 0;
494 let mut temp_buf = buf;
495
496 loop {
497 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 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 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 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 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}