Skip to main content

hickory_net/
h2.rs

1// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! TLS protocol related components for DNS over HTTPS (DoH)
9
10use core::fmt::Debug;
11use core::future::Future;
12use core::net::SocketAddr;
13use core::pin::Pin;
14use core::str::FromStr;
15use core::task::{Context, Poll};
16use std::io;
17use std::sync::Arc;
18
19use bytes::{Buf, Bytes, BytesMut};
20use futures_util::stream::{Stream, StreamExt};
21use h2::client::SendRequest;
22use http::header::{self, CONTENT_LENGTH};
23use http::{Method, Request};
24use rustls::ClientConfig;
25use rustls::pki_types::ServerName;
26use tokio::time::timeout;
27use tokio_rustls::TlsConnector;
28use tracing::{debug, warn};
29
30use crate::error::NetError;
31use crate::http::{RequestContext, SetHeaders, Version};
32use crate::proto::op::{DnsRequest, DnsResponse};
33use crate::runtime::iocompat::AsyncIoStdAsTokio;
34use crate::runtime::{DnsTcpStream, RuntimeProvider, Spawn};
35use crate::xfer::{CONNECT_TIMEOUT, DnsExchange, DnsRequestSender, DnsResponseStream};
36
37/// A DNS client connection for DNS-over-HTTPS
38#[derive(Clone)]
39#[must_use = "futures do nothing unless polled"]
40pub struct HttpsClientStream {
41    context: Arc<RequestContext>,
42    h2: SendRequest<Bytes>,
43    is_shutdown: bool,
44}
45
46impl HttpsClientStream {
47    /// Constructs a new HttpsClientStreamBuilder with the associated ClientConfig
48    pub fn builder<P: RuntimeProvider>(
49        client_config: Arc<ClientConfig>,
50        provider: P,
51    ) -> HttpsClientStreamBuilder<P> {
52        HttpsClientStreamBuilder {
53            provider,
54            client_config,
55            bind_addr: None,
56            set_headers: None,
57        }
58    }
59}
60
61impl DnsRequestSender for HttpsClientStream {
62    /// This indicates that the HTTP message was successfully sent, and we now have the response.RecvStream
63    ///
64    /// If the request fails, this will return the error, and it should be assumed that the Stream portion of
65    ///   this will have no date.
66    ///
67    /// ```text
68    /// RFC 8484              DNS Queries over HTTPS (DoH)          October 2018
69    ///
70    ///
71    /// 4.2.  The HTTP Response
72    ///
73    ///    The only response type defined in this document is "application/dns-
74    ///    message", but it is possible that other response formats will be
75    ///    defined in the future.  A DoH server MUST be able to process
76    ///    "application/dns-message" request messages.
77    ///
78    ///    Different response media types will provide more or less information
79    ///    from a DNS response.  For example, one response type might include
80    ///    information from the DNS header bytes while another might omit it.
81    ///    The amount and type of information that a media type gives are solely
82    ///    up to the format, which is not defined in this protocol.
83    ///
84    ///    Each DNS request-response pair is mapped to one HTTP exchange.  The
85    ///    responses may be processed and transported in any order using HTTP's
86    ///    multi-streaming functionality (see Section 5 of [RFC7540]).
87    ///
88    ///    Section 5.1 discusses the relationship between DNS and HTTP response
89    ///    caching.
90    ///
91    /// 4.2.1.  Handling DNS and HTTP Errors
92    ///
93    ///    DNS response codes indicate either success or failure for the DNS
94    ///    query.  A successful HTTP response with a 2xx status code (see
95    ///    Section 6.3 of [RFC7231]) is used for any valid DNS response,
96    ///    regardless of the DNS response code.  For example, a successful 2xx
97    ///    HTTP status code is used even with a DNS message whose DNS response
98    ///    code indicates failure, such as SERVFAIL or NXDOMAIN.
99    ///
100    ///    HTTP responses with non-successful HTTP status codes do not contain
101    ///    replies to the original DNS question in the HTTP request.  DoH
102    ///    clients need to use the same semantic processing of non-successful
103    ///    HTTP status codes as other HTTP clients.  This might mean that the
104    ///    DoH client retries the query with the same DoH server, such as if
105    ///    there are authorization failures (HTTP status code 401; see
106    ///    Section 3.1 of [RFC7235]).  It could also mean that the DoH client
107    ///    retries with a different DoH server, such as for unsupported media
108    ///    types (HTTP status code 415; see Section 6.5.13 of [RFC7231]), or
109    ///    where the server cannot generate a representation suitable for the
110    ///    client (HTTP status code 406; see Section 6.5.6 of [RFC7231]), and so
111    ///    on.
112    /// ```
113    fn send_message(&mut self, mut request: DnsRequest) -> DnsResponseStream {
114        if self.is_shutdown {
115            panic!("can not send messages after stream is shutdown")
116        }
117
118        // per the RFC, a zero id allows for the HTTP packet to be cached better
119        request.metadata.id = 0;
120
121        let bytes = match request.to_vec() {
122            Ok(bytes) => bytes,
123            Err(err) => return NetError::from(err).into(),
124        };
125
126        Box::pin(send(
127            self.h2.clone(),
128            Bytes::from(bytes),
129            self.context.clone(),
130        ))
131        .into()
132    }
133
134    fn shutdown(&mut self) {
135        self.is_shutdown = true;
136    }
137
138    fn is_shutdown(&self) -> bool {
139        self.is_shutdown
140    }
141}
142
143impl Stream for HttpsClientStream {
144    type Item = Result<(), NetError>;
145
146    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
147        if self.is_shutdown {
148            return Poll::Ready(None);
149        }
150
151        // just checking if the connection is ok
152        match self.h2.poll_ready(cx) {
153            Poll::Ready(Ok(())) => Poll::Ready(Some(Ok(()))),
154            Poll::Pending => Poll::Pending,
155            Poll::Ready(Err(e)) => Poll::Ready(Some(Err(NetError::from(format!(
156                "h2 stream errored: {e}",
157            ))))),
158        }
159    }
160}
161
162/// A HTTPS connection builder for DNS-over-HTTPS
163#[derive(Clone)]
164pub struct HttpsClientStreamBuilder<P> {
165    provider: P,
166    client_config: Arc<ClientConfig>,
167    bind_addr: Option<SocketAddr>,
168    set_headers: Option<Arc<dyn SetHeaders>>,
169}
170
171impl<P: RuntimeProvider> HttpsClientStreamBuilder<P> {
172    /// Sets the address to connect from.
173    pub fn bind_addr(&mut self, bind_addr: SocketAddr) {
174        self.bind_addr = Some(bind_addr);
175    }
176
177    /// Set the [`SetHeaders`] trait object used to inject dynamic headers into the DoH request
178    pub fn set_headers(&mut self, headers: Arc<dyn SetHeaders>) {
179        self.set_headers.replace(headers);
180    }
181
182    /// Creates a new [`DnsExchange`] wrapping the [`HttpsClientStream`] from this builder
183    pub async fn exchange(
184        self,
185        name_server: SocketAddr,
186        server_name: Arc<str>,
187        path: Arc<str>,
188    ) -> Result<DnsExchange<P>, NetError> {
189        let mut handle = self.provider.create_handle();
190        let stream = self.build(name_server, server_name, path).await?;
191        let (exchange, bg) = DnsExchange::from_stream(stream);
192        handle.spawn_bg(bg);
193        Ok(exchange)
194    }
195
196    /// Creates a new HttpsStream to the specified name_server
197    ///
198    /// # Arguments
199    ///
200    /// * `name_server` - IP and Port for the remote DNS resolver
201    /// * `dns_name` - The DNS name associated with a certificate
202    /// * `http_endpoint` - The HTTP endpoint where the remote DNS resolver provides service, typically `/dns-query`
203    pub fn build(
204        self,
205        name_server: SocketAddr,
206        server_name: Arc<str>,
207        path: Arc<str>,
208    ) -> impl Future<Output = Result<HttpsClientStream, NetError>> + Send + 'static {
209        connect(
210            self.provider.connect_tcp(name_server, self.bind_addr, None),
211            self.client_config,
212            name_server,
213            server_name,
214            path,
215            self.set_headers,
216        )
217    }
218}
219
220/// Creates a new HttpsStream with existing connection
221pub fn connect(
222    tcp: impl Future<Output = Result<impl DnsTcpStream, io::Error>> + Send + 'static,
223    mut client_config: Arc<ClientConfig>,
224    name_server: SocketAddr,
225    server_name: Arc<str>,
226    query_path: Arc<str>,
227    set_headers: Option<Arc<dyn SetHeaders>>,
228) -> impl Future<Output = Result<HttpsClientStream, NetError>> + Send + 'static {
229    // ensure the ALPN protocol is set correctly
230    if client_config.alpn_protocols.is_empty() {
231        let mut client_cfg = (*client_config).clone();
232        client_cfg.alpn_protocols = vec![ALPN_H2.to_vec()];
233
234        client_config = Arc::new(client_cfg);
235    }
236
237    let context = Arc::new(RequestContext {
238        version: Version::Http2,
239        server_name,
240        query_path,
241        set_headers,
242    });
243
244    async move {
245        let tls_server_name = match ServerName::try_from(&*context.server_name) {
246            Ok(dns_name) => dns_name.to_owned(),
247            Err(err) => {
248                return Err(NetError::from(format!(
249                    "bad server name {:?}: {err}",
250                    context.server_name
251                )));
252            }
253        };
254
255        let tcp = tcp.await?;
256        let future = timeout(
257            CONNECT_TIMEOUT,
258            TlsConnector::from(client_config).connect(tls_server_name, AsyncIoStdAsTokio(tcp)),
259        );
260
261        let tls = match future.await {
262            Ok(Ok(tls)) => tls,
263            Ok(Err(err)) => return Err(NetError::from(err)),
264            Err(_) => return Err(NetError::Timeout),
265        };
266
267        let mut handshake = h2::client::Builder::new();
268        handshake.enable_push(false);
269        let (h2, driver) = handshake.handshake(tls).await?;
270
271        debug!("h2 connection established to: {name_server}");
272        tokio::spawn(async {
273            if let Err(e) = driver.await {
274                warn!("h2 connection failed: {e}");
275            }
276        });
277
278        Ok(HttpsClientStream {
279            h2,
280            context,
281            is_shutdown: false,
282        })
283    }
284}
285
286async fn send(
287    h2: SendRequest<Bytes>,
288    message: Bytes,
289    cx: Arc<RequestContext>,
290) -> Result<DnsResponse, NetError> {
291    let mut h2 = match h2.ready().await {
292        Ok(h2) => h2,
293        Err(err) => {
294            // TODO: make specific error
295            return Err(NetError::from(format!("h2 send_request error: {err}")));
296        }
297    };
298
299    // build up the http request
300    let request = cx
301        .build(message.remaining())
302        .map_err(|err| NetError::from(format!("bad http request: {err}")))?;
303
304    debug!("request: {:#?}", request);
305
306    // Send the request
307    let (response_future, mut send_stream) = h2
308        .send_request(request, false)
309        .map_err(|err| NetError::from(format!("h2 send_request error: {err}")))?;
310
311    send_stream
312        .send_data(message, true)
313        .map_err(|e| NetError::from(format!("h2 send_data error: {e}")))?;
314
315    let mut response_stream = response_future
316        .await
317        .map_err(|err| NetError::from(format!("received a stream error: {err}")))?;
318
319    debug!("got response: {:#?}", response_stream);
320
321    // get the length of packet
322    let content_length = response_stream
323        .headers()
324        .get(CONTENT_LENGTH)
325        .map(|v| v.to_str())
326        .transpose()
327        .map_err(|e| NetError::from(format!("bad headers received: {e}")))?
328        .map(usize::from_str)
329        .transpose()
330        .map_err(|e| NetError::from(format!("bad headers received: {e}")))?;
331
332    // TODO: what is a good max here?
333    // clamp(512, 4096) says make sure it is at least 512 bytes, and min 4096 says it is at most 4k
334    // just a little protection from malicious actors.
335    let mut response_bytes =
336        BytesMut::with_capacity(content_length.unwrap_or(512).clamp(512, 4_096));
337
338    while let Some(partial_bytes) = response_stream.body_mut().data().await {
339        let partial_bytes =
340            partial_bytes.map_err(|e| NetError::from(format!("bad http request: {e}")))?;
341
342        debug!("got bytes: {}", partial_bytes.len());
343        response_bytes.extend(partial_bytes);
344
345        // assert the length
346        if let Some(content_length) = content_length {
347            if response_bytes.len() >= content_length {
348                break;
349            }
350        }
351    }
352
353    // assert the length
354    if let Some(content_length) = content_length {
355        if response_bytes.len() != content_length {
356            // TODO: make explicit error type
357            return Err(NetError::from(format!(
358                "expected byte length: {}, got: {}",
359                content_length,
360                response_bytes.len()
361            )));
362        }
363    }
364
365    // Was it a successful request?
366    if !response_stream.status().is_success() {
367        let error_string = String::from_utf8_lossy(response_bytes.as_ref());
368
369        // TODO: make explicit error type
370        return Err(NetError::from(format!(
371            "http unsuccessful code: {}, message: {}",
372            response_stream.status(),
373            error_string
374        )));
375    } else {
376        // verify content type
377        {
378            // in the case that the ContentType is not specified, we assume it's the standard DNS format
379            let content_type = response_stream
380                .headers()
381                .get(header::CONTENT_TYPE)
382                .map(|h| {
383                    h.to_str().map_err(|err| {
384                        // TODO: make explicit error type
385                        NetError::from(format!("ContentType header not a string: {err}"))
386                    })
387                })
388                .unwrap_or(Ok(crate::http::MIME_APPLICATION_DNS))?;
389
390            if content_type != crate::http::MIME_APPLICATION_DNS {
391                return Err(NetError::from(format!(
392                    "ContentType unsupported (must be '{}'): '{}'",
393                    crate::http::MIME_APPLICATION_DNS,
394                    content_type
395                )));
396            }
397        }
398    };
399
400    // and finally convert the bytes into a DNS message
401    DnsResponse::from_buffer(response_bytes.to_vec()).map_err(NetError::from)
402}
403
404/// Given an HTTP request, return a future that will result in the next sequence of bytes.
405///
406/// To allow downstream clients to do something interesting with the lifetime of the bytes, this doesn't
407///   perform a conversion to a Message, only collects all the bytes.
408pub async fn message_from<R>(
409    this_server_name: Option<Arc<str>>,
410    this_server_endpoint: Arc<str>,
411    request: Request<R>,
412) -> Result<BytesMut, NetError>
413where
414    R: Stream<Item = Result<Bytes, h2::Error>> + 'static + Send + Debug + Unpin,
415{
416    debug!("Received request: {:#?}", request);
417
418    let this_server_name = this_server_name.as_deref();
419    match crate::http::verify(
420        Version::Http2,
421        this_server_name,
422        &this_server_endpoint,
423        &request,
424    ) {
425        Ok(_) => (),
426        Err(err) => return Err(err),
427    }
428
429    // attempt to get the content length
430    let mut content_length = None;
431    if let Some(length) = request.headers().get(CONTENT_LENGTH) {
432        let length = usize::from_str(length.to_str()?)?;
433        debug!("got message length: {}", length);
434        content_length = Some(length);
435    }
436
437    match *request.method() {
438        Method::GET => Err(format!("GET unimplemented: {}", request.method()).into()),
439        Method::POST => message_from_post(request.into_body(), content_length).await,
440        _ => Err(format!("bad method: {}", request.method()).into()),
441    }
442}
443
444/// Deserialize the message from a POST message
445pub(crate) async fn message_from_post<R>(
446    mut request_stream: R,
447    length: Option<usize>,
448) -> Result<BytesMut, NetError>
449where
450    R: Stream<Item = Result<Bytes, h2::Error>> + 'static + Send + Debug + Unpin,
451{
452    let mut bytes = BytesMut::with_capacity(length.unwrap_or(0).clamp(512, 4_096));
453
454    loop {
455        match request_stream.next().await {
456            Some(Ok(mut frame)) => bytes.extend_from_slice(&frame.split_off(0)),
457            Some(Err(err)) => return Err(err.into()),
458            None => {
459                return if let Some(length) = length {
460                    // wait until we have all the bytes
461                    if bytes.len() == length {
462                        Ok(bytes)
463                    } else {
464                        Err("not all bytes received".into())
465                    }
466                } else {
467                    Ok(bytes)
468                };
469            }
470        };
471
472        if let Some(length) = length {
473            // wait until we have all the bytes
474            if bytes.len() == length {
475                return Ok(bytes);
476            }
477        }
478    }
479}
480
481const ALPN_H2: &[u8] = b"h2";
482
483#[cfg(test)]
484mod tests {
485    use core::net::SocketAddr;
486
487    use rustls::KeyLogFile;
488    use test_support::subscribe;
489
490    use super::*;
491    use crate::proto::op::{DnsRequestOptions, Edns, Message, Query};
492    use crate::proto::rr::{Name, RData, RecordType};
493    use crate::runtime::TokioRuntimeProvider;
494    use crate::tls::client_config;
495    use crate::xfer::FirstAnswer;
496
497    #[cfg(any(feature = "webpki-roots", feature = "rustls-platform-verifier"))]
498    #[tokio::test]
499    async fn test_https_google() {
500        subscribe();
501
502        let google = SocketAddr::from(([8, 8, 8, 8], 443));
503        let mut request = Message::query();
504        let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
505        request.add_query(query);
506        request.metadata.recursion_desired = true;
507        let mut edns = Edns::new();
508        edns.set_version(0);
509        edns.set_max_payload(1232);
510        request.edns = Some(edns);
511
512        let request = DnsRequest::new(request, DnsRequestOptions::default());
513
514        let mut client_config = client_config_h2();
515        client_config.key_log = Arc::new(KeyLogFile::new());
516
517        let provider = TokioRuntimeProvider::new();
518        let https_builder = HttpsClientStream::builder(Arc::new(client_config), provider);
519        let connect = https_builder.build(google, Arc::from("dns.google"), Arc::from("/dns-query"));
520
521        let mut https = connect.await.expect("https connect failed");
522
523        let response = https
524            .send_message(request)
525            .first_answer()
526            .await
527            .expect("send_message failed");
528
529        assert!(
530            response
531                .answers
532                .iter()
533                .any(|record| matches!(record.data, RData::A(_)))
534        );
535
536        //
537        // assert that the connection works for a second query
538        let mut request = Message::query();
539        let query = Query::query(
540            Name::from_str("www.example.com.").unwrap(),
541            RecordType::AAAA,
542        );
543        request.add_query(query);
544        request.metadata.recursion_desired = true;
545        let mut edns = Edns::new();
546        edns.set_version(0);
547        edns.set_max_payload(1232);
548        request.edns = Some(edns);
549
550        let request = DnsRequest::new(request, DnsRequestOptions::default());
551
552        let response = https
553            .send_message(request.clone())
554            .first_answer()
555            .await
556            .expect("send_message failed");
557
558        assert!(
559            response
560                .answers
561                .iter()
562                .any(|record| matches!(record.data, RData::AAAA(_)))
563        );
564    }
565
566    #[cfg(any(feature = "webpki-roots", feature = "rustls-platform-verifier"))]
567    #[tokio::test]
568    async fn test_https_google_with_pure_ip_address_server() {
569        subscribe();
570
571        let google = SocketAddr::from(([8, 8, 8, 8], 443));
572        let mut request = Message::query();
573        let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
574        request.add_query(query);
575        request.metadata.recursion_desired = true;
576        let mut edns = Edns::new();
577        edns.set_version(0);
578        edns.set_max_payload(1232);
579        request.edns = Some(edns);
580
581        let request = DnsRequest::new(request, DnsRequestOptions::default());
582
583        let mut client_config = client_config_h2();
584        client_config.key_log = Arc::new(KeyLogFile::new());
585
586        let provider = TokioRuntimeProvider::new();
587        let https_builder = HttpsClientStream::builder(Arc::new(client_config), provider);
588        let connect = https_builder.build(
589            google,
590            Arc::from(google.ip().to_string()),
591            Arc::from("/dns-query"),
592        );
593
594        let mut https = connect.await.expect("https connect failed");
595
596        let response = https
597            .send_message(request)
598            .first_answer()
599            .await
600            .expect("send_message failed");
601
602        assert!(
603            response
604                .answers
605                .iter()
606                .any(|record| matches!(record.data, RData::A(_)))
607        );
608
609        //
610        // assert that the connection works for a second query
611        let mut request = Message::query();
612        let query = Query::query(
613            Name::from_str("www.example.com.").unwrap(),
614            RecordType::AAAA,
615        );
616        request.add_query(query);
617        request.metadata.recursion_desired = true;
618        let mut edns = Edns::new();
619        edns.set_version(0);
620        edns.set_max_payload(1232);
621        request.edns = Some(edns);
622
623        let request = DnsRequest::new(request, DnsRequestOptions::default());
624
625        let response = https
626            .send_message(request.clone())
627            .first_answer()
628            .await
629            .expect("send_message failed");
630
631        assert!(
632            response
633                .answers
634                .iter()
635                .any(|record| matches!(record.data, RData::AAAA(_)))
636        );
637    }
638
639    #[cfg(any(feature = "webpki-roots", feature = "rustls-platform-verifier"))]
640    #[tokio::test]
641    #[ignore = "cloudflare has been unreliable as a public test service"]
642    async fn test_https_cloudflare() {
643        subscribe();
644
645        let cloudflare = SocketAddr::from(([1, 1, 1, 1], 443));
646        let mut request = Message::query();
647        let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
648        request.add_query(query);
649        request.metadata.recursion_desired = true;
650        let mut edns = Edns::new();
651        edns.set_version(0);
652        edns.set_max_payload(1232);
653        request.edns = Some(edns);
654
655        let request = DnsRequest::new(request, DnsRequestOptions::default());
656
657        let client_config = client_config_h2();
658        let provider = TokioRuntimeProvider::new();
659        let https_builder = HttpsClientStream::builder(Arc::new(client_config), provider);
660        let connect = https_builder.build(
661            cloudflare,
662            Arc::from("cloudflare-dns.com"),
663            Arc::from("/dns-query"),
664        );
665
666        let mut https = connect.await.expect("https connect failed");
667
668        let response = https
669            .send_message(request)
670            .first_answer()
671            .await
672            .expect("send_message failed");
673
674        assert!(
675            response
676                .answers
677                .iter()
678                .any(|record| matches!(record.data, RData::A(_)))
679        );
680
681        //
682        // assert that the connection works for a second query
683        let mut request = Message::query();
684        let query = Query::query(
685            Name::from_str("www.example.com.").unwrap(),
686            RecordType::AAAA,
687        );
688        request.add_query(query);
689        request.metadata.recursion_desired = true;
690        let mut edns = Edns::new();
691        edns.set_version(0);
692        edns.set_max_payload(1232);
693        request.edns = Some(edns);
694
695        let request = DnsRequest::new(request, DnsRequestOptions::default());
696
697        let response = https
698            .send_message(request)
699            .first_answer()
700            .await
701            .expect("send_message failed");
702
703        assert!(
704            response
705                .answers
706                .iter()
707                .any(|record| matches!(record.data, RData::AAAA(_)))
708        );
709    }
710
711    fn client_config_h2() -> ClientConfig {
712        let mut config = client_config().unwrap();
713        config.alpn_protocols = vec![ALPN_H2.to_vec()];
714        config
715    }
716
717    #[tokio::test]
718    async fn test_from_post() {
719        subscribe();
720        let message = Message::query();
721        let msg_bytes = message.to_vec().unwrap();
722        let len = msg_bytes.len();
723        let stream = TestBytesStream(vec![Ok(Bytes::from(msg_bytes))]);
724        let cx = RequestContext {
725            version: Version::Http2,
726            server_name: Arc::from("ns.example.com"),
727            query_path: Arc::from("/dns-query"),
728            set_headers: None,
729        };
730
731        let request = cx.build(len).unwrap();
732        let request = request.map(|()| stream);
733
734        let bytes = message_from(
735            Some(Arc::from("ns.example.com")),
736            "/dns-query".into(),
737            request,
738        )
739        .await
740        .unwrap();
741
742        let msg_from_post = Message::from_vec(bytes.as_ref()).expect("bytes failed");
743        assert_eq!(message, msg_from_post);
744    }
745
746    #[derive(Debug)]
747    struct TestBytesStream(Vec<Result<Bytes, h2::Error>>);
748
749    impl Stream for TestBytesStream {
750        type Item = Result<Bytes, h2::Error>;
751
752        fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
753            match self.0.pop() {
754                Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes))),
755                Some(Err(err)) => Poll::Ready(Some(Err(err))),
756                None => Poll::Ready(None),
757            }
758        }
759    }
760}