Weird behavior with rustls

Hi, currently I'm trying to implement a https low-level request with windows-rs crate for dns lookup, socket2 crate for low-level socket connection and rustls to create a TLS handshake stream, all of this on Windows 10, version 22H2. The problem I'm facing is that at the moment of reading all the bytes to a buffer, the program ends with an unexpected windows error:

Error: Error { code: HRESULT(0x8000FFFF), message: "Catastrophic failure" }

So I would like to know, what am I doing wrong? That error doesn't says much

It should be noted that I don't have that problem making a request without TLS, everything works perfectly, even with another TLS crate works well, such as native-tls

The code:

Rust version: 1.82.0

[dependencies]
rustls = "0.23.2"
webpki-roots = "0.26.7"
socket2 = "0.5.8"

[dependencies.windows]
version = "0.57"
features = ["Win32_Networking_WinSock", "Win32_Foundation"]
use rustls::pki_types::ServerName;
use socket2::{Domain, Protocol, Socket, Type};
use std::io::{BufReader, Write};
use std::mem;
use std::net::ToSocketAddrs;
use std::slice::from_raw_parts;
use std::sync::Arc;
use std::time::Duration;
use windows::core::*;
use windows::Win32::Networking::WinSock::{
  GetAddrInfoW, WSAAddressToStringW, WSAStartup, ADDRINFOW, AF_UNSPEC,
  IPPROTO_TCP, SOCK_STREAM, WSADATA,
};
use std::io::Read;

fn main() -> windows::core::Result<()> {
  let mut hints: ADDRINFOW = unsafe { mem::zeroed() };
  hints.ai_family = AF_UNSPEC.0 as i32;
  hints.ai_socktype = SOCK_STREAM.0;
  hints.ai_protocol = IPPROTO_TCP.0;

  let mut wsdata: WSADATA = unsafe { mem::zeroed() };

  unsafe {
    let code = WSAStartup(514, &mut wsdata);
    if code != 0 {
      eprintln!("{:?}", code);
    }
  }

  let mut res = std::ptr::null_mut();

  unsafe {
    let code =
      GetAddrInfoW(w!("google.com"), w!("443"), Some(&hints), &mut res);
    if code != 0 {
      eprintln!("{:?}", code);
    }
  }

  let addr = unsafe {
    let mut cb_buffer = 257_u32;
    let mut buffer = Vec::<u16>::with_capacity(cb_buffer as usize);
    let lp_buffer = PWSTR(buffer.as_mut_ptr());

    WSAAddressToStringW(
      (*res).ai_addr,
      (*res).ai_addrlen as u32,
      None,
      lp_buffer,
      &mut cb_buffer,
    );

    let buffer = { from_raw_parts(lp_buffer.0, cb_buffer as usize - 1) };

    let addr = String::from_utf16_lossy(buffer);

    println!("{}", addr);

    addr
  };
  let mut socket =
    Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP))?;
  let addr = addr.to_socket_addrs()?.collect::<Vec<_>>()[0];
  socket.connect_timeout(&addr.into(), Duration::from_secs(10))?;

  let root_store = rustls::RootCertStore {
    roots: webpki_roots::TLS_SERVER_ROOTS.into(),
  };
  let mut config = rustls::ClientConfig::builder()
    .with_root_certificates(root_store)
    .with_no_client_auth();

  config.key_log = Arc::new(rustls::KeyLogFile::new());

  let server_name = ServerName::DnsName("wwww.google.com".try_into().unwrap());
  let mut sess =
    rustls::ClientConnection::new(Arc::new(config), server_name).unwrap();
    sess.send_close_notify();
  let mut tls = rustls::Stream::new(&mut sess, &mut socket);

  let _ = tls.write(
    concat!(
      "GET / HTTP/1.1\r\n",
      "Host: www.google.com\r\n",
      "Connection: close\r\n",
      "Accept-Encoding: identity\r\n",
      "\r\n"
    )
    .as_bytes(),
  )?;

  let mut reader = BufReader::new(tls);
  let mut buffer = Vec::new();
  reader.read_to_end(&mut buffer)?;

  println!("{}", String::from_utf8_lossy(&buffer));

  Ok(())
}

The first problem I hit is that this code resolves google.com with AF_UNSPEC, which returns an IPv6 on my network, and later fails to create the socket with Domain::IPV4. Changing the resolver to use AF_INET fixed that and then I was able to reproduce the reported error.

The tls.write() call is returning Err(Kind(UnexpectedEof)), and that gets converted into "Catastrophic failure" by the ? operator. The EOF is noted as expected in some cases: rustls Unexpected EOF

It seems that was the problem. EOF wasn't handled correctly, now the "catastrophic failure" makes sense.

Making those changes it works perfectly, (notice the removed sess.send_close_notify(); and just leave the connection closed on the headers with some extra for body compression)

let mut sess = ClientConnection::new(Arc::new(config), server_name).unwrap();
let mut tls = Stream::new(&mut sess, &mut socket);

let _ = tls.write(
  concat!(
    "GET / HTTP/1.1\r\n",
    "Host: www.google.com\r\n",
    "Connection: close\r\n",
    "Accept-Encoding: compress\r\n",
    "Accept: */*\r\n",
    "\r\n",
  )
  .as_bytes(),
)?;

let mut reader = BufReader::new(tls);
let mut buffer = Vec::new();

while reader.read_until(b'\n', &mut buffer).is_ok() {
  let line = String::from_utf8_lossy(&buffer);
  println!("{}", line);

  buffer.clear();
}

Thanks for your help.