#[macro_use]
extern crate log;
use quiche::h3::NameValue;
use ring::rand::*;
const MAX_DATAGRAM_SIZE: usize = 1350;
fn main() {
let mut buf = [0; 65535];
let mut out = [0; MAX_DATAGRAM_SIZE];
let mut args = std::env::args();
let cmd = &args.next().unwrap();
if args.len() != 1 {
println!("Usage: {cmd} URL");
println!("\nSee tools/apps/ for more complete implementations.");
return;
}
let url = url::Url::parse(&args.next().unwrap()).unwrap();
let mut poll = mio::Poll::new().unwrap();
let mut events = mio::Events::with_capacity(1024);
let peer_addr = url.socket_addrs(|| None).unwrap()[0];
let bind_addr = match peer_addr {
std::net::SocketAddr::V4(_) => "0.0.0.0:0",
std::net::SocketAddr::V6(_) => "[::]:0",
};
let mut socket =
mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap();
poll.registry()
.register(&mut socket, mio::Token(0), mio::Interest::READABLE)
.unwrap();
let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();
config.verify_peer(false);
config
.set_application_protos(quiche::h3::APPLICATION_PROTOCOL)
.unwrap();
config.set_max_idle_timeout(5000);
config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);
config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);
config.set_initial_max_data(10_000_000);
config.set_initial_max_stream_data_bidi_local(1_000_000);
config.set_initial_max_stream_data_bidi_remote(1_000_000);
config.set_initial_max_stream_data_uni(1_000_000);
config.set_initial_max_streams_bidi(100);
config.set_initial_max_streams_uni(100);
config.set_disable_active_migration(true);
let mut http3_conn = None;
let mut scid = [0; quiche::MAX_CONN_ID_LEN];
SystemRandom::new().fill(&mut scid[..]).unwrap();
let scid = quiche::ConnectionId::from_ref(&scid);
let local_addr = socket.local_addr().unwrap();
let mut conn =
quiche::connect(url.domain(), &scid, local_addr, peer_addr, &mut config)
.unwrap();
info!(
"connecting to {:} from {:} with scid {}",
peer_addr,
socket.local_addr().unwrap(),
hex_dump(&scid)
);
let (write, send_info) = conn.send(&mut out).expect("initial send failed");
while let Err(e) = socket.send_to(&out[..write], send_info.to) {
if e.kind() == std::io::ErrorKind::WouldBlock {
debug!("send() would block");
continue;
}
panic!("send() failed: {e:?}");
}
debug!("written {write}");
let h3_config = quiche::h3::Config::new().unwrap();
let mut path = String::from(url.path());
if let Some(query) = url.query() {
path.push('?');
path.push_str(query);
}
let req = vec![
quiche::h3::Header::new(b":method", b"GET"),
quiche::h3::Header::new(b":scheme", url.scheme().as_bytes()),
quiche::h3::Header::new(
b":authority",
url.host_str().unwrap().as_bytes(),
),
quiche::h3::Header::new(b":path", path.as_bytes()),
quiche::h3::Header::new(b"user-agent", b"quiche"),
];
let req_start = std::time::Instant::now();
let mut req_sent = false;
loop {
poll.poll(&mut events, conn.timeout()).unwrap();
'read: loop {
if events.is_empty() {
debug!("timed out");
conn.on_timeout();
break 'read;
}
let (len, from) = match socket.recv_from(&mut buf) {
Ok(v) => v,
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock {
debug!("recv() would block");
break 'read;
}
panic!("recv() failed: {e:?}");
},
};
debug!("got {len} bytes");
let recv_info = quiche::RecvInfo {
to: local_addr,
from,
};
let read = match conn.recv(&mut buf[..len], recv_info) {
Ok(v) => v,
Err(e) => {
error!("recv failed: {e:?}");
continue 'read;
},
};
debug!("processed {read} bytes");
}
debug!("done reading");
if conn.is_closed() {
info!("connection closed, {:?}", conn.stats());
break;
}
if conn.is_established() && http3_conn.is_none() {
http3_conn = Some(
quiche::h3::Connection::with_transport(&mut conn, &h3_config)
.expect("Unable to create HTTP/3 connection, check the server's uni stream limit and window size"),
);
}
if let Some(h3_conn) = &mut http3_conn {
if !req_sent {
info!("sending HTTP request {req:?}");
h3_conn.send_request(&mut conn, &req, true).unwrap();
req_sent = true;
}
}
if let Some(http3_conn) = &mut http3_conn {
loop {
match http3_conn.poll(&mut conn) {
Ok((stream_id, quiche::h3::Event::Headers { list, .. })) => {
info!(
"got response headers {:?} on stream id {}",
hdrs_to_strings(&list),
stream_id
);
},
Ok((stream_id, quiche::h3::Event::Data)) => {
while let Ok(read) =
http3_conn.recv_body(&mut conn, stream_id, &mut buf)
{
debug!(
"got {read} bytes of response data on stream {stream_id}"
);
print!("{}", unsafe {
std::str::from_utf8_unchecked(&buf[..read])
});
}
},
Ok((_stream_id, quiche::h3::Event::Finished)) => {
info!(
"response received in {:?}, closing...",
req_start.elapsed()
);
conn.close(true, 0x100, b"kthxbye").unwrap();
},
Ok((_stream_id, quiche::h3::Event::Reset(e))) => {
error!("request was reset by peer with {e}, closing...");
conn.close(true, 0x100, b"kthxbye").unwrap();
},
Ok((_, quiche::h3::Event::PriorityUpdate)) => unreachable!(),
Ok((goaway_id, quiche::h3::Event::GoAway)) => {
info!("GOAWAY id={goaway_id}");
},
Err(quiche::h3::Error::Done) => {
break;
},
Err(e) => {
error!("HTTP/3 processing failed: {e:?}");
break;
},
}
}
}
loop {
let (write, send_info) = match conn.send(&mut out) {
Ok(v) => v,
Err(quiche::Error::Done) => {
debug!("done writing");
break;
},
Err(e) => {
error!("send failed: {e:?}");
conn.close(false, 0x1, b"fail").ok();
break;
},
};
if let Err(e) = socket.send_to(&out[..write], send_info.to) {
if e.kind() == std::io::ErrorKind::WouldBlock {
debug!("send() would block");
break;
}
panic!("send() failed: {e:?}");
}
debug!("written {write}");
}
if conn.is_closed() {
info!("connection closed, {:?}", conn.stats());
break;
}
}
}
fn hex_dump(buf: &[u8]) -> String {
let vec: Vec<String> = buf.iter().map(|b| format!("{b:02x}")).collect();
vec.join("")
}
pub fn hdrs_to_strings(hdrs: &[quiche::h3::Header]) -> Vec<(String, String)> {
hdrs.iter()
.map(|h| {
let name = String::from_utf8_lossy(h.name()).to_string();
let value = String::from_utf8_lossy(h.value()).to_string();
(name, value)
})
.collect()
}