hyperium / hyperium/h2

RecvStream::is_end_stream() is never true. RecvStream::data() never returns None

Open
#882 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
1.6k
Forks
382
Avg merge
20h 23m
Merged PRs (30d)
9

Description

When running a slightly modified version of the example (to get it to connect to an easily reproducable real world-endpoint), RecvStream's never stops returning when called and is_end_stream() never returns true.

Example Code

use std::error::Error as StdError;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4};
use std::sync::Arc;

use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, DnsName};
use tokio::net::TcpStream;
use tokio_rustls::{TlsConnector, rustls};

use h2::client;
use http::Request;

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
    let mut root_cert_store = rustls::RootCertStore::empty();

    let (a, b) = root_cert_store.add_parsable_certificates(
        CertificateDer::pem_file_iter("/etc/ssl/cert.pem")
            .expect("Cannot open CA file")
            .map(|result| result.unwrap()),
    );

    let mut config = rustls::ClientConfig::builder()
        .with_root_certificates(root_cert_store)
        .with_no_client_auth();
    config.alpn_protocols = vec![b"h2".to_vec()];

    let domain: DnsName = "google.com".try_into().unwrap();
    let addr: SocketAddrV4 = "142.250.117.139:443".parse().unwrap(); // manual dns check for google.com

    let tcp_stream = TcpStream::connect(addr).await?;

    let tls_stream = TlsConnector::from(Arc::new(config))
        .connect(domain.into(), tcp_stream)
        .await?;

    let (mut client, h2) = client::handshake(tls_stream).await?;

    let request = Request::builder()
        .uri("https://google.com/")
        .body(())
        .unwrap();

    let (response, _) = client.send_request(request.clone(), false).unwrap();

    // Spawn a task to run the conn...
    tokio::spawn(async move {
        if let Err(e) = h2.await {
            println!("GOT ERR={:?}", e);
        }
    });

    let response = response.await?;

    // Get the body
    let mut body = response.into_body();

    let mut i = 0;

    while let Some(chunk) = body.data().await {
        match chunk {
            Ok(data) => println!("Yay data: {data:?}"),
            Err(err) => {
                println!("Boo! {err:?}. {:?}", body.is_end_stream());
            }
        }

        // Safety to prevent this going on forever.
        i += 1;
        if i > 10 {
            break;
        }
    }

    Ok(())
}

Output

Yay data: b"<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>301 Moved</TITLE></HEAD><BODY>\n<H1>301 Moved</H1>\nThe document has moved\n<A HREF=\"https://www.google.com/\">here</A>.\r\n</BODY></HTML>\r\n"
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false
Boo! Error { kind: Reset(StreamId(1), NO_ERROR, Remote) }. false

This is despite confirming via tcpdump while this code is running that the server does not send Reset frames over and over. Connecting to the endpoint via curl shows they set the eos flag on the first frame with html data in it. This never seems to be picked up by h2.

A frame with the end stream flag set should be enough to mark is_end_stream to true, surely, or failing that a real Reset frame with a reason of NO_ERROR should also.

This behaviour began with 0.4.10.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing RecvStream::data() and RecvStream::is_end_stream() from the supplied reproduction, comparing their behavior before and after version 0.4.10. Verify how the received END_STREAM and reset conditions are represented, then confirm that the stream terminates rather than repeatedly returning the same reset error.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.