#[non_exhaustive]pub struct TransportProfile {Show 18 fields
pub congestion: Option<Congestion>,
pub initial_rtt: Option<Duration>,
pub receive_window: Option<u64>,
pub stream_receive_window: Option<u64>,
pub send_window: Option<u64>,
pub max_concurrent_uni_streams: Option<u64>,
pub max_concurrent_bidi_streams: Option<u64>,
pub max_idle_timeout: Option<Duration>,
pub keep_alive_interval: Option<Duration>,
pub packet_threshold: Option<u32>,
pub time_threshold: Option<f32>,
pub persistent_congestion_threshold: Option<u32>,
pub ack_frequency: Option<AckFrequency>,
pub initial_mtu: Option<u16>,
pub min_mtu: Option<u16>,
pub mtu_discovery: Option<MtuDiscovery>,
pub send_fairness: Option<bool>,
pub datagram_receive_buffer: Option<DatagramBuffer>,
}Expand description
A per-leg description of QUIC transport parameters.
Every field is optional and every None means leave this alone. There
is no field whose None is a value: a profile that sets three knobs is a
profile about three knobs, and the other thirteen belong to whoever built
the quinn::TransportConfig it is applied to.
#[non_exhaustive] with a Default, as the shaping configs are:
the attribute lets a later release add a seventeenth knob without a
break, and outside this crate it makes both struct-expression and
..Default::default() syntax illegal, so the Default is what leaves a
construction path open at all. The documented way to build one is
therefore TransportProfile::default() followed by field assignment,
which is what the example does — it is the only path the attribute
leaves, so it is the one worth proving.
use std::time::Duration;
use moqtap_proxy::transport::{Congestion, MtuDiscovery, TransportProfile};
let mut profile = TransportProfile::default();
profile.congestion = Some(Congestion::Bbr);
profile.initial_rtt = Some(Duration::from_millis(40));
profile.receive_window = Some(8 * 1024 * 1024);
profile.initial_mtu = Some(1350);
profile.mtu_discovery = Some(MtuDiscovery::Off);
profile.validate()?;
let config = profile.into_config()?;Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.congestion: Option<Congestion>Which congestion controller to install.
Each variant installs that controller’s own default configuration. The controllers behave very differently on a lossy path — BBR keeps sending through loss that collapses a Cubic sender — so a run comparing two of them wants this named explicitly rather than inherited from whatever quinn’s default happens to be that release.
initial_rtt: Option<Duration>The RTT to assume before a measurement exists.
It decides the first retransmission timeout, so on a long path a default that is far too low spends the opening exchange retransmitting packets that were merely in flight.
receive_window: Option<u64>Connection-wide flow-control window, in bytes.
The cap on unacknowledged data across all streams. Too small for the bandwidth-delay product and the sender stalls on flow control at a throughput that has nothing to do with the congestion controller under test.
stream_receive_window: Option<u64>Per-stream flow-control window, in bytes.
Held below TransportProfile::receive_window so that one slow
reader cannot monopolise the connection’s receive buffers.
send_window: Option<u64>Cap on unacknowledged outgoing data, in bytes.
The send-side counterpart of TransportProfile::receive_window,
and the one window quinn takes as a plain u64 rather than a QUIC
varint, so no range check applies to it.
max_concurrent_uni_streams: Option<u64>How many unidirectional streams the peer may have open at once.
MoQT carries media on unidirectional streams, so this is the ceiling on concurrent subgroups; a value below what a subscription needs shows up as senders blocked waiting for a stream credit rather than as anything resembling congestion.
max_concurrent_bidi_streams: Option<u64>How many bidirectional streams the peer may have open at once.
What this starves depends on the draft, and a profile is installed on a leg before any version has been negotiated, so it cannot depend on which. Three answers over the range, each taken from that draft’s own Section 3.3:
- Drafts 07 through 15 specify a single use of bidirectional streams, the control stream (draft-15 Section 3.3). A cap of zero there does not impair a session, it prevents one.
- Draft-16 specifies two, the control stream and SUBSCRIBE_NAMESPACE (draft-16 Section 3.3). It is the one draft on which a cap can starve something and leave the session running.
- Drafts 17 through 19 moved the control plane onto a pair of
unidirectional streams and give bidirectional streams to
requests alone — six message types on draft-17, seven on drafts
18 and 19 (draft-17 Section 3.3). A cap there is the request-side
counterpart of
TransportProfile::max_concurrent_uni_streamson the media side, and it is the case this knob is carried for.
So a value written without knowing which draft the run will negotiate is a foot-gun rather than a setting, and that is a reason to say so here rather than a reason to leave the field out.
max_idle_timeout: Option<Duration>How long a connection may sit idle before it is closed.
The effective timeout is the smaller of this and the peer’s own, so setting it here only ever shortens the wait.
keep_alive_interval: Option<Duration>How often to send a packet purely to keep the connection alive.
Must be strictly below TransportProfile::max_idle_timeout when
both are set — see
TransportProfileError::KeepAliveNotBelowIdle.
packet_threshold: Option<u32>How many packets may be acknowledged after a packet before it is declared lost.
The reordering tolerance of loss detection. Lowering it makes a reordering path look like a lossy one, which is occasionally the point and is otherwise a way to misread a run.
time_threshold: Option<f32>Loss-detection time threshold, as a multiple of the round-trip estimate.
Must be finite and greater than 1.0: it is a multiplier on the
RTT, so a value at or below one declares packets lost before an
acknowledgement could have arrived.
persistent_congestion_threshold: Option<u32>How many consecutive probe timeouts amount to persistent congestion.
quinn multiplies the probe timeout by this to get the window it looks for entirely-lost packets in, and a path judged persistently congested has its congestion window collapsed to the minimum. Lowering it makes a sender give up on a bad path sooner.
Nothing in this crate demonstrates its effect. Persistent
congestion is entered on a duration of losses, and no test here
asserts a duration, so a gate for this field could assert only that
a setter accepted the value. It ships under that stated limit,
which is a different thing from a knob accepted and ignored — the
same footing as TransportProfile::ack_frequency.
ack_frequency: Option<AckFrequency>Acknowledgement frequency to request of the peer.
None leaves quinn’s default, which is not to negotiate the
extension at all. Some asks for it, with the knobs in
AckFrequency.
initial_mtu: Option<u16>The packet size to start with, in bytes.
Must be at least 1200 — see
TransportProfileError::MtuBelowFloor.
min_mtu: Option<u16>The packet size never to go below, in bytes, after black-hole detection has lowered the discovered MTU.
Must be at least 1200, and no larger than
TransportProfile::initial_mtu.
mtu_discovery: Option<MtuDiscovery>Whether to search for a larger path MTU, and how far.
quinn’s default is to search, so None here means keep searching
and MtuDiscovery::Off is the only way to stop it. The two are
deliberately distinguishable.
send_fairness: Option<bool>Whether to share send capacity fairly between streams rather than draining them in priority order.
It changes which subgroup arrives first when several are ready at once, which is visible in delivery order and not in any counter.
datagram_receive_buffer: Option<DatagramBuffer>How much room to give incoming QUIC datagrams, in bytes.
DatagramBuffer::Disabled refuses datagrams outright, which is a
different thing from leaving the field unset — see
DatagramBuffer for why that distinction has a type rather than a
nested Option.
Implementations§
Source§impl TransportProfile
impl TransportProfile
Sourcepub fn validate(&self) -> Result<(), TransportProfileError>
pub fn validate(&self) -> Result<(), TransportProfileError>
Everything wrong with this profile, before any connection exists.
Returns the first failure, checked in field-declaration order, as the other validators in this workspace do: a profile with two mistakes reports the earlier field, and fixing it reveals the second. The order is fixed so the answer is repeatable rather than dependent on which check happened to be written first.
Two deliberate departures from strict declaration order, both because the more useful thing to be told comes first:
TransportProfileError::KeepAliveNotBelowIdleis checked atkeep_alive_interval, the later of the two fields it compares, so that the earlier field has already had its own range check.- Both MTU floors are checked before
TransportProfileError::MtuInverted. A value below the floor is a single-field fault with a single-field fix, and once both values are legal the inversion may not exist any more.
§What is not checked, and why
The idle timeout has no error variant of its own. IdleTimeout
converts from a Duration through as_millis against the same
varint ceiling as the windows, which puts the limit around 146
million years — no configuration file reaches it, and an error a
reader can never meet is worse than no error at all. The conversion
is nevertheless fallible in Rust, because Duration::from_secs(u64::MAX)
exists, so it is folded into
TransportProfileError::VarIntRange under the field name
max_idle_timeout. That keeps the path free of a panic without
adding a rule to the list an author has to read.
Sourcepub fn apply_to(
&self,
tc: &mut TransportConfig,
) -> Result<(), TransportProfileError>
pub fn apply_to( &self, tc: &mut TransportConfig, ) -> Result<(), TransportProfileError>
Write this profile’s fields into tc, leaving every field it does not
set untouched.
§Why this takes &mut and returns nothing
quinn::TransportConfig has exactly three impls — the inherent setters,
Default and Debug. There is no Clone, and every field is private
with no getter. So there is no way to write fn apply(&self, base: &TransportConfig) -> TransportConfig: the function cannot copy base
and cannot read a single value out of it, so the only thing it could
return is a fresh default with the caller’s configuration silently
thrown away. Mutating in place is the one shape in which leaves the
rest untouched is true rather than merely claimed.
The trap this leaves for whoever maintains it: base.clone()
compiles. &TransportConfig is Clone even though
TransportConfig is not, so the call clones the reference and the
mistake only surfaces at the return, as “TransportConfig does not
implement Clone, so &TransportConfig was cloned instead”. Anyone
who reaches for the by-value signature will meet that message and
should read it as the reason this signature is what it is.
§All or nothing
The first statement is self.validate()?, and that is the whole of
how this method is kept from installing something validate would
have rejected — there is no second list of rules to drift out of step
with the first, and no field-by-field reading to do to check it. It
also means tc is either fully written or not written at all: a
profile that fails returns before the first setter runs, so a caller
who ignores the error is not left with a half-applied config.
The conversions below re-run the fallible steps with ? rather than
unwrapping them. They cannot fail after validate has passed, but
expressing that as a panic would make a future divergence between the
two lists into a crash instead of an error.
Sourcepub fn into_config(&self) -> Result<TransportConfig, TransportProfileError>
pub fn into_config(&self) -> Result<TransportConfig, TransportProfileError>
A fresh config carrying only this profile.
Equivalent to TransportProfile::apply_to over a
quinn::TransportConfig::default(), and defined that way rather than
duplicated, so the two can only ever accept and refuse the same
profiles. Use it for a leg with no configuration of its own; use
apply_to for a leg that already has one.
Trait Implementations§
Source§impl Clone for TransportProfile
impl Clone for TransportProfile
Source§fn clone(&self) -> TransportProfile
fn clone(&self) -> TransportProfile
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more