Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ae0d082
perf(http3): eliminate allocations with zero-copy `DatagramPayload``
larseggert Sep 25, 2025
8aaa3c2
Minimize diff
larseggert Sep 25, 2025
013518b
Update neqo-http3/src/features/extended_connect/session.rs
larseggert Sep 25, 2025
0a53834
Update neqo-http3/src/lib.rs
larseggert Sep 25, 2025
1776fe8
Fixes
larseggert Sep 25, 2025
af45985
Update neqo-http3/src/lib.rs
larseggert Sep 25, 2025
6cebdba
Use Bytes
larseggert Sep 30, 2025
86bfd5b
Merge branch 'main' into fix-2854-v2
larseggert Sep 30, 2025
618f3c0
Update neqo-http3/src/lib.rs
larseggert Sep 30, 2025
7651468
Update neqo-http3/src/features/extended_connect/session.rs
larseggert Sep 30, 2025
1bdaaae
fmt
larseggert Sep 30, 2025
f1cb050
Update neqo-http3/src/features/extended_connect/session.rs
larseggert Sep 30, 2025
906b4dd
clippy
larseggert Sep 30, 2025
d6b102f
Merge branch 'fix-2854-v2' of github.com:larseggert/neqo into fix-285…
larseggert Sep 30, 2025
bc0f298
import
larseggert Sep 30, 2025
54b031f
Update neqo-http3/src/features/extended_connect/session.rs
larseggert Oct 7, 2025
d6fe04b
No more Bytes dep
larseggert Oct 8, 2025
29c4633
Update neqo-common/src/bytes.rs
larseggert Oct 8, 2025
8b28568
Comments from @mxinden
larseggert Oct 9, 2025
f118659
Merge branch 'fix-2854-v2' of github.com:larseggert/neqo into fix-285…
larseggert Oct 9, 2025
398eaca
Merge branch 'main' into fix-2854-v2
larseggert Oct 9, 2025
5dd98cf
Final fixes
larseggert Oct 10, 2025
5272e0a
Fixes
larseggert Oct 13, 2025
a04ce1e
Update neqo-common/src/bytes.rs
larseggert Oct 13, 2025
586f50d
impl From<Vec<u8>> for Bytes
larseggert Oct 13, 2025
e795ac1
Merge branch 'main' into fix-2854-v2
larseggert Oct 13, 2025
6c9fe27
feat(common/datagram): support Bytes as data store
mxinden Oct 13, 2025
04d26ed
Merge pull request #38 from mxinden/fix-2854-v2
larseggert Oct 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ license = "MIT OR Apache-2.0"
rust-version = "1.81.0"

[workspace.dependencies]
bytes = { version = "1.0", default-features = false }
enum-map = { version = "2.7", default-features = false }
enumset = { version = "1.1", default-features = false }
hex = { version = "0.4", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions neqo-http3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ autobenches = false
workspace = true

[dependencies]
bytes = { workspace = true }
enumset = { workspace = true }
log = { workspace = true }
neqo-common = { path = "./../neqo-common" }
Expand Down
10 changes: 5 additions & 5 deletions neqo-http3/src/client_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use crate::{
connection::Http3State,
features::extended_connect::{self, ExtendedConnectEvents, ExtendedConnectType},
settings::HSettingType,
CloseType, Error, Http3StreamInfo, HttpRecvStreamEvents, PushId, RecvStreamEvents, Res,
SendStreamEvents,
CloseType, DatagramPayload, Error, Http3StreamInfo, HttpRecvStreamEvents, PushId,
RecvStreamEvents, Res, SendStreamEvents,
};

#[derive(Debug, PartialEq, Eq, Clone)]
Expand All @@ -40,7 +40,7 @@ pub enum WebTransportEvent {
},
Datagram {
session_id: StreamId,
datagram: Vec<u8>,
datagram: DatagramPayload,
},
}

Expand All @@ -62,7 +62,7 @@ pub enum ConnectUdpEvent {
},
Datagram {
session_id: StreamId,
datagram: Vec<u8>,
datagram: DatagramPayload,
},
}

Expand Down Expand Up @@ -283,7 +283,7 @@ impl ExtendedConnectEvents for Http3ClientEvents {
fn new_datagram(
&self,
session_id: StreamId,
datagram: Vec<u8>,
datagram: DatagramPayload,
connect_type: ExtendedConnectType,
) {
let event = match connect_type {
Expand Down
18 changes: 12 additions & 6 deletions neqo-http3/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,18 +660,24 @@ impl Http3Connection {
}
}

pub(crate) fn handle_datagram(&mut self, datagram: &[u8]) {
let mut decoder = Decoder::new(datagram);
let Some(stream) = decoder
.decode_varint()
.and_then(|id| self.recv_streams.get_mut(&StreamId::from(id * 4)))
pub(crate) fn handle_datagram(&mut self, datagram: Vec<u8>) {
let mut decoder = Decoder::new(&datagram);
let Some(id) = decoder.decode_varint() else {
qdebug!("[{self}] handle_datagram: failed to decode session ID");
return;
};
let varint_len = decoder.offset();

let Some(stream) = self
.recv_streams
.get_mut(&StreamId::from(id * 4))
.and_then(|s| s.extended_connect_session())
else {
qdebug!("[{self}] handle_datagram for unknown extended connect session");
return;
};

stream.borrow_mut().datagram(decoder.as_ref());
stream.borrow_mut().datagram(datagram, varint_len);
}

fn check_stream_exists(&self, stream_type: Http3StreamType) -> Res<()> {
Expand Down
2 changes: 1 addition & 1 deletion neqo-http3/src/connection_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,7 @@ impl Http3Client {
}
}
ConnectionEvent::Datagram(dgram) => {
self.base_handler.handle_datagram(&dgram);
self.base_handler.handle_datagram(dgram);
}
ConnectionEvent::SendStreamComplete { .. }
| ConnectionEvent::OutgoingDatagramOutcome { .. }
Expand Down
2 changes: 1 addition & 1 deletion neqo-http3/src/connection_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ impl Http3ServerHandler {
s.stream_writable();
}
}
ConnectionEvent::Datagram(dgram) => self.base_handler.handle_datagram(&dgram),
ConnectionEvent::Datagram(dgram) => self.base_handler.handle_datagram(dgram),
ConnectionEvent::AuthenticationNeeded
| ConnectionEvent::EchFallbackAuthenticationNeeded { .. }
| ConnectionEvent::ZeroRttRejected
Expand Down
2 changes: 1 addition & 1 deletion neqo-http3/src/features/extended_connect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub(crate) trait ExtendedConnectEvents: Debug {
fn new_datagram(
&self,
session_id: StreamId,
datagram: Vec<u8>,
datagram: crate::DatagramPayload,
connect_type: ExtendedConnectType,
);
}
Expand Down
22 changes: 15 additions & 7 deletions neqo-http3/src/features/extended_connect/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,20 +398,28 @@ impl Session {
Ok(())
}

pub(crate) fn datagram(&self, datagram: &[u8]) {
pub(crate) fn datagram(&self, datagram: Vec<u8>, payload_offset: usize) {
Comment thread
larseggert marked this conversation as resolved.
Outdated
if self.state != State::Active {
qdebug!("[{self}]: received datagram on {:?} session.", self.state);
return;
}
let datagram = match self.protocol.dgram_context_id(datagram) {
Ok(datagram) => datagram,

// Validate the payload portion based on protocol requirements
match self.protocol.dgram_context_id(&datagram[payload_offset..]) {
Ok(slice) => {
// Calculate total offset: session_id varint + context_id (if any)
// If the returned slice is shorter than the input, a context identifier is present.
Comment thread
larseggert marked this conversation as resolved.
Outdated
let context_offset = usize::from(slice.len() != datagram[payload_offset..].len());
Comment thread
larseggert marked this conversation as resolved.
Outdated
Comment thread
larseggert marked this conversation as resolved.
Outdated
Comment thread
larseggert marked this conversation as resolved.
Outdated
let total_offset = payload_offset + context_offset;
let payload = crate::DatagramPayload::new(datagram, total_offset);

self.events
.new_datagram(self.id, payload, self.protocol.connect_type());
}
Err(e) => {
qdebug!("[{self}]: received datagram with invalid context identifier: {e}");
return;
}
};
self.events
.new_datagram(self.id, datagram.to_vec(), self.protocol.connect_type());
}
}

fn has_data_to_send(&self) -> bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ impl WtTest {
Http3ClientEvent::WebTransport(WebTransportEvent::Datagram {
session_id,
datagram
}) if session_id == expected_stream_id && datagram == expected_dgram
}) if session_id == expected_stream_id && datagram.as_ref() == expected_dgram
)
};
assert!(self.client.events().any(wt_datagram_event));
Expand All @@ -631,7 +631,7 @@ impl WtTest {
Http3ServerEvent::WebTransport(WebTransportServerEvent::Datagram {
session,
datagram
}) if session.stream_id() == expected_session.stream_id() && datagram == expected_dgram
}) if session.stream_id() == expected_session.stream_id() && datagram.as_ref() == expected_dgram
)
};
assert!(self.server.events().any(wt_datagram_event));
Expand Down
44 changes: 44 additions & 0 deletions neqo-http3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ mod stream_type_reader;
use std::{cell::RefCell, fmt::Debug, rc::Rc, time::Instant};

use buffered_send_stream::BufferedStream;
use bytes::Bytes;
pub use client_events::{ConnectUdpEvent, Http3ClientEvent, WebTransportEvent};
pub use conn_params::Http3Parameters;
pub use connection::{Http3State, SessionAcceptAction};
Expand All @@ -184,6 +185,49 @@ use thiserror::Error;

use crate::{features::extended_connect, priority::PriorityHandler};

#[derive(Debug, Clone, Eq, PartialEq)]
Comment thread
larseggert marked this conversation as resolved.
Outdated
pub struct DatagramPayload(Bytes);

impl DatagramPayload {
/// # Panics
///
/// If the payload offset lies outside the data.
#[must_use]
pub fn new(data: Vec<u8>, offset: usize) -> Self {
assert!(offset <= data.len());
Self(Bytes::from(data).slice(offset..))
Comment thread
larseggert marked this conversation as resolved.
Outdated
}

#[must_use]
pub const fn payload(&self) -> &Bytes {
&self.0
}
}

impl AsRef<[u8]> for DatagramPayload {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

impl From<DatagramPayload> for Bytes {
fn from(payload: DatagramPayload) -> Self {
payload.0
}
}

impl<const N: usize> PartialEq<[u8; N]> for DatagramPayload {
fn eq(&self, other: &[u8; N]) -> bool {
self.0 == other.as_slice()
}
}

impl PartialEq<&[u8]> for DatagramPayload {
fn eq(&self, other: &&[u8]) -> bool {
Comment thread
larseggert marked this conversation as resolved.
Outdated
self.0 == *other
Comment thread
larseggert marked this conversation as resolved.
Outdated
}
}

type Res<T> = Result<T, Error>;

#[derive(Clone, Debug, PartialEq, Eq, Error)]
Expand Down
6 changes: 3 additions & 3 deletions neqo-http3/src/server_connection_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub enum WebTransportEvent {
NewStream(Http3StreamInfo),
Datagram {
session_id: StreamId,
datagram: Vec<u8>,
datagram: crate::DatagramPayload,
},
}

Expand All @@ -81,7 +81,7 @@ pub enum ConnectUdpEvent {
},
Datagram {
session_id: StreamId,
datagram: Vec<u8>,
datagram: crate::DatagramPayload,
},
}

Expand Down Expand Up @@ -220,7 +220,7 @@ impl ExtendedConnectEvents for Http3ServerConnEvents {
fn new_datagram(
&self,
session_id: StreamId,
datagram: Vec<u8>,
datagram: crate::DatagramPayload,
connect_type: ExtendedConnectType,
) {
let event = match connect_type {
Expand Down
16 changes: 12 additions & 4 deletions neqo-http3/src/server_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ pub enum WebTransportServerEvent {
NewStream(Http3OrWebTransportStream),
Datagram {
session: WebTransportRequest,
datagram: Vec<u8>,
datagram: crate::DatagramPayload,
Comment thread
larseggert marked this conversation as resolved.
Outdated
},
}

Expand All @@ -525,7 +525,7 @@ pub enum ConnectUdpServerEvent {
},
Datagram {
session: ConnectUdpRequest,
datagram: Vec<u8>,
datagram: crate::DatagramPayload,
},
}

Expand Down Expand Up @@ -724,12 +724,20 @@ impl Http3ServerEvents {
));
}

pub(crate) fn webtransport_datagram(&self, session: WebTransportRequest, datagram: Vec<u8>) {
pub(crate) fn webtransport_datagram(
&self,
session: WebTransportRequest,
datagram: crate::DatagramPayload,
) {
self.insert(Http3ServerEvent::WebTransport(
WebTransportServerEvent::Datagram { session, datagram },
));
}
pub(crate) fn connect_udp_datagram(&self, session: ConnectUdpRequest, datagram: Vec<u8>) {
pub(crate) fn connect_udp_datagram(
&self,
session: ConnectUdpRequest,
datagram: crate::DatagramPayload,
) {
self.insert(Http3ServerEvent::ConnectUdp(
ConnectUdpServerEvent::Datagram { session, datagram },
));
Expand Down
5 changes: 3 additions & 2 deletions neqo-http3/tests/connect_udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#![cfg(test)]

use bytes::Bytes;
use neqo_common::{event::Provider as _, header::HeadersExt as _, qinfo, Datagram, Tos};
use neqo_crypto::AuthenticationStatus;
use neqo_http3::{
Expand Down Expand Up @@ -143,7 +144,7 @@ fn exchange_packets_through_proxy(
DEFAULT_ADDR,
DEFAULT_ADDR,
Tos::default(),
datagram,
Bytes::from(datagram),
))
}
_ => None,
Expand Down Expand Up @@ -184,7 +185,7 @@ fn exchange_packets_through_proxy(
DEFAULT_ADDR,
DEFAULT_ADDR,
Tos::default(),
datagram,
Bytes::from(datagram),
))
} else {
None
Expand Down
2 changes: 1 addition & 1 deletion neqo-http3/tests/webtransport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ fn wt_session_ok_and_wt_datagram_in_same_udp_datagram() {
client.events().next(),
Some(Http3ClientEvent::WebTransport(
WebTransportEvent::Datagram{ session_id, datagram }
)) if session_id == wt_server_session.stream_id() && datagram == b"PING",
)) if session_id == wt_server_session.stream_id() && &datagram == b"PING",
),
"Should receive datagram"
);
Expand Down
Loading