diff --git a/pr/451/docs/bench/enum.CipherSuite.html b/pr/451/docs/bench/enum.CipherSuite.html index 6904e5d0d..cd78cb8b9 100644 --- a/pr/451/docs/bench/enum.CipherSuite.html +++ b/pr/451/docs/bench/enum.CipherSuite.html @@ -2,7 +2,7 @@ Aes128, Aes256, Chacha20, -}

Variants§

§

Aes128

§

Aes256

§

Chacha20

Implementations§

Source§

impl CipherSuite

Source

pub fn as_rustls(self) -> SupportedCipherSuite

Trait Implementations§

Source§

impl Clone for CipherSuite

Source§

fn clone(&self) -> CipherSuite

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for CipherSuite

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl FromStr for CipherSuite

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
Source§

impl PartialEq for CipherSuite

Source§

fn eq(&self, other: &CipherSuite) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +}

Variants§

§

Aes128

§

Aes256

§

Chacha20

Implementations§

Source§

impl CipherSuite

Source

pub fn as_rustls(self) -> SupportedCipherSuite

Trait Implementations§

Source§

impl Clone for CipherSuite

Source§

fn clone(&self) -> CipherSuite

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for CipherSuite

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl FromStr for CipherSuite

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
Source§

impl PartialEq for CipherSuite

Source§

fn eq(&self, other: &CipherSuite) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for CipherSuite

Source§

impl Eq for CipherSuite

Source§

impl StructuralPartialEq for CipherSuite

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/bench/enum.RuntimeType.html b/pr/451/docs/bench/enum.RuntimeType.html index e56c4d369..597314c53 100644 --- a/pr/451/docs/bench/enum.RuntimeType.html +++ b/pr/451/docs/bench/enum.RuntimeType.html @@ -1,7 +1,7 @@ RuntimeType in bench - Rust

RuntimeType

Enum RuntimeType 

Source
pub enum RuntimeType {
     Tokio,
     TokioCurrentThread,
-}

Variants§

§

Tokio

§

TokioCurrentThread

Trait Implementations§

Source§

impl Clone for RuntimeType

Source§

fn clone(&self) -> RuntimeType

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RuntimeType

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl FromStr for RuntimeType

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
Source§

impl Copy for RuntimeType

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +}

Variants§

§

Tokio

§

TokioCurrentThread

Trait Implementations§

Source§

impl Clone for RuntimeType

Source§

fn clone(&self) -> RuntimeType

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RuntimeType

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl FromStr for RuntimeType

Source§

type Err = Error

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
Source§

impl Copy for RuntimeType

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/bench/fn.connect_client.html b/pr/451/docs/bench/fn.connect_client.html index 1f4edbfc9..8d7d3562c 100644 --- a/pr/451/docs/bench/fn.connect_client.html +++ b/pr/451/docs/bench/fn.connect_client.html @@ -2,5 +2,5 @@ server_addr: SocketAddr, server_cert: CertificateDer<'_>, opt: Opt, -) -> Result<(Endpoint, Connection)>
Expand description

Create a client endpoint and client connection

+) -> Result<(Endpoint, Connection)>
Expand description

Create a client endpoint and client connection

\ No newline at end of file diff --git a/pr/451/docs/bench/fn.drain_stream.html b/pr/451/docs/bench/fn.drain_stream.html index 1156135bd..3c1afc29e 100644 --- a/pr/451/docs/bench/fn.drain_stream.html +++ b/pr/451/docs/bench/fn.drain_stream.html @@ -1,4 +1,4 @@ drain_stream in bench - Rust

drain_stream

Function drain_stream 

Source
pub async fn drain_stream(
     stream: RecvStream,
     read_unordered: bool,
-) -> Result<usize>
\ No newline at end of file +) -> Result<usize>

\ No newline at end of file diff --git a/pr/451/docs/bench/fn.send_data_on_stream.html b/pr/451/docs/bench/fn.send_data_on_stream.html index dbdfc4d28..1982b23de 100644 --- a/pr/451/docs/bench/fn.send_data_on_stream.html +++ b/pr/451/docs/bench/fn.send_data_on_stream.html @@ -1,4 +1,4 @@ send_data_on_stream in bench - Rust

send_data_on_stream

Function send_data_on_stream 

Source
pub async fn send_data_on_stream(
     stream: &mut SendStream,
     stream_size: u64,
-) -> Result<()>
\ No newline at end of file +) -> Result<()>
\ No newline at end of file diff --git a/pr/451/docs/bulk/fn.client.html b/pr/451/docs/bulk/fn.client.html index 11e16fbf0..64bb3368e 100644 --- a/pr/451/docs/bulk/fn.client.html +++ b/pr/451/docs/bulk/fn.client.html @@ -2,4 +2,4 @@ server_addr: SocketAddr, server_cert: CertificateDer<'static>, opt: Opt, -) -> Result<ClientStats>
\ No newline at end of file +) -> Result<ClientStats>
\ No newline at end of file diff --git a/pr/451/docs/bulk/fn.handle_client_stream.html b/pr/451/docs/bulk/fn.handle_client_stream.html index 057b0d3ce..18505aaac 100644 --- a/pr/451/docs/bulk/fn.handle_client_stream.html +++ b/pr/451/docs/bulk/fn.handle_client_stream.html @@ -2,4 +2,4 @@ connection: Arc<Connection>, upload_size: u64, read_unordered: bool, -) -> Result<(TransferResult, TransferResult)> \ No newline at end of file +) -> Result<(TransferResult, TransferResult)> \ No newline at end of file diff --git a/pr/451/docs/bulk/fn.server.html b/pr/451/docs/bulk/fn.server.html index c1722630f..c6daeb89c 100644 --- a/pr/451/docs/bulk/fn.server.html +++ b/pr/451/docs/bulk/fn.server.html @@ -1 +1 @@ -server in bulk - Rust

server

Function server 

Source
pub(crate) async fn server(endpoint: Endpoint, opt: Opt) -> Result<()>
\ No newline at end of file +server in bulk - Rust

server

Function server 

Source
pub(crate) async fn server(endpoint: Endpoint, opt: Opt) -> Result<()>
\ No newline at end of file diff --git a/pr/451/docs/data_transfer/fn.open_bidirectional_stream.html b/pr/451/docs/data_transfer/fn.open_bidirectional_stream.html index 2655892df..c8053c307 100644 --- a/pr/451/docs/data_transfer/fn.open_bidirectional_stream.html +++ b/pr/451/docs/data_transfer/fn.open_bidirectional_stream.html @@ -1,3 +1,3 @@ open_bidirectional_stream in data_transfer - Rust

open_bidirectional_stream

Function open_bidirectional_stream 

Source
pub(crate) async fn open_bidirectional_stream(
     connection: Connection,
-) -> Result<()>
\ No newline at end of file +) -> Result<()> \ No newline at end of file diff --git a/pr/451/docs/data_transfer/fn.open_unidirectional_stream.html b/pr/451/docs/data_transfer/fn.open_unidirectional_stream.html index dabbc95a8..dbfe07c07 100644 --- a/pr/451/docs/data_transfer/fn.open_unidirectional_stream.html +++ b/pr/451/docs/data_transfer/fn.open_unidirectional_stream.html @@ -1,3 +1,3 @@ open_unidirectional_stream in data_transfer - Rust

open_unidirectional_stream

Function open_unidirectional_stream 

Source
pub(crate) async fn open_unidirectional_stream(
     connection: Connection,
-) -> Result<()>
\ No newline at end of file +) -> Result<()> \ No newline at end of file diff --git a/pr/451/docs/data_transfer/fn.receive_bidirectional_stream.html b/pr/451/docs/data_transfer/fn.receive_bidirectional_stream.html index 7622451c8..8461993e2 100644 --- a/pr/451/docs/data_transfer/fn.receive_bidirectional_stream.html +++ b/pr/451/docs/data_transfer/fn.receive_bidirectional_stream.html @@ -1,3 +1,3 @@ receive_bidirectional_stream in data_transfer - Rust

receive_bidirectional_stream

Function receive_bidirectional_stream 

Source
pub(crate) async fn receive_bidirectional_stream(
     connection: Connection,
-) -> Result<()>
\ No newline at end of file +) -> Result<()> \ No newline at end of file diff --git a/pr/451/docs/data_transfer/fn.receive_datagram.html b/pr/451/docs/data_transfer/fn.receive_datagram.html index 53214940b..b2a5dd96b 100644 --- a/pr/451/docs/data_transfer/fn.receive_datagram.html +++ b/pr/451/docs/data_transfer/fn.receive_datagram.html @@ -1 +1 @@ -receive_datagram in data_transfer - Rust

receive_datagram

Function receive_datagram 

Source
pub(crate) async fn receive_datagram(connection: Connection) -> Result<()>
\ No newline at end of file +receive_datagram in data_transfer - Rust

receive_datagram

Function receive_datagram 

Source
pub(crate) async fn receive_datagram(connection: Connection) -> Result<()>
\ No newline at end of file diff --git a/pr/451/docs/data_transfer/fn.receive_unidirectional_stream.html b/pr/451/docs/data_transfer/fn.receive_unidirectional_stream.html index 3cb11052a..2e96d2dbb 100644 --- a/pr/451/docs/data_transfer/fn.receive_unidirectional_stream.html +++ b/pr/451/docs/data_transfer/fn.receive_unidirectional_stream.html @@ -1,3 +1,3 @@ receive_unidirectional_stream in data_transfer - Rust

receive_unidirectional_stream

Function receive_unidirectional_stream 

Source
pub(crate) async fn receive_unidirectional_stream(
     connection: Connection,
-) -> Result<()>
\ No newline at end of file +) -> Result<()> \ No newline at end of file diff --git a/pr/451/docs/data_transfer/fn.send_unreliable.html b/pr/451/docs/data_transfer/fn.send_unreliable.html index eb16f5a71..00d3b0575 100644 --- a/pr/451/docs/data_transfer/fn.send_unreliable.html +++ b/pr/451/docs/data_transfer/fn.send_unreliable.html @@ -1 +1 @@ -send_unreliable in data_transfer - Rust

send_unreliable

Function send_unreliable 

Source
pub(crate) async fn send_unreliable(connection: Connection) -> Result<()>
\ No newline at end of file +send_unreliable in data_transfer - Rust

send_unreliable

Function send_unreliable 

Source
pub(crate) async fn send_unreliable(connection: Connection) -> Result<()>
\ No newline at end of file diff --git a/pr/451/docs/help.html b/pr/451/docs/help.html index cda0c7f93..cb140c09e 100644 --- a/pr/451/docs/help.html +++ b/pr/451/docs/help.html @@ -1 +1 @@ -Help

All

Rustdoc help

Back
\ No newline at end of file +Help

All

Rustdoc help

Back
\ No newline at end of file diff --git a/pr/451/docs/noq/all.html b/pr/451/docs/noq/all.html index a9ab61b80..1f86b1b8d 100644 --- a/pr/451/docs/noq/all.html +++ b/pr/451/docs/noq/all.html @@ -1 +1 @@ -List of all items in this crate

All

List of all items

Structs

Enums

Traits

Functions

\ No newline at end of file +List of all items in this crate

All

List of all items

Structs

Enums

Traits

Functions

\ No newline at end of file diff --git a/pr/451/docs/noq/congestion/index.html b/pr/451/docs/noq/congestion/index.html index 7b4789267..e284c4e08 100644 --- a/pr/451/docs/noq/congestion/index.html +++ b/pr/451/docs/noq/congestion/index.html @@ -1,4 +1,4 @@ -noq::congestion - Rust

Module congestion

Module congestion 

Source
Expand description

Logic for controlling the rate at which data is sent

+noq::congestion - Rust

Module congestion

Module congestion 

Source
Expand description

Logic for controlling the rate at which data is sent

Structs§

Bbr3
Experimental! Use at your own risk.
Bbr3Config
Configuration for the Bbr3 congestion controller
ControllerMetrics
Common congestion controller metrics used both for logging purposes but also to alter the pacing of the connection with pacing_rate and send_quantum
Cubic
The RFC8312 congestion controller, as widely used for TCP
CubicConfig
Configuration for the Cubic congestion controller
NewReno
A simple, standard congestion controller
NewRenoConfig
Configuration for the NewReno congestion controller

Traits§

Controller
Common interface for different congestion controllers
ControllerFactory
Constructs controllers on demand
\ No newline at end of file diff --git a/pr/451/docs/noq/congestion/struct.Bbr3.html b/pr/451/docs/noq/congestion/struct.Bbr3.html index e20076bcb..59bc80777 100644 --- a/pr/451/docs/noq/congestion/struct.Bbr3.html +++ b/pr/451/docs/noq/congestion/struct.Bbr3.html @@ -1,4 +1,4 @@ -Bbr3 in noq::congestion - Rust

Bbr3

Struct Bbr3 

Source
pub struct Bbr3 { /* private fields */ }
Expand description

Experimental! Use at your own risk.

+Bbr3 in noq::congestion - Rust

Bbr3

Struct Bbr3 

Source
pub struct Bbr3 { /* private fields */ }
Expand description

Experimental! Use at your own risk.

Aims for reduced buffer bloat and improved performance over high bandwidth-delay product networks. Based on https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html equivalent to a combination of BBR and C states diff --git a/pr/451/docs/noq/congestion/struct.Bbr3Config.html b/pr/451/docs/noq/congestion/struct.Bbr3Config.html index 70744366a..d041209df 100644 --- a/pr/451/docs/noq/congestion/struct.Bbr3Config.html +++ b/pr/451/docs/noq/congestion/struct.Bbr3Config.html @@ -1,4 +1,4 @@ -Bbr3Config in noq::congestion - Rust

Bbr3Config

Struct Bbr3Config 

Source
pub struct Bbr3Config { /* private fields */ }
Expand description

Configuration for the Bbr3 congestion controller

+Bbr3Config in noq::congestion - Rust

Bbr3Config

Struct Bbr3Config 

Source
pub struct Bbr3Config { /* private fields */ }
Expand description

Configuration for the Bbr3 congestion controller

Different pacing_gains can be set to modify the multiplier used to increase the sending rates. Different cwnd_gains can be set to modify the multiplier used to increase diff --git a/pr/451/docs/noq/congestion/struct.ControllerMetrics.html b/pr/451/docs/noq/congestion/struct.ControllerMetrics.html index e5c0772bb..065d1dea8 100644 --- a/pr/451/docs/noq/congestion/struct.ControllerMetrics.html +++ b/pr/451/docs/noq/congestion/struct.ControllerMetrics.html @@ -1,4 +1,4 @@ -ControllerMetrics in noq::congestion - Rust

ControllerMetrics

Struct ControllerMetrics 

Source
#[non_exhaustive]
pub struct ControllerMetrics { +ControllerMetrics in noq::congestion - Rust

ControllerMetrics

Struct ControllerMetrics 

Source
#[non_exhaustive]
pub struct ControllerMetrics { pub congestion_window: u64, pub ssthresh: Option<u64>, pub pacing_rate: Option<u64>, diff --git a/pr/451/docs/noq/congestion/struct.Cubic.html b/pr/451/docs/noq/congestion/struct.Cubic.html index 8fc498e57..306b802bf 100644 --- a/pr/451/docs/noq/congestion/struct.Cubic.html +++ b/pr/451/docs/noq/congestion/struct.Cubic.html @@ -1,4 +1,4 @@ -Cubic in noq::congestion - Rust

Cubic

Struct Cubic 

Source
pub struct Cubic { /* private fields */ }
Expand description

The RFC8312 congestion controller, as widely used for TCP

+Cubic in noq::congestion - Rust

Cubic

Struct Cubic 

Source
pub struct Cubic { /* private fields */ }
Expand description

The RFC8312 congestion controller, as widely used for TCP

Implementations§

Source§

impl Cubic

Source

pub fn new(config: Arc<CubicConfig>, _now: Instant, current_mtu: u16) -> Cubic

Construct a state using the given config and current time now

Trait Implementations§

Source§

impl Clone for Cubic

Source§

fn clone(&self) -> Cubic

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Controller for Cubic

Source§

fn on_ack( &mut self, diff --git a/pr/451/docs/noq/congestion/struct.CubicConfig.html b/pr/451/docs/noq/congestion/struct.CubicConfig.html index a41c39a3b..794cb2b54 100644 --- a/pr/451/docs/noq/congestion/struct.CubicConfig.html +++ b/pr/451/docs/noq/congestion/struct.CubicConfig.html @@ -1,4 +1,4 @@ -CubicConfig in noq::congestion - Rust

CubicConfig

Struct CubicConfig 

Source
pub struct CubicConfig { /* private fields */ }
Expand description

Configuration for the Cubic congestion controller

+CubicConfig in noq::congestion - Rust

CubicConfig

Struct CubicConfig 

Source
pub struct CubicConfig { /* private fields */ }
Expand description

Configuration for the Cubic congestion controller

Implementations§

Source§

impl CubicConfig

Source

pub fn initial_window(&mut self, value: u64) -> &mut CubicConfig

Default limit on the amount of outstanding data in bytes.

Recommended value: min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))

Trait Implementations§

Source§

impl Clone for CubicConfig

Source§

fn clone(&self) -> CubicConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl ControllerFactory for CubicConfig

Source§

fn build( diff --git a/pr/451/docs/noq/congestion/struct.NewReno.html b/pr/451/docs/noq/congestion/struct.NewReno.html index 56b75d807..c4cf2f63c 100644 --- a/pr/451/docs/noq/congestion/struct.NewReno.html +++ b/pr/451/docs/noq/congestion/struct.NewReno.html @@ -1,4 +1,4 @@ -NewReno in noq::congestion - Rust

NewReno

Struct NewReno 

Source
pub struct NewReno { /* private fields */ }
Expand description

A simple, standard congestion controller

+NewReno in noq::congestion - Rust

NewReno

Struct NewReno 

Source
pub struct NewReno { /* private fields */ }
Expand description

A simple, standard congestion controller

Implementations§

Source§

impl NewReno

Source

pub fn new( config: Arc<NewRenoConfig>, now: Instant, diff --git a/pr/451/docs/noq/congestion/struct.NewRenoConfig.html b/pr/451/docs/noq/congestion/struct.NewRenoConfig.html index 4b226f8c0..41f0b4d7c 100644 --- a/pr/451/docs/noq/congestion/struct.NewRenoConfig.html +++ b/pr/451/docs/noq/congestion/struct.NewRenoConfig.html @@ -1,4 +1,4 @@ -NewRenoConfig in noq::congestion - Rust

NewRenoConfig

Struct NewRenoConfig 

Source
pub struct NewRenoConfig { /* private fields */ }
Expand description

Configuration for the NewReno congestion controller

+NewRenoConfig in noq::congestion - Rust

NewRenoConfig

Struct NewRenoConfig 

Source
pub struct NewRenoConfig { /* private fields */ }
Expand description

Configuration for the NewReno congestion controller

Implementations§

Source§

impl NewRenoConfig

Source

pub fn initial_window(&mut self, value: u64) -> &mut NewRenoConfig

Default limit on the amount of outstanding data in bytes.

Recommended value: min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))

Source

pub fn loss_reduction_factor(&mut self, value: f32) -> &mut NewRenoConfig

Reduction in congestion window when a new loss event is detected.

diff --git a/pr/451/docs/noq/congestion/trait.Controller.html b/pr/451/docs/noq/congestion/trait.Controller.html index 6c44b51fa..443d47ffd 100644 --- a/pr/451/docs/noq/congestion/trait.Controller.html +++ b/pr/451/docs/noq/congestion/trait.Controller.html @@ -1,4 +1,4 @@ -Controller in noq::congestion - Rust

Controller

Trait Controller 

Source
pub trait Controller:
+Controller in noq::congestion - Rust

Controller

Trait Controller 

Source
pub trait Controller:
     Send
     + Sync
     + Debug {
diff --git a/pr/451/docs/noq/congestion/trait.ControllerFactory.html b/pr/451/docs/noq/congestion/trait.ControllerFactory.html
index 4c9343f49..f9b43c998 100644
--- a/pr/451/docs/noq/congestion/trait.ControllerFactory.html
+++ b/pr/451/docs/noq/congestion/trait.ControllerFactory.html
@@ -1,4 +1,4 @@
-ControllerFactory in noq::congestion - Rust

ControllerFactory

Trait ControllerFactory 

Source
pub trait ControllerFactory {
+ControllerFactory in noq::congestion - Rust

ControllerFactory

Trait ControllerFactory 

Source
pub trait ControllerFactory {
     // Required method
     fn build(
         self: Arc<Self>,
diff --git a/pr/451/docs/noq/crypto/index.html b/pr/451/docs/noq/crypto/index.html
index 0d19d1acc..8a1103728 100644
--- a/pr/451/docs/noq/crypto/index.html
+++ b/pr/451/docs/noq/crypto/index.html
@@ -1,4 +1,4 @@
-noq::crypto - Rust

Module crypto

Module crypto 

Source
Expand description

Traits and implementations for the QUIC cryptography protocol

+noq::crypto - Rust

Module crypto

Module crypto 

Source
Expand description

Traits and implementations for the QUIC cryptography protocol

The protocol logic in noq is contained in types that abstract over the actual cryptographic protocol used. This module contains the traits used for this abstraction layer as well as a single implementation of these traits that uses diff --git a/pr/451/docs/noq/crypto/rustls/enum.Error.html b/pr/451/docs/noq/crypto/rustls/enum.Error.html index 836ac7220..cd7876111 100644 --- a/pr/451/docs/noq/crypto/rustls/enum.Error.html +++ b/pr/451/docs/noq/crypto/rustls/enum.Error.html @@ -1,4 +1,4 @@ -Error in noq::crypto::rustls - Rust

Error

Enum Error 

#[non_exhaustive]
pub enum Error { +Error in noq::crypto::rustls - Rust

Error

Enum Error 

#[non_exhaustive]
pub enum Error {
Show 22 variants InappropriateMessage { expect_types: Vec<ContentType>, got_type: ContentType, diff --git a/pr/451/docs/noq/crypto/rustls/index.html b/pr/451/docs/noq/crypto/rustls/index.html index 90d332226..a6532b1c3 100644 --- a/pr/451/docs/noq/crypto/rustls/index.html +++ b/pr/451/docs/noq/crypto/rustls/index.html @@ -1,2 +1,2 @@ -noq::crypto::rustls - Rust

Module rustls

Module rustls 

Source
Expand description

TLS interface based on rustls

+noq::crypto::rustls - Rust

Module rustls

Module rustls 

Source
Expand description

TLS interface based on rustls

Structs§

HandshakeData
Authentication data for (rustls) TLS session
NoInitialCipherSuite
The initial cipher suite (AES-128-GCM-SHA256) is not available
QuicClientConfig
A QUIC-compatible TLS client configuration
QuicServerConfig
A QUIC-compatible TLS server configuration
TlsSession
A rustls TLS session

Enums§

Error
rustls reports protocol errors using this type.
\ No newline at end of file diff --git a/pr/451/docs/noq/crypto/rustls/struct.HandshakeData.html b/pr/451/docs/noq/crypto/rustls/struct.HandshakeData.html index 616b610ef..0bfd77426 100644 --- a/pr/451/docs/noq/crypto/rustls/struct.HandshakeData.html +++ b/pr/451/docs/noq/crypto/rustls/struct.HandshakeData.html @@ -1,4 +1,4 @@ -HandshakeData in noq::crypto::rustls - Rust

HandshakeData

Struct HandshakeData 

Source
#[non_exhaustive]
pub struct HandshakeData { +HandshakeData in noq::crypto::rustls - Rust

HandshakeData

Struct HandshakeData 

Source
#[non_exhaustive]
pub struct HandshakeData { pub protocol: Option<Vec<u8>>, pub server_name: Option<String>, pub negotiated_key_exchange_group: Option<NamedGroup>, diff --git a/pr/451/docs/noq/crypto/rustls/struct.NoInitialCipherSuite.html b/pr/451/docs/noq/crypto/rustls/struct.NoInitialCipherSuite.html index e019c918e..50b72c878 100644 --- a/pr/451/docs/noq/crypto/rustls/struct.NoInitialCipherSuite.html +++ b/pr/451/docs/noq/crypto/rustls/struct.NoInitialCipherSuite.html @@ -1,4 +1,4 @@ -NoInitialCipherSuite in noq::crypto::rustls - Rust

NoInitialCipherSuite

Struct NoInitialCipherSuite 

Source
pub struct NoInitialCipherSuite { /* private fields */ }
Expand description

The initial cipher suite (AES-128-GCM-SHA256) is not available

+NoInitialCipherSuite in noq::crypto::rustls - Rust

NoInitialCipherSuite

Struct NoInitialCipherSuite 

Source
pub struct NoInitialCipherSuite { /* private fields */ }
Expand description

The initial cipher suite (AES-128-GCM-SHA256) is not available

When the cipher suite is supplied with_initial(), it must be [CipherSuite::TLS13_AES_128_GCM_SHA256]. When the cipher suite is derived from a config’s CryptoProvider, that provider must reference a cipher suite with the same ID.

diff --git a/pr/451/docs/noq/crypto/rustls/struct.QuicClientConfig.html b/pr/451/docs/noq/crypto/rustls/struct.QuicClientConfig.html index 6dbcb6b30..fd4d95bfd 100644 --- a/pr/451/docs/noq/crypto/rustls/struct.QuicClientConfig.html +++ b/pr/451/docs/noq/crypto/rustls/struct.QuicClientConfig.html @@ -1,4 +1,4 @@ -QuicClientConfig in noq::crypto::rustls - Rust

QuicClientConfig

Struct QuicClientConfig 

Source
pub struct QuicClientConfig { /* private fields */ }
Expand description

A QUIC-compatible TLS client configuration

+QuicClientConfig in noq::crypto::rustls - Rust

QuicClientConfig

Struct QuicClientConfig 

Source
pub struct QuicClientConfig { /* private fields */ }
Expand description

A QUIC-compatible TLS client configuration

noq implicitly constructs a QuicClientConfig with reasonable defaults within ClientConfig::with_root_certificates() and ClientConfig::try_with_platform_verifier(). Alternatively, QuicClientConfig’s TryFrom implementation can be used to wrap around a diff --git a/pr/451/docs/noq/crypto/rustls/struct.QuicServerConfig.html b/pr/451/docs/noq/crypto/rustls/struct.QuicServerConfig.html index c2a99906d..80da981cf 100644 --- a/pr/451/docs/noq/crypto/rustls/struct.QuicServerConfig.html +++ b/pr/451/docs/noq/crypto/rustls/struct.QuicServerConfig.html @@ -1,4 +1,4 @@ -QuicServerConfig in noq::crypto::rustls - Rust

QuicServerConfig

Struct QuicServerConfig 

Source
pub struct QuicServerConfig { /* private fields */ }
Expand description

A QUIC-compatible TLS server configuration

+QuicServerConfig in noq::crypto::rustls - Rust

QuicServerConfig

Struct QuicServerConfig 

Source
pub struct QuicServerConfig { /* private fields */ }
Expand description

A QUIC-compatible TLS server configuration

noq implicitly constructs a QuicServerConfig with reasonable defaults within ServerConfig::with_single_cert(). Alternatively, QuicServerConfig’s TryFrom implementation or with_initial method can be used to wrap around a custom diff --git a/pr/451/docs/noq/crypto/rustls/struct.TlsSession.html b/pr/451/docs/noq/crypto/rustls/struct.TlsSession.html index 9bd57d417..4be11ed1c 100644 --- a/pr/451/docs/noq/crypto/rustls/struct.TlsSession.html +++ b/pr/451/docs/noq/crypto/rustls/struct.TlsSession.html @@ -1,4 +1,4 @@ -TlsSession in noq::crypto::rustls - Rust

TlsSession

Struct TlsSession 

Source
pub struct TlsSession { /* private fields */ }
Expand description

A rustls TLS session

+TlsSession in noq::crypto::rustls - Rust

TlsSession

Struct TlsSession 

Source
pub struct TlsSession { /* private fields */ }
Expand description

A rustls TLS session

Trait Implementations§

Source§

impl Session for TlsSession

Source§

fn peer_identity(&self) -> Option<Box<dyn Any>>

For the rustls TlsSession, the Any type is Vec<rustls::pki_types::CertificateDer>

Source§

fn initial_keys(&self, dst_cid: ConnectionId, side: Side) -> Keys

Create the initial set of keys given the client’s initial destination ConnectionId
Source§

fn handshake_data(&self) -> Option<Box<dyn Any>>

Get data negotiated during the handshake, if available Read more
Source§

fn early_crypto(&self) -> Option<(Box<dyn HeaderKey>, Box<dyn PacketKey>)>

Get the 0-RTT keys if available (clients only) Read more
Source§

fn early_data_accepted(&self) -> Option<bool>

If the 0-RTT-encrypted data has been accepted by the peer
Source§

fn is_handshaking(&self) -> bool

Returns true until the connection is fully established.
Source§

fn read_handshake(&mut self, buf: &[u8]) -> Result<bool, Error>

Read bytes of handshake data Read more
Source§

fn transport_parameters(&self) -> Result<Option<TransportParameters>, Error>

The peer’s QUIC transport parameters Read more
Source§

fn write_handshake(&mut self, buf: &mut Vec<u8>) -> Option<Keys>

Writes handshake bytes into the given buffer and optionally returns the negotiated keys Read more
Source§

fn next_1rtt_keys(&mut self) -> Option<KeyPair<Box<dyn PacketKey>>>

Compute keys for the next key update
Source§

fn is_valid_retry( &self, diff --git a/pr/451/docs/noq/crypto/struct.CryptoError.html b/pr/451/docs/noq/crypto/struct.CryptoError.html index 569eb4367..9eede6a07 100644 --- a/pr/451/docs/noq/crypto/struct.CryptoError.html +++ b/pr/451/docs/noq/crypto/struct.CryptoError.html @@ -1,4 +1,4 @@ -CryptoError in noq::crypto - Rust

CryptoError

Struct CryptoError 

Source
pub struct CryptoError;
Expand description

Generic crypto errors

+CryptoError in noq::crypto - Rust

CryptoError

Struct CryptoError 

Source
pub struct CryptoError;
Expand description

Generic crypto errors

Trait Implementations§

Source§

impl Debug for CryptoError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/crypto/struct.ExportKeyingMaterialError.html b/pr/451/docs/noq/crypto/struct.ExportKeyingMaterialError.html index ba9c3d145..0a4025d30 100644 --- a/pr/451/docs/noq/crypto/struct.ExportKeyingMaterialError.html +++ b/pr/451/docs/noq/crypto/struct.ExportKeyingMaterialError.html @@ -1,4 +1,4 @@ -ExportKeyingMaterialError in noq::crypto - Rust

ExportKeyingMaterialError

Struct ExportKeyingMaterialError 

Source
pub struct ExportKeyingMaterialError;
Expand description

Error returned by Session::export_keying_material.

+ExportKeyingMaterialError in noq::crypto - Rust

ExportKeyingMaterialError

Struct ExportKeyingMaterialError 

Source
pub struct ExportKeyingMaterialError;
Expand description

Error returned by Session::export_keying_material.

This error occurs if the requested output length is too large.

Trait Implementations§

Source§

impl Debug for ExportKeyingMaterialError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl PartialEq for ExportKeyingMaterialError

Source§

fn eq(&self, other: &ExportKeyingMaterialError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Eq for ExportKeyingMaterialError

Source§

impl StructuralPartialEq for ExportKeyingMaterialError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where diff --git a/pr/451/docs/noq/crypto/struct.KeyPair.html b/pr/451/docs/noq/crypto/struct.KeyPair.html index 0a703377b..13180bee7 100644 --- a/pr/451/docs/noq/crypto/struct.KeyPair.html +++ b/pr/451/docs/noq/crypto/struct.KeyPair.html @@ -1,4 +1,4 @@ -KeyPair in noq::crypto - Rust

KeyPair

Struct KeyPair 

Source
pub struct KeyPair<T> {
+KeyPair in noq::crypto - Rust

KeyPair

Struct KeyPair 

Source
pub struct KeyPair<T> {
     pub local: T,
     pub remote: T,
 }
Expand description

A pair of keys for bidirectional communication

diff --git a/pr/451/docs/noq/crypto/struct.Keys.html b/pr/451/docs/noq/crypto/struct.Keys.html index 620d9df84..e4564b71e 100644 --- a/pr/451/docs/noq/crypto/struct.Keys.html +++ b/pr/451/docs/noq/crypto/struct.Keys.html @@ -1,4 +1,4 @@ -Keys in noq::crypto - Rust

Keys

Struct Keys 

Source
pub struct Keys {
+Keys in noq::crypto - Rust

Keys

Struct Keys 

Source
pub struct Keys {
     pub header: KeyPair<Box<dyn HeaderKey>>,
     pub packet: KeyPair<Box<dyn PacketKey>>,
 }
Expand description

A complete set of keys for a certain packet space

diff --git a/pr/451/docs/noq/crypto/struct.UnsupportedVersion.html b/pr/451/docs/noq/crypto/struct.UnsupportedVersion.html index 52ba3fac8..653fe7028 100644 --- a/pr/451/docs/noq/crypto/struct.UnsupportedVersion.html +++ b/pr/451/docs/noq/crypto/struct.UnsupportedVersion.html @@ -1,4 +1,4 @@ -UnsupportedVersion in noq::crypto - Rust

UnsupportedVersion

Struct UnsupportedVersion 

Source
pub struct UnsupportedVersion;
Expand description

Error indicating that the specified QUIC version is not supported

+UnsupportedVersion in noq::crypto - Rust

UnsupportedVersion

Struct UnsupportedVersion 

Source
pub struct UnsupportedVersion;
Expand description

Error indicating that the specified QUIC version is not supported

Trait Implementations§

Source§

impl Debug for UnsupportedVersion

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl From<UnsupportedVersion> for ConnectError

Source§

fn from(_: UnsupportedVersion) -> ConnectError

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/crypto/trait.ClientConfig.html b/pr/451/docs/noq/crypto/trait.ClientConfig.html index 2df73cbd7..2cf2122e2 100644 --- a/pr/451/docs/noq/crypto/trait.ClientConfig.html +++ b/pr/451/docs/noq/crypto/trait.ClientConfig.html @@ -1,4 +1,4 @@ -ClientConfig in noq::crypto - Rust

ClientConfig

Trait ClientConfig 

Source
pub trait ClientConfig: Send + Sync {
+ClientConfig in noq::crypto - Rust

ClientConfig

Trait ClientConfig 

Source
pub trait ClientConfig: Send + Sync {
     // Required method
     fn start_session(
         &self,
diff --git a/pr/451/docs/noq/crypto/trait.HandshakeTokenKey.html b/pr/451/docs/noq/crypto/trait.HandshakeTokenKey.html
index cf51ee2ce..d066ce2d4 100644
--- a/pr/451/docs/noq/crypto/trait.HandshakeTokenKey.html
+++ b/pr/451/docs/noq/crypto/trait.HandshakeTokenKey.html
@@ -1,4 +1,4 @@
-HandshakeTokenKey in noq::crypto - Rust

HandshakeTokenKey

Trait HandshakeTokenKey 

Source
pub trait HandshakeTokenKey: Send + Sync {
+HandshakeTokenKey in noq::crypto - Rust

HandshakeTokenKey

Trait HandshakeTokenKey 

Source
pub trait HandshakeTokenKey: Send + Sync {
     // Required methods
     fn seal(
         &self,
diff --git a/pr/451/docs/noq/crypto/trait.HeaderKey.html b/pr/451/docs/noq/crypto/trait.HeaderKey.html
index 0270f5240..55c6d9600 100644
--- a/pr/451/docs/noq/crypto/trait.HeaderKey.html
+++ b/pr/451/docs/noq/crypto/trait.HeaderKey.html
@@ -1,4 +1,4 @@
-HeaderKey in noq::crypto - Rust

HeaderKey

Trait HeaderKey 

Source
pub trait HeaderKey: Send + Sync {
+HeaderKey in noq::crypto - Rust

HeaderKey

Trait HeaderKey 

Source
pub trait HeaderKey: Send + Sync {
     // Required methods
     fn decrypt(&self, pn_offset: usize, packet: &mut [u8]);
     fn encrypt(&self, pn_offset: usize, packet: &mut [u8]);
diff --git a/pr/451/docs/noq/crypto/trait.HmacKey.html b/pr/451/docs/noq/crypto/trait.HmacKey.html
index f57593ce2..4c5fb114e 100644
--- a/pr/451/docs/noq/crypto/trait.HmacKey.html
+++ b/pr/451/docs/noq/crypto/trait.HmacKey.html
@@ -1,4 +1,4 @@
-HmacKey in noq::crypto - Rust

HmacKey

Trait HmacKey 

Source
pub trait HmacKey: Send + Sync {
+HmacKey in noq::crypto - Rust

HmacKey

Trait HmacKey 

Source
pub trait HmacKey: Send + Sync {
     // Required methods
     fn sign(&self, data: &[u8], signature_out: &mut [u8]);
     fn signature_len(&self) -> usize;
diff --git a/pr/451/docs/noq/crypto/trait.PacketKey.html b/pr/451/docs/noq/crypto/trait.PacketKey.html
index a3baef92e..851b4b011 100644
--- a/pr/451/docs/noq/crypto/trait.PacketKey.html
+++ b/pr/451/docs/noq/crypto/trait.PacketKey.html
@@ -1,4 +1,4 @@
-PacketKey in noq::crypto - Rust

PacketKey

Trait PacketKey 

Source
pub trait PacketKey: Send + Sync {
+PacketKey in noq::crypto - Rust

PacketKey

Trait PacketKey 

Source
pub trait PacketKey: Send + Sync {
     // Required methods
     fn encrypt(
         &self,
diff --git a/pr/451/docs/noq/crypto/trait.ServerConfig.html b/pr/451/docs/noq/crypto/trait.ServerConfig.html
index b535a589c..51a76889f 100644
--- a/pr/451/docs/noq/crypto/trait.ServerConfig.html
+++ b/pr/451/docs/noq/crypto/trait.ServerConfig.html
@@ -1,4 +1,4 @@
-ServerConfig in noq::crypto - Rust

ServerConfig

Trait ServerConfig 

Source
pub trait ServerConfig: Send + Sync {
+ServerConfig in noq::crypto - Rust

ServerConfig

Trait ServerConfig 

Source
pub trait ServerConfig: Send + Sync {
     // Required methods
     fn initial_keys(
         &self,
diff --git a/pr/451/docs/noq/crypto/trait.Session.html b/pr/451/docs/noq/crypto/trait.Session.html
index 813e78b20..5a7892e5d 100644
--- a/pr/451/docs/noq/crypto/trait.Session.html
+++ b/pr/451/docs/noq/crypto/trait.Session.html
@@ -1,4 +1,4 @@
-Session in noq::crypto - Rust

Session

Trait Session 

Source
pub trait Session:
+Session in noq::crypto - Rust

Session

Trait Session 

Source
pub trait Session:
     Send
     + Sync
     + 'static {
diff --git a/pr/451/docs/noq/enum.ClosePathError.html b/pr/451/docs/noq/enum.ClosePathError.html
index d584d3078..1934fc9be 100644
--- a/pr/451/docs/noq/enum.ClosePathError.html
+++ b/pr/451/docs/noq/enum.ClosePathError.html
@@ -1,4 +1,4 @@
-ClosePathError in noq - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
+ClosePathError in noq - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
     MultipathNotNegotiated,
     ClosedPath,
     LastOpenPath,
@@ -7,8 +7,8 @@
 
§

ClosedPath

The path is already closed or was never opened

§

LastOpenPath

Cannot close the last remaining open path via the local API.

Use Connection::close to end the connection instead.

-

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for ClosePathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ClosePathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosePathError

Source§

fn eq(&self, other: &ClosePathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for ClosePathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ClosePathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosePathError

Source§

fn eq(&self, other: &ClosePathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.ConfigError.html b/pr/451/docs/noq/enum.ConfigError.html index 6813b219d..41ff58dec 100644 --- a/pr/451/docs/noq/enum.ConfigError.html +++ b/pr/451/docs/noq/enum.ConfigError.html @@ -1,4 +1,4 @@ -ConfigError in noq - Rust

ConfigError

Enum ConfigError 

Source
#[non_exhaustive]
pub enum ConfigError { +ConfigError in noq - Rust

ConfigError

Enum ConfigError 

Source
#[non_exhaustive]
pub enum ConfigError { OutOfBounds, }
Expand description

Errors in the configuration of an endpoint

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

OutOfBounds

Value exceeds supported bounds

diff --git a/pr/451/docs/noq/enum.ConnectError.html b/pr/451/docs/noq/enum.ConnectError.html index 07520f04b..2fdc2a63a 100644 --- a/pr/451/docs/noq/enum.ConnectError.html +++ b/pr/451/docs/noq/enum.ConnectError.html @@ -1,4 +1,4 @@ -ConnectError in noq - Rust

ConnectError

Enum ConnectError 

Source
pub enum ConnectError {
+ConnectError in noq - Rust

ConnectError

Enum ConnectError 

Source
pub enum ConnectError {
     EndpointStopping,
     CidsExhausted,
     InvalidServerName(String),
@@ -17,8 +17,8 @@
 
§

NoDefaultClientConfig

No default client configuration was set up

Use Endpoint::connect_with to specify a client configuration.

§

UnsupportedVersion

The local endpoint does not support the QUIC version specified in the client configuration

-

Trait Implementations§

Source§

impl Clone for ConnectError

Source§

fn clone(&self) -> ConnectError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<UnsupportedVersion> for ConnectError

Source§

fn from(_: UnsupportedVersion) -> ConnectError

Converts to this type from the input type.
Source§

impl PartialEq for ConnectError

Source§

fn eq(&self, other: &ConnectError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ConnectError

Source§

impl StructuralPartialEq for ConnectError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectError

Source§

fn clone(&self) -> ConnectError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<UnsupportedVersion> for ConnectError

Source§

fn from(_: UnsupportedVersion) -> ConnectError

Converts to this type from the input type.
Source§

impl PartialEq for ConnectError

Source§

fn eq(&self, other: &ConnectError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ConnectError

Source§

impl StructuralPartialEq for ConnectError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.ConnectionError.html b/pr/451/docs/noq/enum.ConnectionError.html index 60b0997ae..11de94844 100644 --- a/pr/451/docs/noq/enum.ConnectionError.html +++ b/pr/451/docs/noq/enum.ConnectionError.html @@ -1,4 +1,4 @@ -ConnectionError in noq - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
+ConnectionError in noq - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
     VersionMismatch,
     TransportError(Error),
     ConnectionClosed(ConnectionClose),
@@ -20,8 +20,8 @@ and §

LocallyClosed

The local application closed the connection

§

CidsExhausted

The connection could not be created because not enough of the CID space is available

Try using longer connection IDs.

-

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectionError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectionError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Close> for ConnectionError

Source§

fn from(x: Close) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<CloseReason> for ConnectionError

Source§

fn from(value: CloseReason) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ReadError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ResetError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for SendDatagramError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for StoppedError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for WriteError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: Error) -> ConnectionError

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

fn eq(&self, other: &ConnectionError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectionError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectionError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Close> for ConnectionError

Source§

fn from(x: Close) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<CloseReason> for ConnectionError

Source§

fn from(value: CloseReason) -> ConnectionError

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ReadError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for ResetError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for SendDatagramError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for StoppedError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<ConnectionError> for WriteError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: Error) -> ConnectionError

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

fn eq(&self, other: &ConnectionError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.Dir.html b/pr/451/docs/noq/enum.Dir.html index 183c2bdbf..016a0ae22 100644 --- a/pr/451/docs/noq/enum.Dir.html +++ b/pr/451/docs/noq/enum.Dir.html @@ -1,25 +1,25 @@ -Dir in noq - Rust

Dir

Enum Dir 

Source
pub enum Dir {
+Dir in noq - Rust

Dir

Enum Dir 

Source
pub enum Dir {
     Bi = 0,
     Uni = 1,
 }
Expand description

Whether a stream communicates data in both directions or only from the initiator

Variants§

§

Bi = 0

Data flows in both directions

§

Uni = 1

Data flows only from the stream’s initiator

-

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for Dir

Source§

fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Dir, Error>

Generate an arbitrary value of Self from the given unstructured data. Read more
Source§

fn arbitrary_take_rest(u: Unstructured<'arbitrary>) -> Result<Dir, Error>

Generate an arbitrary value of Self from the entirety of the given -unstructured data. Read more
Source§

fn size_hint(depth: usize) -> (usize, Option<usize>)

Get a size hint for how many bytes out of an Unstructured this type +

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for Dir

Source§

fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Dir, Error>

Generate an arbitrary value of Self from the given unstructured data. Read more
Source§

fn arbitrary_take_rest(u: Unstructured<'arbitrary>) -> Result<Dir, Error>

Generate an arbitrary value of Self from the entirety of the given +unstructured data. Read more
Source§

fn size_hint(depth: usize) -> (usize, Option<usize>)

Get a size hint for how many bytes out of an Unstructured this type needs to construct itself. Read more
§

fn try_size_hint( depth: usize, ) -> Result<(usize, Option<usize>), MaxRecursionReached>

Get a size hint for how many bytes out of an Unstructured this type -needs to construct itself. Read more
Source§

impl Clone for Dir

Source§

fn clone(&self) -> Dir

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Dir

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for Dir

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Hash for Dir

Source§

fn hash<__H>(&self, state: &mut __H)
where +needs to construct itself. Read more

Source§

impl Clone for Dir

Source§

fn clone(&self) -> Dir

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Dir

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for Dir

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Hash for Dir

Source§

fn hash<__H>(&self, state: &mut __H)
where __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for Dir

Source§

fn cmp(&self, other: &Dir) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for Dir

Source§

fn cmp(&self, other: &Dir) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for Dir

Source§

fn eq(&self, other: &Dir) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for Dir

Source§

fn partial_cmp(&self, other: &Dir) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more

Source§

impl PartialEq for Dir

Source§

fn eq(&self, other: &Dir) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for Dir

Source§

fn partial_cmp(&self, other: &Dir) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl Copy for Dir

Source§

impl Eq for Dir

Source§

impl StructuralPartialEq for Dir

Auto Trait Implementations§

§

impl Freeze for Dir

§

impl RefUnwindSafe for Dir

§

impl Send for Dir

§

impl Sync for Dir

§

impl Unpin for Dir

§

impl UnwindSafe for Dir

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl Copy for Dir

Source§

impl Eq for Dir

Source§

impl StructuralPartialEq for Dir

Auto Trait Implementations§

§

impl Freeze for Dir

§

impl RefUnwindSafe for Dir

§

impl Send for Dir

§

impl Sync for Dir

§

impl Unpin for Dir

§

impl UnwindSafe for Dir

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.EcnCodepoint.html b/pr/451/docs/noq/enum.EcnCodepoint.html index 98a256056..514051de8 100644 --- a/pr/451/docs/noq/enum.EcnCodepoint.html +++ b/pr/451/docs/noq/enum.EcnCodepoint.html @@ -1,4 +1,4 @@ -EcnCodepoint in noq - Rust

EcnCodepoint

Enum EcnCodepoint 

Source
#[repr(u8)]
pub enum EcnCodepoint { +EcnCodepoint in noq - Rust

EcnCodepoint

Enum EcnCodepoint 

Source
#[repr(u8)]
pub enum EcnCodepoint { Ect0 = 2, Ect1 = 1, Ce = 3, @@ -6,10 +6,10 @@

Variants§

§

Ect0 = 2

The ECT(0) codepoint, indicating that an endpoint is ECN-capable

§

Ect1 = 1

The ECT(1) codepoint, indicating that an endpoint is ECN-capable

§

Ce = 3

The CE codepoint, signalling that congestion was experienced

-

Implementations§

Source§

impl EcnCodepoint

Source

pub fn from_bits(x: u8) -> Option<EcnCodepoint>

Create new object from the given bits

-
Source

pub fn is_ce(self) -> bool

Returns whether the codepoint is a CE, signalling that congestion was experienced

-

Trait Implementations§

Source§

impl Clone for EcnCodepoint

Source§

fn clone(&self) -> EcnCodepoint

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EcnCodepoint

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl PartialEq for EcnCodepoint

Source§

fn eq(&self, other: &EcnCodepoint) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for EcnCodepoint

Source§

impl Eq for EcnCodepoint

Source§

impl StructuralPartialEq for EcnCodepoint

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Implementations§

Source§

impl EcnCodepoint

Source

pub fn from_bits(x: u8) -> Option<EcnCodepoint>

Create new object from the given bits

+
Source

pub fn is_ce(self) -> bool

Returns whether the codepoint is a CE, signalling that congestion was experienced

+

Trait Implementations§

Source§

impl Clone for EcnCodepoint

Source§

fn clone(&self) -> EcnCodepoint

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EcnCodepoint

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl PartialEq for EcnCodepoint

Source§

fn eq(&self, other: &EcnCodepoint) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for EcnCodepoint

Source§

impl Eq for EcnCodepoint

Source§

impl StructuralPartialEq for EcnCodepoint

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.FrameType.html b/pr/451/docs/noq/enum.FrameType.html index ded15b19f..5797f7858 100644 --- a/pr/451/docs/noq/enum.FrameType.html +++ b/pr/451/docs/noq/enum.FrameType.html @@ -1,4 +1,4 @@ -FrameType in noq - Rust

FrameType

Enum FrameType 

Source
pub enum FrameType {
+FrameType in noq - Rust

FrameType

Enum FrameType 

Source
pub enum FrameType {
 
Show 44 variants Padding, Ping, Ack, diff --git a/pr/451/docs/noq/enum.PathError.html b/pr/451/docs/noq/enum.PathError.html index 1d570cbef..64f02affc 100644 --- a/pr/451/docs/noq/enum.PathError.html +++ b/pr/451/docs/noq/enum.PathError.html @@ -1,4 +1,4 @@ -PathError in noq - Rust

PathError

Enum PathError 

Source
pub enum PathError {
+PathError in noq - Rust

PathError

Enum PathError 

Source
pub enum PathError {
     MultipathNotNegotiated,
     ServerSideNotAllowed,
     MaxPathIdReached,
@@ -12,8 +12,8 @@
 
§

RemoteCidsExhausted

No remote CIDs available to open a new path

§

ValidationFailed

Path could not be validated and will be abandoned

§

InvalidRemoteAddress(SocketAddr)

The remote address for the path is not supported by the endpoint

-

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for PathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for PathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for PathError

Source§

fn eq(&self, other: &PathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for PathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for PathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for PathError

Source§

fn eq(&self, other: &PathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.PathEvent.html b/pr/451/docs/noq/enum.PathEvent.html index b4d26b655..6b0f57a69 100644 --- a/pr/451/docs/noq/enum.PathEvent.html +++ b/pr/451/docs/noq/enum.PathEvent.html @@ -1,46 +1,48 @@ -PathEvent in noq - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
-    Established {
+PathEvent in noq - Rust

PathEvent

Enum PathEvent 

Source
#[non_exhaustive]
pub enum PathEvent { +
#[non_exhaustive]
Established { id: PathId, }, - Abandoned { +
#[non_exhaustive]
Abandoned { id: PathId, reason: PathAbandonReason, }, - Discarded { +
#[non_exhaustive]
Discarded { id: PathId, path_stats: Box<PathStats>, }, - RemoteStatus { +
#[non_exhaustive]
RemoteStatus { id: PathId, status: PathStatus, }, - ObservedAddr { +
#[non_exhaustive]
ObservedAddr { id: PathId, addr: SocketAddr, }, }
Expand description

Application events about paths

-

Variants§

§

Established

A new path has established connection with the peer.

-

Fields

§id: PathId

The path which can now be used for application data.

-
§

Abandoned

A path was abandoned and is no longer usable.

+

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

#[non_exhaustive]
Established

A new path has established connection with the peer.

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

The path which can now be used for application data.

+
§

#[non_exhaustive]
Abandoned

A path was abandoned and is no longer usable.

+

Note that this may be the first event for a path: If a path is abandoned +before having been established, no Self::Established event is emitted.

This event will always be followed by Self::Discarded after some time.

-

Fields

§id: PathId

With path was abandoned.

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

The path that was abandoned.

§reason: PathAbandonReason

Reason why this path was abandoned.

-
§

Discarded

A path was discarded and all remaining state for it has been removed.

+
§

#[non_exhaustive]
Discarded

A path was discarded and all remaining state for it has been removed.

This event is the last event for a path, and is always emitted after Self::Abandoned.

-

Fields

§id: PathId

Which path had its state dropped

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

Which path had its state dropped

§path_stats: Box<PathStats>

The final path stats, they are no longer available via Connection::stats

-
§

RemoteStatus

The remote changed the status of the path

+
§

#[non_exhaustive]
RemoteStatus

The remote changed the status of the path

The local status is not changed because of this event. It is up to the application to update the local status, which is used for packet scheduling, when the remote changes the status.

-

Fields

§id: PathId

Path which has changed status

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

Path which has changed status

§status: PathStatus

The new status set by the remote

-
§

ObservedAddr

Received an observation of our external address from the peer.

-

Fields

§id: PathId

Path over which the observed address was reported, PathId::ZERO when multipath is +

§

#[non_exhaustive]
ObservedAddr

Received an observation of our external address from the peer.

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

Path over which the observed address was reported, PathId::ZERO when multipath is not negotiated

§addr: SocketAddr

The address observed by the remote over this path

-

Trait Implementations§

Source§

impl Clone for PathEvent

Source§

fn clone(&self) -> PathEvent

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl PartialEq for PathEvent

Source§

fn eq(&self, other: &PathEvent) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathEvent

Source§

fn clone(&self) -> PathEvent

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl PartialEq for PathEvent

Source§

fn eq(&self, other: &PathEvent) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.PathStatus.html b/pr/451/docs/noq/enum.PathStatus.html index 8e85a1c2f..ec15a1976 100644 --- a/pr/451/docs/noq/enum.PathStatus.html +++ b/pr/451/docs/noq/enum.PathStatus.html @@ -1,4 +1,4 @@ -PathStatus in noq - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
+PathStatus in noq - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
     Available,
     Backup,
 }
Expand description

The QUIC-MULTIPATH path status

@@ -10,8 +10,8 @@ capacity.

§

Backup

Paths marked as backup will only be used if there are no available paths

If the max_idle_timeout is specified the path will be kept alive so that it does not expire.

-

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for PathStatus

Source§

fn default() -> PathStatus

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStatus

Source§

fn eq(&self, other: &PathStatus) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for PathStatus

Source§

fn default() -> PathStatus

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStatus

Source§

fn eq(&self, other: &PathStatus) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.ReadError.html b/pr/451/docs/noq/enum.ReadError.html index ee88e31be..42be837cb 100644 --- a/pr/451/docs/noq/enum.ReadError.html +++ b/pr/451/docs/noq/enum.ReadError.html @@ -1,4 +1,4 @@ -ReadError in noq - Rust

ReadError

Enum ReadError 

Source
pub enum ReadError {
+ReadError in noq - Rust

ReadError

Enum ReadError 

Source
pub enum ReadError {
     Reset(VarInt),
     ConnectionLost(ConnectionError),
     ClosedStream,
diff --git a/pr/451/docs/noq/enum.ReadExactError.html b/pr/451/docs/noq/enum.ReadExactError.html
index 0b972c610..6b0f809bf 100644
--- a/pr/451/docs/noq/enum.ReadExactError.html
+++ b/pr/451/docs/noq/enum.ReadExactError.html
@@ -1,4 +1,4 @@
-ReadExactError in noq - Rust

ReadExactError

Enum ReadExactError 

Source
pub enum ReadExactError {
+ReadExactError in noq - Rust

ReadExactError

Enum ReadExactError 

Source
pub enum ReadExactError {
     FinishedEarly(usize),
     ReadError(ReadError),
 }
Expand description

Errors that arise from reading from a stream.

diff --git a/pr/451/docs/noq/enum.ReadToEndError.html b/pr/451/docs/noq/enum.ReadToEndError.html index 7c91e932c..27a7cf695 100644 --- a/pr/451/docs/noq/enum.ReadToEndError.html +++ b/pr/451/docs/noq/enum.ReadToEndError.html @@ -1,4 +1,4 @@ -ReadToEndError in noq - Rust

ReadToEndError

Enum ReadToEndError 

Source
pub enum ReadToEndError {
+ReadToEndError in noq - Rust

ReadToEndError

Enum ReadToEndError 

Source
pub enum ReadToEndError {
     Read(ReadError),
     TooLong,
 }
Expand description

Errors from RecvStream::read_to_end

diff --git a/pr/451/docs/noq/enum.ResetError.html b/pr/451/docs/noq/enum.ResetError.html index bc3349d44..54175d61d 100644 --- a/pr/451/docs/noq/enum.ResetError.html +++ b/pr/451/docs/noq/enum.ResetError.html @@ -1,4 +1,4 @@ -ResetError in noq - Rust

ResetError

Enum ResetError 

Source
pub enum ResetError {
+ResetError in noq - Rust

ResetError

Enum ResetError 

Source
pub enum ResetError {
     ConnectionLost(ConnectionError),
     ZeroRttRejected,
 }
Expand description

Errors that arise while waiting for a stream to be reset

diff --git a/pr/451/docs/noq/enum.SendDatagramError.html b/pr/451/docs/noq/enum.SendDatagramError.html index 7b7c4bac5..166bb1057 100644 --- a/pr/451/docs/noq/enum.SendDatagramError.html +++ b/pr/451/docs/noq/enum.SendDatagramError.html @@ -1,4 +1,4 @@ -SendDatagramError in noq - Rust

SendDatagramError

Enum SendDatagramError 

Source
pub enum SendDatagramError {
+SendDatagramError in noq - Rust

SendDatagramError

Enum SendDatagramError 

Source
pub enum SendDatagramError {
     UnsupportedByPeer,
     Disabled,
     TooLarge,
@@ -10,8 +10,8 @@
 

Indicates that the path MTU minus overhead or the limit advertised by the peer has been exceeded.

§

ConnectionLost(ConnectionError)

The connection was lost

-

Trait Implementations§

Source§

impl Clone for SendDatagramError

Source§

fn clone(&self) -> SendDatagramError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SendDatagramError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for SendDatagramError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for SendDatagramError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ConnectionError> for SendDatagramError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for SendDatagramError

Source§

fn eq(&self, other: &SendDatagramError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for SendDatagramError

Source§

impl StructuralPartialEq for SendDatagramError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for SendDatagramError

Source§

fn clone(&self) -> SendDatagramError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SendDatagramError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for SendDatagramError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for SendDatagramError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ConnectionError> for SendDatagramError

Source§

fn from(source: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for SendDatagramError

Source§

fn eq(&self, other: &SendDatagramError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for SendDatagramError

Source§

impl StructuralPartialEq for SendDatagramError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.SetPathStatusError.html b/pr/451/docs/noq/enum.SetPathStatusError.html index d03b36116..70ad89393 100644 --- a/pr/451/docs/noq/enum.SetPathStatusError.html +++ b/pr/451/docs/noq/enum.SetPathStatusError.html @@ -1,11 +1,11 @@ -SetPathStatusError in noq - Rust

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
+SetPathStatusError in noq - Rust

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
     ClosedPath,
     MultipathNotNegotiated,
 }
Expand description

Error from setting path status

Variants§

§

ClosedPath

Error indicating that a path has not been opened or has already been abandoned

§

MultipathNotNegotiated

Error indicating that this operation requires multipath to be negotiated whereas it hasn’t been

-

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for SetPathStatusError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for SetPathStatusError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for SetPathStatusError

Source§

fn eq(&self, other: &SetPathStatusError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for SetPathStatusError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for SetPathStatusError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for SetPathStatusError

Source§

fn eq(&self, other: &SetPathStatusError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.Side.html b/pr/451/docs/noq/enum.Side.html index cd20a1d92..54b533f14 100644 --- a/pr/451/docs/noq/enum.Side.html +++ b/pr/451/docs/noq/enum.Side.html @@ -1,27 +1,27 @@ -Side in noq - Rust

Side

Enum Side 

Source
pub enum Side {
+Side in noq - Rust

Side

Enum Side 

Source
pub enum Side {
     Client = 0,
     Server = 1,
 }
Expand description

Whether an endpoint was the initiator of a connection

Variants§

§

Client = 0

The initiator of a connection

§

Server = 1

The acceptor of a connection

-

Implementations§

Source§

impl Side

Source

pub fn is_client(self) -> bool

Shorthand for self == Side::Client

-
Source

pub fn is_server(self) -> bool

Shorthand for self == Side::Server

-

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for Side

Source§

fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Side, Error>

Generate an arbitrary value of Self from the given unstructured data. Read more
Source§

fn arbitrary_take_rest(u: Unstructured<'arbitrary>) -> Result<Side, Error>

Generate an arbitrary value of Self from the entirety of the given -unstructured data. Read more
Source§

fn size_hint(depth: usize) -> (usize, Option<usize>)

Get a size hint for how many bytes out of an Unstructured this type +

Implementations§

Source§

impl Side

Source

pub fn is_client(self) -> bool

Shorthand for self == Side::Client

+
Source

pub fn is_server(self) -> bool

Shorthand for self == Side::Server

+

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for Side

Source§

fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Side, Error>

Generate an arbitrary value of Self from the given unstructured data. Read more
Source§

fn arbitrary_take_rest(u: Unstructured<'arbitrary>) -> Result<Side, Error>

Generate an arbitrary value of Self from the entirety of the given +unstructured data. Read more
Source§

fn size_hint(depth: usize) -> (usize, Option<usize>)

Get a size hint for how many bytes out of an Unstructured this type needs to construct itself. Read more
§

fn try_size_hint( depth: usize, ) -> Result<(usize, Option<usize>), MaxRecursionReached>

Get a size hint for how many bytes out of an Unstructured this type -needs to construct itself. Read more
Source§

impl Clone for Side

Source§

fn clone(&self) -> Side

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Side

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Hash for Side

Source§

fn hash<__H>(&self, state: &mut __H)
where +needs to construct itself. Read more

Source§

impl Clone for Side

Source§

fn clone(&self) -> Side

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Side

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Hash for Side

Source§

fn hash<__H>(&self, state: &mut __H)
where __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Not for Side

Source§

type Output = Side

The resulting type after applying the ! operator.
Source§

fn not(self) -> Side

Performs the unary ! operation. Read more
Source§

impl Ord for Side

Source§

fn cmp(&self, other: &Side) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Not for Side

Source§

type Output = Side

The resulting type after applying the ! operator.
Source§

fn not(self) -> Side

Performs the unary ! operation. Read more
Source§

impl Ord for Side

Source§

fn cmp(&self, other: &Side) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for Side

Source§

fn eq(&self, other: &Side) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for Side

Source§

fn partial_cmp(&self, other: &Side) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more

Source§

impl PartialEq for Side

Source§

fn eq(&self, other: &Side) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for Side

Source§

fn partial_cmp(&self, other: &Side) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl Copy for Side

Source§

impl Eq for Side

Source§

impl StructuralPartialEq for Side

Auto Trait Implementations§

§

impl Freeze for Side

§

impl RefUnwindSafe for Side

§

impl Send for Side

§

impl Sync for Side

§

impl Unpin for Side

§

impl UnwindSafe for Side

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl Copy for Side

Source§

impl Eq for Side

Source§

impl StructuralPartialEq for Side

Auto Trait Implementations§

§

impl Freeze for Side

§

impl RefUnwindSafe for Side

§

impl Send for Side

§

impl Sync for Side

§

impl Unpin for Side

§

impl UnwindSafe for Side

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/enum.StoppedError.html b/pr/451/docs/noq/enum.StoppedError.html index 2401e7bfe..30261742b 100644 --- a/pr/451/docs/noq/enum.StoppedError.html +++ b/pr/451/docs/noq/enum.StoppedError.html @@ -1,4 +1,4 @@ -StoppedError in noq - Rust

StoppedError

Enum StoppedError 

Source
pub enum StoppedError {
+StoppedError in noq - Rust

StoppedError

Enum StoppedError 

Source
pub enum StoppedError {
     ConnectionLost(ConnectionError),
     ZeroRttRejected,
 }
Expand description

Errors that arise while monitoring for a send stream stop from the peer

diff --git a/pr/451/docs/noq/enum.WriteError.html b/pr/451/docs/noq/enum.WriteError.html index 81128fb54..247aeb25c 100644 --- a/pr/451/docs/noq/enum.WriteError.html +++ b/pr/451/docs/noq/enum.WriteError.html @@ -1,4 +1,4 @@ -WriteError in noq - Rust

WriteError

Enum WriteError 

Source
pub enum WriteError {
+WriteError in noq - Rust

WriteError

Enum WriteError 

Source
pub enum WriteError {
     Stopped(VarInt),
     ConnectionLost(ConnectionError),
     ClosedStream,
diff --git a/pr/451/docs/noq/fn.default_runtime.html b/pr/451/docs/noq/fn.default_runtime.html
index b7d92b411..bfa4ecf3d 100644
--- a/pr/451/docs/noq/fn.default_runtime.html
+++ b/pr/451/docs/noq/fn.default_runtime.html
@@ -1,4 +1,4 @@
-default_runtime in noq - Rust

default_runtime

Function default_runtime 

Source
pub fn default_runtime() -> Option<Arc<dyn Runtime>>
Expand description

Automatically select an appropriate runtime from those enabled at compile time

+default_runtime in noq - Rust

default_runtime

Function default_runtime 

Source
pub fn default_runtime() -> Option<Arc<dyn Runtime>>
Expand description

Automatically select an appropriate runtime from those enabled at compile time

If runtime-tokio is enabled and this function is called from within a Tokio runtime context, then TokioRuntime is returned. Otherwise, if runtime-smol is enabled, SmolRuntime is returned. Otherwise, None is returned.

diff --git a/pr/451/docs/noq/index.html b/pr/451/docs/noq/index.html index 4c80a5d26..a4851a8f5 100644 --- a/pr/451/docs/noq/index.html +++ b/pr/451/docs/noq/index.html @@ -1,4 +1,4 @@ -noq - Rust

Crate noq

Crate noq 

Source
Expand description

QUIC transport protocol implementation

+noq - Rust

Crate noq

Crate noq 

Source
Expand description

QUIC transport protocol implementation

QUIC is a modern transport protocol addressing shortcomings of TCP, such as head-of-line blocking, poor security, slow handshakes, and inefficient congestion control. This crate provides a portable userspace implementation. It @@ -29,6 +29,6 @@ server, it is useful for a QUIC server to be identified by a certificate signed authority. If this is infeasible–for example, if servers are short-lived or not associated with a domain name–then as with TLS, self-signed certificates can be used to provide encryption alone.

-

Re-exports§

pub use rustls;
pub use udp;

Modules§

congestion
Logic for controlling the rate at which data is sent
crypto
Traits and implementations for the QUIC cryptography protocol

Structs§

Accept
Future produced by Endpoint::accept
AcceptBi
Future produced by Connection::accept_bi
AcceptUni
Future produced by Connection::accept_uni
AckFrequencyConfig
Parameters for controlling the peer’s acknowledgement frequency
AddressDiscovery
Stream produced by Path::observed_external_addr
ApplicationClose
Reason given by an application for closing the connection
BloomTokenLog
Bloom filter-based TokenLog
Chunk
A chunk of data from the receive stream
ClientConfig
Configuration for outgoing connections
Closed
State of a Connection at the moment it was closed.
ClosedPath
Error indicating that a path has not been opened or has already been abandoned
ClosedStream
Error indicating that a stream has not been opened or has already been finished or reset
Connecting
In-progress connection attempt future
Connection
A QUIC connection.
ConnectionClose
Reason given by the transport for closing the connection
ConnectionId
Protocol-level identifier for a connection.
ConnectionStats
Connection statistics.
DecryptedInitial
Decrypted payload of a QUIC Initial packet
Endpoint
A QUIC endpoint.
EndpointConfig
Global configuration for the endpoint, affecting all connections
EndpointStats
Statistics on Endpoint activity
FrameStats
Number of frames transmitted or received of each frame type.
IdleTimeout
Maximum duration of inactivity to accept before timing out the connection
Incoming
An incoming connection for which the server has not yet begun its part of the handshake
IncomingFuture
Basic adapter to let Incoming be await-ed like a Connecting
InvalidCid
The connection ID was not recognized by the ConnectionIdGenerator
Lagged
The receiver lagged too far behind.
MtuDiscoveryConfig
Parameters governing MTU discovery.
NatTraversalUpdates
A stream of NAT traversal updates for a connection.
NoneTokenLog
Null implementation of TokenLog, which never accepts tokens
NoneTokenStore
Null implementation of TokenStore, which does not store any tokens
ObservedExternalAddr
Watches the external address reported by the peer for this connection.
OnClosed
Future returned by Connection::on_closed
OpenBi
Future produced by Connection::open_bi
OpenPath
Future produced by crate::Connection::open_path
OpenUni
Future produced by Connection::open_uni
Path
An open network transmission within a multipath-enabled connection.
PathEvents
A stream of PathEvents for all paths in a connection.
PathId
Id representing different paths when using multipath extension
PathStats
Statistics related to a transmission path.
QlogConfig
Configuration for qlog trace logging.
QlogFileFactory
Enables writing qlog traces to a directory.
ReadDatagram
Future produced by Connection::read_datagram
RecvStream
A stream that can only be used to receive data
RetryError
Error for attempting to retry an Incoming which already bears a token from a previous retry
SendDatagram
Future produced by Connection::send_datagram_wait
SendStream
A stream that can only be used to send data
ServerConfig
Parameters governing incoming connections
SmolRuntime
A noq runtime for smol
StdSystemTime
Default implementation of TimeSource
Stopped
Future returned from SendStream::stopped.
StreamId
Identifier for a stream within a particular connection
TokenMemoryCache
TokenStore implementation that stores up to N tokens per server name for up to a +

Re-exports§

pub use rustls;
pub use udp;

Modules§

congestion
Logic for controlling the rate at which data is sent
crypto
Traits and implementations for the QUIC cryptography protocol

Structs§

Accept
Future produced by Endpoint::accept
AcceptBi
Future produced by Connection::accept_bi
AcceptUni
Future produced by Connection::accept_uni
AckFrequencyConfig
Parameters for controlling the peer’s acknowledgement frequency
AddressDiscovery
Stream produced by Path::observed_external_addr
ApplicationClose
Reason given by an application for closing the connection
BloomTokenLog
Bloom filter-based TokenLog
Chunk
A chunk of data from the receive stream
ClientConfig
Configuration for outgoing connections
Closed
State of a Connection at the moment it was closed.
ClosedPath
Error indicating that a path has not been opened or has already been abandoned
ClosedStream
Error indicating that a stream has not been opened or has already been finished or reset
Connecting
In-progress connection attempt future
Connection
A QUIC connection.
ConnectionClose
Reason given by the transport for closing the connection
ConnectionId
Protocol-level identifier for a connection.
ConnectionStats
Connection statistics.
DecryptedInitial
Decrypted payload of a QUIC Initial packet
Endpoint
A QUIC endpoint.
EndpointConfig
Global configuration for the endpoint, affecting all connections
EndpointStats
Statistics on Endpoint activity
FourTuple
Identifies a network path by the combination of remote and local addresses
FrameStats
Number of frames transmitted or received of each frame type.
IdleTimeout
Maximum duration of inactivity to accept before timing out the connection
Incoming
An incoming connection for which the server has not yet begun its part of the handshake
IncomingFuture
Basic adapter to let Incoming be await-ed like a Connecting
InvalidCid
The connection ID was not recognized by the ConnectionIdGenerator
Lagged
The receiver lagged too far behind.
MtuDiscoveryConfig
Parameters governing MTU discovery.
NatTraversalUpdates
A stream of NAT traversal updates for a connection.
NoneTokenLog
Null implementation of TokenLog, which never accepts tokens
NoneTokenStore
Null implementation of TokenStore, which does not store any tokens
ObservedExternalAddr
Watches the external address reported by the peer for this connection.
OnClosed
Future returned by Connection::on_closed
OpenBi
Future produced by Connection::open_bi
OpenPath
Future produced by crate::Connection::open_path
OpenUni
Future produced by Connection::open_uni
Path
An open network transmission within a multipath-enabled connection.
PathEvents
A stream of PathEvents for all paths in a connection.
PathId
Id representing different paths when using multipath extension
PathStats
Statistics related to a transmission path.
QlogConfig
Configuration for qlog trace logging.
QlogFileFactory
Enables writing qlog traces to a directory.
ReadDatagram
Future produced by Connection::read_datagram
RecvStream
A stream that can only be used to receive data
RetryError
Error for attempting to retry an Incoming which already bears a token from a previous retry
SendDatagram
Future produced by Connection::send_datagram_wait
SendStream
A stream that can only be used to send data
ServerConfig
Parameters governing incoming connections
SmolRuntime
A noq runtime for smol
StdSystemTime
Default implementation of TimeSource
Stopped
Future returned from SendStream::stopped.
StreamId
Identifier for a stream within a particular connection
TokenMemoryCache
TokenStore implementation that stores up to N tokens per server name for up to a limited number of server names, in-memory
TokenReuseError
Error for when a validation token may have been reused
TokioRuntime
A noq runtime for Tokio
Transmit
An outgoing packet
TransportConfig
Parameters governing the core QUIC state machine
TransportErrorCode
Transport-level error code
UdpStats
Statistics about UDP datagrams transmitted or received on a connection.
UnorderedRecvStream
A stream that can be used to receive data out-of-order.
ValidationTokenConfig
Configuration for sending and handling validation tokens in incoming connections
VarInt
An integer less than 2^62
VarIntBoundsExceeded
Error returned when constructing a VarInt from a value >= 2^62
WeakConnectionHandle
A handle to some connection internals, use with care.
WeakPathHandle
Weak handle for a Path that does not keep the connection alive.
ZeroRttAccepted
Future that completes when a connection is fully established

Enums§

ClosePathError
Errors triggered when abandoning a path
ConfigError
Errors in the configuration of an endpoint
ConnectError
Errors in the parameters being used to create a new connection
ConnectionError
Reasons why a connection might be lost
Dir
Whether a stream communicates data in both directions or only from the initiator
EcnCodepoint
Explicit congestion notification codepoint
FrameType
PathError
Errors that might trigger a path being closed
PathEvent
Application events about paths
PathStatus
The QUIC-MULTIPATH path status
ReadError
Errors that arise from reading from a stream.
ReadExactError
Errors that arise from reading from a stream.
ReadToEndError
Errors from RecvStream::read_to_end
ResetError
Errors that arise while waiting for a stream to be reset
SendDatagramError
Errors that can arise when sending a datagram
SetPathStatusError
Error from setting path status
Side
Whether an endpoint was the initiator of a connection
StoppedError
Errors that arise while monitoring for a send stream stop from the peer
WriteError
Errors that arise from writing to a stream

Traits§

AsyncTimer
Abstract implementation of an async timer for runtime independence
AsyncUdpSocket
Abstract implementation of a UDP socket for runtime independence
ConnectionIdGenerator
Generates connection IDs for incoming connections
NetworkChangeHint
Hints when the caller identifies a network change.
QlogFactory
Constructs a QlogConfig for individual connections.
Runtime
Abstracts I/O and timer operations for runtime independence
TimeSource
Object to get current SystemTime
TokenLog
Responsible for limiting clients’ ability to reuse validation tokens
TokenStore
Responsible for storing validation tokens received from servers and retrieving them for use in subsequent connections
UdpSender
An object for asynchronously writing to an associated AsyncUdpSocket.

Functions§

default_runtime
Automatically select an appropriate runtime from those enabled at compile time
\ No newline at end of file diff --git a/pr/451/docs/noq/sidebar-items.js b/pr/451/docs/noq/sidebar-items.js index 8e14b6da5..f816c2154 100644 --- a/pr/451/docs/noq/sidebar-items.js +++ b/pr/451/docs/noq/sidebar-items.js @@ -1 +1 @@ -window.SIDEBAR_ITEMS = {"enum":["ClosePathError","ConfigError","ConnectError","ConnectionError","Dir","EcnCodepoint","FrameType","PathError","PathEvent","PathStatus","ReadError","ReadExactError","ReadToEndError","ResetError","SendDatagramError","SetPathStatusError","Side","StoppedError","WriteError"],"fn":["default_runtime"],"mod":["congestion","crypto"],"struct":["Accept","AcceptBi","AcceptUni","AckFrequencyConfig","AddressDiscovery","ApplicationClose","BloomTokenLog","Chunk","ClientConfig","Closed","ClosedPath","ClosedStream","Connecting","Connection","ConnectionClose","ConnectionId","ConnectionStats","DecryptedInitial","Endpoint","EndpointConfig","EndpointStats","FrameStats","IdleTimeout","Incoming","IncomingFuture","InvalidCid","Lagged","MtuDiscoveryConfig","NatTraversalUpdates","NoneTokenLog","NoneTokenStore","ObservedExternalAddr","OnClosed","OpenBi","OpenPath","OpenUni","Path","PathEvents","PathId","PathStats","QlogConfig","QlogFileFactory","ReadDatagram","RecvStream","RetryError","SendDatagram","SendStream","ServerConfig","SmolRuntime","StdSystemTime","Stopped","StreamId","TokenMemoryCache","TokenReuseError","TokioRuntime","Transmit","TransportConfig","TransportErrorCode","UdpStats","UnorderedRecvStream","ValidationTokenConfig","VarInt","VarIntBoundsExceeded","WeakConnectionHandle","WeakPathHandle","ZeroRttAccepted"],"trait":["AsyncTimer","AsyncUdpSocket","ConnectionIdGenerator","NetworkChangeHint","QlogFactory","Runtime","TimeSource","TokenLog","TokenStore","UdpSender"]}; \ No newline at end of file +window.SIDEBAR_ITEMS = {"enum":["ClosePathError","ConfigError","ConnectError","ConnectionError","Dir","EcnCodepoint","FrameType","PathError","PathEvent","PathStatus","ReadError","ReadExactError","ReadToEndError","ResetError","SendDatagramError","SetPathStatusError","Side","StoppedError","WriteError"],"fn":["default_runtime"],"mod":["congestion","crypto"],"struct":["Accept","AcceptBi","AcceptUni","AckFrequencyConfig","AddressDiscovery","ApplicationClose","BloomTokenLog","Chunk","ClientConfig","Closed","ClosedPath","ClosedStream","Connecting","Connection","ConnectionClose","ConnectionId","ConnectionStats","DecryptedInitial","Endpoint","EndpointConfig","EndpointStats","FourTuple","FrameStats","IdleTimeout","Incoming","IncomingFuture","InvalidCid","Lagged","MtuDiscoveryConfig","NatTraversalUpdates","NoneTokenLog","NoneTokenStore","ObservedExternalAddr","OnClosed","OpenBi","OpenPath","OpenUni","Path","PathEvents","PathId","PathStats","QlogConfig","QlogFileFactory","ReadDatagram","RecvStream","RetryError","SendDatagram","SendStream","ServerConfig","SmolRuntime","StdSystemTime","Stopped","StreamId","TokenMemoryCache","TokenReuseError","TokioRuntime","Transmit","TransportConfig","TransportErrorCode","UdpStats","UnorderedRecvStream","ValidationTokenConfig","VarInt","VarIntBoundsExceeded","WeakConnectionHandle","WeakPathHandle","ZeroRttAccepted"],"trait":["AsyncTimer","AsyncUdpSocket","ConnectionIdGenerator","NetworkChangeHint","QlogFactory","Runtime","TimeSource","TokenLog","TokenStore","UdpSender"]}; \ No newline at end of file diff --git a/pr/451/docs/noq/struct.Accept.html b/pr/451/docs/noq/struct.Accept.html index b9e3a15b2..cf5bc1d1c 100644 --- a/pr/451/docs/noq/struct.Accept.html +++ b/pr/451/docs/noq/struct.Accept.html @@ -1,6 +1,6 @@ -Accept in noq - Rust

Accept

Struct Accept 

Source
pub struct Accept<'a> { /* private fields */ }
Expand description

Future produced by Endpoint::accept

-

Trait Implementations§

Source§

impl Future for Accept<'_>

Source§

type Output = Option<Incoming>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering -the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for Accept<'a>
where +Accept in noq - Rust

Accept

Struct Accept 

Source
pub struct Accept<'a> { /* private fields */ }
Expand description

Future produced by Endpoint::accept

+

Trait Implementations§

Source§

impl Future for Accept<'_>

Source§

type Output = Option<Incoming>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering +the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for Accept<'a>
where PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,

Auto Trait Implementations§

§

impl<'a> !Freeze for Accept<'a>

§

impl<'a> !RefUnwindSafe for Accept<'a>

§

impl<'a> Send for Accept<'a>

§

impl<'a> Sync for Accept<'a>

§

impl<'a> !UnwindSafe for Accept<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.AcceptBi.html b/pr/451/docs/noq/struct.AcceptBi.html index 3e461acfa..f55b681ac 100644 --- a/pr/451/docs/noq/struct.AcceptBi.html +++ b/pr/451/docs/noq/struct.AcceptBi.html @@ -1,6 +1,6 @@ -AcceptBi in noq - Rust

AcceptBi

Struct AcceptBi 

Source
pub struct AcceptBi<'a> { /* private fields */ }
Expand description

Future produced by Connection::accept_bi

-

Trait Implementations§

Source§

impl Future for AcceptBi<'_>

Source§

type Output = Result<(SendStream, RecvStream), ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering -the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for AcceptBi<'a>
where +AcceptBi in noq - Rust

AcceptBi

Struct AcceptBi 

Source
pub struct AcceptBi<'a> { /* private fields */ }
Expand description

Future produced by Connection::accept_bi

+

Trait Implementations§

Source§

impl Future for AcceptBi<'_>

Source§

type Output = Result<(SendStream, RecvStream), ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering +the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for AcceptBi<'a>
where PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,

Auto Trait Implementations§

§

impl<'a> !Freeze for AcceptBi<'a>

§

impl<'a> !RefUnwindSafe for AcceptBi<'a>

§

impl<'a> Send for AcceptBi<'a>

§

impl<'a> Sync for AcceptBi<'a>

§

impl<'a> !UnwindSafe for AcceptBi<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.AcceptUni.html b/pr/451/docs/noq/struct.AcceptUni.html index 37d0e3897..37056003a 100644 --- a/pr/451/docs/noq/struct.AcceptUni.html +++ b/pr/451/docs/noq/struct.AcceptUni.html @@ -1,6 +1,6 @@ -AcceptUni in noq - Rust

AcceptUni

Struct AcceptUni 

Source
pub struct AcceptUni<'a> { /* private fields */ }
Expand description

Future produced by Connection::accept_uni

-

Trait Implementations§

Source§

impl Future for AcceptUni<'_>

Source§

type Output = Result<RecvStream, ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering -the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for AcceptUni<'a>
where +AcceptUni in noq - Rust

AcceptUni

Struct AcceptUni 

Source
pub struct AcceptUni<'a> { /* private fields */ }
Expand description

Future produced by Connection::accept_uni

+

Trait Implementations§

Source§

impl Future for AcceptUni<'_>

Source§

type Output = Result<RecvStream, ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering +the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for AcceptUni<'a>
where PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,

Auto Trait Implementations§

§

impl<'a> !Freeze for AcceptUni<'a>

§

impl<'a> !RefUnwindSafe for AcceptUni<'a>

§

impl<'a> Send for AcceptUni<'a>

§

impl<'a> Sync for AcceptUni<'a>

§

impl<'a> !UnwindSafe for AcceptUni<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.AckFrequencyConfig.html b/pr/451/docs/noq/struct.AckFrequencyConfig.html index 24267c9b1..3f1c20464 100644 --- a/pr/451/docs/noq/struct.AckFrequencyConfig.html +++ b/pr/451/docs/noq/struct.AckFrequencyConfig.html @@ -1,4 +1,4 @@ -AckFrequencyConfig in noq - Rust

AckFrequencyConfig

Struct AckFrequencyConfig 

Source
pub struct AckFrequencyConfig { /* private fields */ }
Expand description

Parameters for controlling the peer’s acknowledgement frequency

+AckFrequencyConfig in noq - Rust

AckFrequencyConfig

Struct AckFrequencyConfig 

Source
pub struct AckFrequencyConfig { /* private fields */ }
Expand description

Parameters for controlling the peer’s acknowledgement frequency

The parameters provided in this config will be sent to the peer at the beginning of the connection, so it can take them into account when sending acknowledgements (see each parameter’s description for details on how it influences acknowledgement frequency).

@@ -7,7 +7,7 @@ description for details on how it influences acknowledgement frequency).

The defaults produce behavior slightly different than the behavior without this extension, because they change the way reordered packets are handled (see AckFrequencyConfig::reordering_threshold for details).

-

Implementations§

Source§

impl AckFrequencyConfig

Implementations§

Source§

impl AckFrequencyConfig

Source

pub fn ack_eliciting_threshold( &mut self, value: VarInt, ) -> &mut AckFrequencyConfig

The ack-eliciting threshold we will request the peer to use

@@ -17,7 +17,7 @@ without immediately sending an ACK.

ack-eliciting packets have been received. A value of 0 results in a receiver immediately acknowledging every ack-eliciting packet.

Defaults to 1, which sends ACK frames for every other ack-eliciting packet.

-
Source

pub fn max_ack_delay( +

Source

pub fn max_ack_delay( &mut self, value: Option<Duration>, ) -> &mut AckFrequencyConfig

The max_ack_delay we will request the peer to use

@@ -27,7 +27,7 @@ an ACK when the ack-eliciting threshold hasn’t been reached.

transport parameter, and at most the greater of the current path RTT or 25ms.

Defaults to None, in which case the peer’s original max_ack_delay will be used, as obtained from its transport parameters.

-
Source

pub fn reordering_threshold(&mut self, value: VarInt) -> &mut AckFrequencyConfig

The reordering threshold we will request the peer to use

+
Source

pub fn reordering_threshold(&mut self, value: VarInt) -> &mut AckFrequencyConfig

The reordering threshold we will request the peer to use

This threshold represents the amount of out-of-order packets that will trigger an endpoint to send an ACK, without waiting for ack_eliciting_threshold to be exceeded or for max_ack_delay to be elapsed.

@@ -37,7 +37,7 @@ behavior when the extension is disabled).

It is recommended to set this value to TransportConfig::packet_threshold minus one. Since the default value for TransportConfig::packet_threshold is 3, this value defaults to 2.

-

Trait Implementations§

Source§

impl Clone for AckFrequencyConfig

Source§

fn clone(&self) -> AckFrequencyConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for AckFrequencyConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for AckFrequencyConfig

Source§

fn default() -> AckFrequencyConfig

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for AckFrequencyConfig

Source§

fn clone(&self) -> AckFrequencyConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for AckFrequencyConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for AckFrequencyConfig

Source§

fn default() -> AckFrequencyConfig

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.AddressDiscovery.html b/pr/451/docs/noq/struct.AddressDiscovery.html index 32e720a5e..76214f0c9 100644 --- a/pr/451/docs/noq/struct.AddressDiscovery.html +++ b/pr/451/docs/noq/struct.AddressDiscovery.html @@ -1,7 +1,7 @@ -AddressDiscovery in noq - Rust

AddressDiscovery

Struct AddressDiscovery 

Source
pub struct AddressDiscovery { /* private fields */ }
Expand description

Stream produced by Path::observed_external_addr

+AddressDiscovery in noq - Rust

AddressDiscovery

Struct AddressDiscovery 

Source
pub struct AddressDiscovery { /* private fields */ }
Expand description

Stream produced by Path::observed_external_addr

This will always return the external address most recently reported by the remote over this path. If the extension is not negotiated, this stream will never return.

-

Trait Implementations§

Source§

impl Stream for AddressDiscovery

Source§

type Item = SocketAddr

Values yielded by the stream.
Source§

fn poll_next( +

Trait Implementations§

Source§

impl Stream for AddressDiscovery

Source§

type Item = SocketAddr

Values yielded by the stream.
Source§

fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll<Option<Self::Item>>

Attempt to pull out the next value of this stream, registering the diff --git a/pr/451/docs/noq/struct.ApplicationClose.html b/pr/451/docs/noq/struct.ApplicationClose.html index e13b4bff3..dfe16914e 100644 --- a/pr/451/docs/noq/struct.ApplicationClose.html +++ b/pr/451/docs/noq/struct.ApplicationClose.html @@ -1,4 +1,4 @@ -ApplicationClose in noq - Rust

ApplicationClose

Struct ApplicationClose 

Source
pub struct ApplicationClose {
+ApplicationClose in noq - Rust

ApplicationClose

Struct ApplicationClose 

Source
pub struct ApplicationClose {
     pub error_code: VarInt,
     pub reason: Bytes,
 }
Expand description

Reason given by an application for closing the connection

diff --git a/pr/451/docs/noq/struct.BloomTokenLog.html b/pr/451/docs/noq/struct.BloomTokenLog.html index c7937e4fa..b7023342b 100644 --- a/pr/451/docs/noq/struct.BloomTokenLog.html +++ b/pr/451/docs/noq/struct.BloomTokenLog.html @@ -1,4 +1,4 @@ -BloomTokenLog in noq - Rust

BloomTokenLog

Struct BloomTokenLog 

Source
pub struct BloomTokenLog(/* private fields */);
Expand description

Bloom filter-based TokenLog

+BloomTokenLog in noq - Rust

BloomTokenLog

Struct BloomTokenLog 

Source
pub struct BloomTokenLog(/* private fields */);
Expand description

Bloom filter-based TokenLog

Parameterizable over an approximate maximum number of bytes to allocate. Starts out by storing used tokens in a hash set. Once the hash set becomes too large, converts it to a bloom filter. This achieves a memory profile of linear growth with an upper bound.

diff --git a/pr/451/docs/noq/struct.Chunk.html b/pr/451/docs/noq/struct.Chunk.html index 036e1b9f0..f21031649 100644 --- a/pr/451/docs/noq/struct.Chunk.html +++ b/pr/451/docs/noq/struct.Chunk.html @@ -1,4 +1,4 @@ -Chunk in noq - Rust

Chunk

Struct Chunk 

Source
pub struct Chunk {
+Chunk in noq - Rust

Chunk

Struct Chunk 

Source
pub struct Chunk {
     pub offset: u64,
     pub bytes: Bytes,
 }
Expand description

A chunk of data from the receive stream

diff --git a/pr/451/docs/noq/struct.ClientConfig.html b/pr/451/docs/noq/struct.ClientConfig.html index 21299ee51..22d612990 100644 --- a/pr/451/docs/noq/struct.ClientConfig.html +++ b/pr/451/docs/noq/struct.ClientConfig.html @@ -1,4 +1,4 @@ -ClientConfig in noq - Rust

ClientConfig

Struct ClientConfig 

Source
#[non_exhaustive]
pub struct ClientConfig { /* private fields */ }
Expand description

Configuration for outgoing connections

+ClientConfig in noq - Rust

ClientConfig

Struct ClientConfig 

Source
#[non_exhaustive]
pub struct ClientConfig { /* private fields */ }
Expand description

Configuration for outgoing connections

Default values should be suitable for most internet applications.

Implementations§

Source§

impl ClientConfig

Source

pub fn new(crypto: Arc<dyn ClientConfig>) -> ClientConfig

Create a default config with a particular cryptographic config

Source

pub fn initial_dst_cid_provider( diff --git a/pr/451/docs/noq/struct.Closed.html b/pr/451/docs/noq/struct.Closed.html index d7ec51bac..395ba0fba 100644 --- a/pr/451/docs/noq/struct.Closed.html +++ b/pr/451/docs/noq/struct.Closed.html @@ -1,4 +1,4 @@ -Closed in noq - Rust

Closed

Struct Closed 

Source
#[non_exhaustive]
pub struct Closed { +Closed in noq - Rust

Closed

Struct Closed 

Source
#[non_exhaustive]
pub struct Closed { pub reason: ConnectionError, pub stats: ConnectionStats, pub path_stats: Vec<(PathId, PathStats)>, @@ -10,7 +10,7 @@

This includes paths that haven’t been discarded at close time, plus any already-discarded paths whose final stats had been retained because a Path or WeakPathHandle handle was kept alive.

-

Trait Implementations§

Source§

impl Clone for Closed

Source§

fn clone(&self) -> Closed

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Closed

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !Freeze for Closed

§

impl !RefUnwindSafe for Closed

§

impl Send for Closed

§

impl Sync for Closed

§

impl Unpin for Closed

§

impl !UnwindSafe for Closed

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for Closed

Source§

fn clone(&self) -> Closed

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Closed

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !Freeze for Closed

§

impl !RefUnwindSafe for Closed

§

impl Send for Closed

§

impl Sync for Closed

§

impl Unpin for Closed

§

impl !UnwindSafe for Closed

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.ClosedPath.html b/pr/451/docs/noq/struct.ClosedPath.html index 75f10da12..df4a0c642 100644 --- a/pr/451/docs/noq/struct.ClosedPath.html +++ b/pr/451/docs/noq/struct.ClosedPath.html @@ -1,6 +1,6 @@ -ClosedPath in noq - Rust

ClosedPath

Struct ClosedPath 

Source
pub struct ClosedPath { /* private fields */ }
Expand description

Error indicating that a path has not been opened or has already been abandoned

-

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedPath

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedPath

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosedPath

Source§

fn eq(&self, other: &ClosedPath) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +ClosedPath in noq - Rust

ClosedPath

Struct ClosedPath 

Source
pub struct ClosedPath { /* private fields */ }
Expand description

Error indicating that a path has not been opened or has already been abandoned

+

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedPath

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedPath

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosedPath

Source§

fn eq(&self, other: &ClosedPath) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.ClosedStream.html b/pr/451/docs/noq/struct.ClosedStream.html index 23ec58b77..03556400a 100644 --- a/pr/451/docs/noq/struct.ClosedStream.html +++ b/pr/451/docs/noq/struct.ClosedStream.html @@ -1,6 +1,6 @@ -ClosedStream in noq - Rust

ClosedStream

Struct ClosedStream 

Source
pub struct ClosedStream { /* private fields */ }
Expand description

Error indicating that a stream has not been opened or has already been finished or reset

-

Trait Implementations§

Source§

impl Clone for ClosedStream

Source§

fn clone(&self) -> ClosedStream

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedStream

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedStream

Source§

fn default() -> ClosedStream

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedStream

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedStream

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ClosedStream> for WriteError

Source§

fn from(_: ClosedStream) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ClosedStream

Source§

fn eq(&self, other: &ClosedStream) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ClosedStream

Source§

impl StructuralPartialEq for ClosedStream

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +ClosedStream in noq - Rust

ClosedStream

Struct ClosedStream 

Source
pub struct ClosedStream { /* private fields */ }
Expand description

Error indicating that a stream has not been opened or has already been finished or reset

+

Trait Implementations§

Source§

impl Clone for ClosedStream

Source§

fn clone(&self) -> ClosedStream

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedStream

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedStream

Source§

fn default() -> ClosedStream

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedStream

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedStream

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ClosedStream> for WriteError

Source§

fn from(_: ClosedStream) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ClosedStream

Source§

fn eq(&self, other: &ClosedStream) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ClosedStream

Source§

impl StructuralPartialEq for ClosedStream

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.Connecting.html b/pr/451/docs/noq/struct.Connecting.html index db197a0ae..72832fb2c 100644 --- a/pr/451/docs/noq/struct.Connecting.html +++ b/pr/451/docs/noq/struct.Connecting.html @@ -1,4 +1,4 @@ -Connecting in noq - Rust

Connecting

Struct Connecting 

Source
pub struct Connecting { /* private fields */ }
Expand description

In-progress connection attempt future

+Connecting in noq - Rust

Connecting

Struct Connecting 

Source
pub struct Connecting { /* private fields */ }
Expand description

In-progress connection attempt future

Implementations§

Source§

impl Connecting

Source

pub fn into_0rtt(self) -> Result<(Connection, ZeroRttAccepted), Self>

Convert into a 0-RTT or 0.5-RTT connection at the cost of weakened security

Returns Ok immediately if the local endpoint is able to attempt sending 0/0.5-RTT data. If so, the returned Connection can be used to send application data without waiting for diff --git a/pr/451/docs/noq/struct.Connection.html b/pr/451/docs/noq/struct.Connection.html index a5137126e..7e973b82d 100644 --- a/pr/451/docs/noq/struct.Connection.html +++ b/pr/451/docs/noq/struct.Connection.html @@ -1,4 +1,4 @@ -Connection in noq - Rust

Connection

Struct Connection 

Source
pub struct Connection(/* private fields */);
Expand description

A QUIC connection.

+Connection in noq - Rust

Connection

Struct Connection 

Source
pub struct Connection(/* private fields */);
Expand description

A QUIC connection.

If all references to a connection (including every clone of the Connection handle, streams of incoming streams, and the various stream types) have been dropped, then the connection will be automatically closed with an error_code of 0 and an empty reason. You can also close the @@ -8,7 +8,7 @@ receiving CONNECTION_CLOSE the peer may drop any stream data not yet de application. Connection::close() describes in more detail how to gracefully close a connection without losing application data.

May be cloned to obtain another handle to the same connection.

-

Implementations§

Source§

impl Connection

Source

pub fn weak_handle(&self) -> WeakConnectionHandle

Returns a weak reference to the inner connection struct.

+

Implementations§

Source§

impl Connection

Source

pub fn weak_handle(&self) -> WeakConnectionHandle

Returns a weak reference to the inner connection struct.

Source

pub fn open_uni(&self) -> OpenUni<'_>

Initiate a new outgoing unidirectional stream.

Streams are cheap and instantaneous to open unless blocked by flow control. As a consequence, the peer won’t be notified that a stream has been opened until the stream is @@ -24,17 +24,25 @@ anything to Connection is able to accept_bi(). Calling open_bi() then waiting on the RecvStream without writing anything to SendStream will never succeed.

Source

pub fn read_datagram(&self) -> ReadDatagram<'_>

Receive an application datagram

-
Source

pub fn open_path_ensure( +

Source

pub fn open_path_ensure( &self, - addr: SocketAddr, + network_path: impl Into<FourTuple>, initial_status: PathStatus, -) -> OpenPath

Opens a new path if no path exists yet for the remote address.

+) -> OpenPath

Opens a new path if no path exists yet for network_path.

+

If network_path has no local IP set, then this will open a new path +if no path exists for this remote address, independent of the existing +path’s local IP. If a local IP is set, it will match against the full +four-tuple of existing paths.

Otherwise behaves exactly as open_path.

-
Source

pub fn open_path( +

Source

pub fn open_path( &self, - addr: SocketAddr, + network_path: impl Into<FourTuple>, initial_status: PathStatus, ) -> OpenPath

Opens an additional path if the multipath extension is negotiated.

+

This function takes a FourTuple, which contains the remote address and an optional +local IP. If the local IP is set, the path will be opened with this source address, +and the endpoint must support sending from that IP address. You can also pass a +SocketAddr to only set the remote address and leave the local IP interface unspecified.

The returned future completes once the path is either fully opened and ready to carry application data, or if there was an error.

Dropping the returned future does not cancel the opening of the path, the @@ -45,34 +53,34 @@ the path opens. The OpenPath::path_id will return None and the future will be ready immediately. If the failure happens later, a PathEvent will be emitted.

-
Source

pub fn path(&self, id: PathId) -> Option<Path>

Returns the Path structure of an open path

-
Source

pub fn path_events(&self) -> PathEvents

A stream of PathEvents for all paths in this connection.

+
Source

pub fn path(&self, id: PathId) -> Option<Path>

Returns the Path structure of an open path

+
Source

pub fn path_events(&self) -> PathEvents

A stream of PathEvents for all paths in this connection.

The stream will yield a PathEvent whenever there is a change in the state of any path in this connection. The events need to be processed immediately, since there isn’t an unbounded buffer for them.

If processing of events lags behind too much, you will get an error of type crate::Lagged indicating how many events were lost. The stream continues after a lag, delivering the oldest retained message next.

-
Source

pub fn nat_traversal_updates(&self) -> NatTraversalUpdates

A stream of NAT traversal updates for this connection.

+
Source

pub fn nat_traversal_updates(&self) -> NatTraversalUpdates

A stream of NAT traversal updates for this connection.

The events need to be processed immediately, since there isn’t an unbounded buffer for them.

If processing of events lags behind too much, you will get an error of type crate::Lagged indicating how many events were lost. The stream continues after a lag, delivering the oldest retained message next.

-
Source

pub async fn closed(&self) -> ConnectionError

Wait for the connection to be closed for any reason

+
Source

pub async fn closed(&self) -> ConnectionError

Wait for the connection to be closed for any reason

Despite the return type’s name, closed connections are often not an error condition at the application layer. Cases that might be routine include ConnectionError::LocallyClosed and ConnectionError::ApplicationClosed.

-
Source

pub fn on_closed(&self) -> OnClosed

Wait for the connection to be closed without keeping a strong reference to the connection

+
Source

pub fn on_closed(&self) -> OnClosed

Wait for the connection to be closed without keeping a strong reference to the connection

Returns a future that resolves, once the connection is closed, to a Closed struct describing the close reason and final connection and per-path statistics.

Calling Self::closed keeps the connection alive until it is either closed locally via Connection::close or closed by the remote peer. This function instead does not keep the connection itself alive, so if all other clones of the connection are dropped, the connection will be closed implicitly even if there are futures returned from this function still being awaited.

-
Source

pub fn close_reason(&self) -> Option<ConnectionError>

Whether the connection is closed, and why.

+
Source

pub fn close_reason(&self) -> Option<ConnectionError>

Whether the connection is closed, and why.

The close_reason is always set to Some(ConnectionError) when a socket is closed; whether it was closed manually by calling Connection::close() or due to an internal error (such as an idle timeout or the peer closing the connection).

Note: when the connection is closed, connection.close_reason().is_some() will always be true.

-
Source

pub fn close(&self, error_code: VarInt, reason: &[u8])

Close the connection immediately.

+
Source

pub fn close(&self, error_code: VarInt, reason: &[u8])

Close the connection immediately.

Pending operations will fail immediately with ConnectionError::LocallyClosed. No more data is sent to the peer and the peer may drop buffered data upon receiving the CONNECTION_CLOSE frame.

@@ -93,7 +101,7 @@ endpoint. Once the local side sends a CONNECTION_CLOSE frame in response to call close() the remote endpoint may drop any data it received but is as yet undelivered to the application, including data that was acknowledged as received to the local endpoint.

-
Source

pub async fn handshake_confirmed(&self) -> Result<(), ConnectionError>

Wait for the handshake to be confirmed.

+
Source

pub async fn handshake_confirmed(&self) -> Result<(), ConnectionError>

Wait for the handshake to be confirmed.

As a server, who must be authenticated by clients, this happens when the handshake completes upon receiving a TLS Finished message from the client. @@ -101,44 +109,44 @@ In return, the server send a HANDSHAKE_DONE frame.

As a client, this happens when receiving a HANDSHAKE_DONE frame. At this point, the server has either accepted our authentication, or, if client authentication is not required, accepted our lack of authentication.

-
Source

pub fn send_datagram(&self, data: Bytes) -> Result<(), SendDatagramError>

Transmit data as an unreliable, unordered application datagram

+
Source

pub fn send_datagram(&self, data: Bytes) -> Result<(), SendDatagramError>

Transmit data as an unreliable, unordered application datagram

Application datagrams are a low-level primitive. They may be lost or delivered out of order, and data must both fit inside a single QUIC packet and be smaller than the maximum dictated by the peer.

Previously queued datagrams which are still unsent may be discarded to make space for this datagram, in order of oldest to newest.

-
Source

pub fn send_datagram_wait(&self, data: Bytes) -> SendDatagram<'_>

Transmit data as an unreliable, unordered application datagram

+
Source

pub fn send_datagram_wait(&self, data: Bytes) -> SendDatagram<'_>

Transmit data as an unreliable, unordered application datagram

Unlike send_datagram(), this method will wait for buffer space during congestion conditions, which effectively prioritizes old datagrams over new datagrams.

See send_datagram() for details.

-
Source

pub fn max_datagram_size(&self) -> Option<usize>

Compute the maximum size of datagrams that may be passed to send_datagram().

+
Source

pub fn max_datagram_size(&self) -> Option<usize>

Compute the maximum size of datagrams that may be passed to send_datagram().

Returns None if datagrams are unsupported by the peer or disabled locally.

This may change over the lifetime of a connection according to variation in the path MTU estimate. The peer can also enforce an arbitrarily small fixed limit, but if the peer’s limit is large this is guaranteed to be a little over a kilobyte at minimum.

Not necessarily the maximum size of received datagrams.

-
Source

pub fn datagram_send_buffer_space(&self) -> usize

Bytes available in the outgoing datagram buffer

+
Source

pub fn datagram_send_buffer_space(&self) -> usize

Bytes available in the outgoing datagram buffer

When greater than zero, calling send_datagram() with a datagram of at most this size is guaranteed not to cause older datagrams to be dropped.

-
Source

pub fn side(&self) -> Side

The side of the connection (client or server)

-
Source

pub fn rtt(&self, path_id: PathId) -> Option<Duration>

Current best estimate of this connection’s latency (round-trip-time)

-
Source

pub fn stats(&self) -> ConnectionStats

Returns connection statistics

-
Source

pub fn path_stats(&self, path_id: PathId) -> Option<PathStats>

Returns path statistics

-
Source

pub fn congestion_state(&self, path_id: PathId) -> Option<Box<dyn Controller>>

Current state of the congestion control algorithm, for debugging purposes

-
Source

pub fn handshake_data(&self) -> Option<Box<dyn Any>>

Parameters negotiated during the handshake

+
Source

pub fn side(&self) -> Side

The side of the connection (client or server)

+
Source

pub fn rtt(&self, path_id: PathId) -> Option<Duration>

Current best estimate of this connection’s latency (round-trip-time)

+
Source

pub fn stats(&self) -> ConnectionStats

Returns connection statistics

+
Source

pub fn path_stats(&self, path_id: PathId) -> Option<PathStats>

Returns path statistics

+
Source

pub fn congestion_state(&self, path_id: PathId) -> Option<Box<dyn Controller>>

Current state of the congestion control algorithm, for debugging purposes

+
Source

pub fn handshake_data(&self) -> Option<Box<dyn Any>>

Parameters negotiated during the handshake

Guaranteed to return Some on fully established connections or after Connecting::handshake_data() succeeds. See that method’s documentations for details on the returned value.

-
Source

pub fn peer_identity(&self) -> Option<Box<dyn Any>>

Cryptographic identity of the peer

+
Source

pub fn peer_identity(&self) -> Option<Box<dyn Any>>

Cryptographic identity of the peer

The dynamic type returned is determined by the configured Session. For the default rustls session, the return value can be downcast to a Vec<[rustls::pki_types::CertificateDer]>

-
Source

pub fn stable_id(&self) -> usize

A stable identifier for this connection

+
Source

pub fn stable_id(&self) -> usize

A stable identifier for this connection

Peer addresses and connection IDs can change, but this value will remain fixed for the lifetime of the connection.

-
Source

pub fn force_key_update(&self)

Update traffic keys spontaneously

+
Source

pub fn force_key_update(&self)

Update traffic keys spontaneously

This primarily exists for testing purposes.

-
Source

pub fn export_keying_material( +

Source

pub fn export_keying_material( &self, output: &mut [u8], label: &[u8], @@ -149,17 +157,17 @@ arguments and output buffers of equal length, they will get the same sequence of bytes in output. These bytes are cryptographically strong and pseudorandom, and are suitable for use as keying material.

See RFC5705 for more information.

-

Source

pub fn set_max_concurrent_uni_streams(&self, count: VarInt)

Modify the number of remotely initiated unidirectional streams that may be concurrently open

+
Source

pub fn set_max_concurrent_uni_streams(&self, count: VarInt)

Modify the number of remotely initiated unidirectional streams that may be concurrently open

No streams may be opened by the peer unless fewer than count are already open. Large counts increase both minimum and worst-case memory consumption.

-
Source

pub fn set_send_window(&self, send_window: u64)

Source

pub fn set_receive_window(&self, receive_window: VarInt)

Source

pub fn set_max_concurrent_bi_streams(&self, count: VarInt)

Modify the number of remotely initiated bidirectional streams that may be concurrently open

+
Source

pub fn set_send_window(&self, send_window: u64)

Source

pub fn set_receive_window(&self, receive_window: VarInt)

Source

pub fn set_max_concurrent_bi_streams(&self, count: VarInt)

Modify the number of remotely initiated bidirectional streams that may be concurrently open

No streams may be opened by the peer unless fewer than count are already open. Large counts increase both minimum and worst-case memory consumption.

-
Source

pub fn observed_external_addr(&self) -> ObservedExternalAddr

Track changes on our external address as reported by the peer.

-
Source

pub fn is_multipath_enabled(&self) -> bool

Is multipath enabled?

-
Source

pub fn add_nat_traversal_address( +

Source

pub fn observed_external_addr(&self) -> ObservedExternalAddr

Track changes on our external address as reported by the peer.

+
Source

pub fn is_multipath_enabled(&self) -> bool

Is multipath enabled?

+
Source

pub fn add_nat_traversal_address( &self, address: SocketAddr, ) -> Result<(), Error>

Registers one address at which this endpoint might be reachable

@@ -167,7 +175,7 @@ strong and pseudorandom, and are suitable for use as keying material.

ADD_ADDRESS frames. This allows clients to obtain server address candidates to initiate NAT traversal attempts. Clients provide their own reachable addresses in REACH_OUT frames when Self::initiate_nat_traversal_round is called.

-
Source

pub fn remove_nat_traversal_address( +

Source

pub fn remove_nat_traversal_address( &self, address: SocketAddr, ) -> Result<(), Error>

Removes one or more addresses from the set of addresses at which this endpoint is reachable

@@ -176,13 +184,13 @@ clients in REMOVE_ADDRESS frames. This allows clients to stop using server address candidates that are no longer valid for NAT traversal.

For clients, removed addresses will no longer be advertised in REACH_OUT frames.

Addresses not present in the set will be silently ignored.

-
Source

pub fn get_local_nat_traversal_addresses( +

Source

pub fn get_local_nat_traversal_addresses( &self, ) -> Result<Vec<SocketAddr>, Error>

Get the current local nat traversal addresses

-
Source

pub fn get_remote_nat_traversal_addresses( +

Source

pub fn get_remote_nat_traversal_addresses( &self, ) -> Result<Vec<SocketAddr>, Error>

Get the currently advertised nat traversal addresses by the server

-
Source

pub fn initiate_nat_traversal_round(&self) -> Result<Vec<SocketAddr>, Error>

Initiates a new nat traversal round

+
Source

pub fn initiate_nat_traversal_round(&self) -> Result<Vec<SocketAddr>, Error>

Initiates a new nat traversal round

A nat traversal round involves advertising the client’s local addresses in REACH_OUT frames, and initiating probing of the known remote addresses. When a new round is initiated, the previous one is cancelled, and paths that have not been opened are closed.

diff --git a/pr/451/docs/noq/struct.ConnectionClose.html b/pr/451/docs/noq/struct.ConnectionClose.html index f99d64e9e..cf6611ac1 100644 --- a/pr/451/docs/noq/struct.ConnectionClose.html +++ b/pr/451/docs/noq/struct.ConnectionClose.html @@ -1,4 +1,4 @@ -ConnectionClose in noq - Rust

ConnectionClose

Struct ConnectionClose 

Source
pub struct ConnectionClose {
+ConnectionClose in noq - Rust

ConnectionClose

Struct ConnectionClose 

Source
pub struct ConnectionClose {
     pub error_code: Code,
     pub frame_type: MaybeFrame,
     pub reason: Bytes,
diff --git a/pr/451/docs/noq/struct.ConnectionId.html b/pr/451/docs/noq/struct.ConnectionId.html
index d2f0fc923..b33aa4e45 100644
--- a/pr/451/docs/noq/struct.ConnectionId.html
+++ b/pr/451/docs/noq/struct.ConnectionId.html
@@ -1,7 +1,7 @@
-ConnectionId in noq - Rust

ConnectionId

Struct ConnectionId 

Source
pub struct ConnectionId { /* private fields */ }
Expand description

Protocol-level identifier for a connection.

+ConnectionId in noq - Rust

ConnectionId

Struct ConnectionId 

Source
pub struct ConnectionId { /* private fields */ }
Expand description

Protocol-level identifier for a connection.

Mainly useful for identifying this connection’s packets on the wire with tools like Wireshark.

-

Implementations§

Source§

impl ConnectionId

Source

pub fn new(bytes: &[u8]) -> ConnectionId

Construct cid from byte array

-
Source

pub fn from_buf(buf: &mut (impl Buf + ?Sized), len: usize) -> ConnectionId

Constructs cid by reading len bytes from a Buf

+

Implementations§

Source§

impl ConnectionId

Source

pub fn new(bytes: &[u8]) -> ConnectionId

Construct cid from byte array

+
Source

pub fn from_buf(buf: &mut (impl Buf + ?Sized), len: usize) -> ConnectionId

Constructs cid by reading len bytes from a Buf

Callers need to assure that buf.remaining() >= len

Methods from Deref<Target = [u8]>§

1.23.0 · Source

pub fn is_ascii(&self) -> bool

Checks if all bytes in this slice are within the ASCII range.

Source

pub fn as_ascii(&self) -> Option<&[AsciiChar]>

🔬This is a nightly-only experimental API. (ascii_char)

If this slice is_ascii, returns it as a slice of @@ -2422,17 +2422,17 @@ is mapped to its ASCII lower case equivalent.

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, but non-ASCII letters are unchanged.

To lowercase the value in-place, use make_ascii_lowercase.

-

Trait Implementations§

Source§

impl Clone for ConnectionId

Source§

fn clone(&self) -> ConnectionId

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Deref for ConnectionId

Source§

type Target = [u8]

The resulting type after dereferencing.
Source§

fn deref(&self) -> &[u8]

Dereferences the value.
Source§

impl DerefMut for ConnectionId

Source§

fn deref_mut(&mut self) -> &mut [u8]

Mutably dereferences the value.
Source§

impl Display for ConnectionId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Hash for ConnectionId

Source§

fn hash<__H>(&self, state: &mut __H)
where +

Trait Implementations§

Source§

impl Clone for ConnectionId

Source§

fn clone(&self) -> ConnectionId

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Deref for ConnectionId

Source§

type Target = [u8]

The resulting type after dereferencing.
Source§

fn deref(&self) -> &[u8]

Dereferences the value.
Source§

impl DerefMut for ConnectionId

Source§

fn deref_mut(&mut self) -> &mut [u8]

Mutably dereferences the value.
Source§

impl Display for ConnectionId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Hash for ConnectionId

Source§

fn hash<__H>(&self, state: &mut __H)
where __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for ConnectionId

Source§

fn cmp(&self, other: &ConnectionId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for ConnectionId

Source§

fn cmp(&self, other: &ConnectionId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for ConnectionId

Source§

fn eq(&self, other: &ConnectionId) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for ConnectionId

Source§

fn partial_cmp(&self, other: &ConnectionId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more

Source§

impl PartialEq for ConnectionId

Source§

fn eq(&self, other: &ConnectionId) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for ConnectionId

Source§

fn partial_cmp(&self, other: &ConnectionId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl Copy for ConnectionId

Source§

impl Eq for ConnectionId

Source§

impl StructuralPartialEq for ConnectionId

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl Copy for ConnectionId

Source§

impl Eq for ConnectionId

Source§

impl StructuralPartialEq for ConnectionId

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.ConnectionStats.html b/pr/451/docs/noq/struct.ConnectionStats.html index 31792da81..55003c85c 100644 --- a/pr/451/docs/noq/struct.ConnectionStats.html +++ b/pr/451/docs/noq/struct.ConnectionStats.html @@ -1,4 +1,4 @@ -ConnectionStats in noq - Rust

ConnectionStats

Struct ConnectionStats 

Source
#[non_exhaustive]
pub struct ConnectionStats { +ConnectionStats in noq - Rust

ConnectionStats

Struct ConnectionStats 

Source
#[non_exhaustive]
pub struct ConnectionStats { pub udp_tx: UdpStats, pub udp_rx: UdpStats, pub frame_tx: FrameStats, @@ -14,7 +14,7 @@ paths that exist as well as all paths that previously existed.

§frame_rx: FrameStats

Statistics about frames received on the connection.

§lost_packets: u64

The number of packets lost on the connection.

§lost_bytes: u64

The number of bytes lost on the connection.

-

Trait Implementations§

Source§

impl Add<PathStats> for ConnectionStats

Source§

type Output = ConnectionStats

The resulting type after applying the + operator.
Source§

fn add(self, rhs: PathStats) -> <ConnectionStats as Add<PathStats>>::Output

Performs the + operation. Read more
Source§

impl AddAssign<PathStats> for ConnectionStats

Source§

fn add_assign(&mut self, rhs: PathStats)

Performs the += operation. Read more
Source§

impl Clone for ConnectionStats

Source§

fn clone(&self) -> ConnectionStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for ConnectionStats

Source§

fn default() -> ConnectionStats

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Add<PathStats> for ConnectionStats

Source§

type Output = ConnectionStats

The resulting type after applying the + operator.
Source§

fn add(self, rhs: PathStats) -> <ConnectionStats as Add<PathStats>>::Output

Performs the + operation. Read more
Source§

impl AddAssign<PathStats> for ConnectionStats

Source§

fn add_assign(&mut self, rhs: PathStats)

Performs the += operation. Read more
Source§

impl Clone for ConnectionStats

Source§

fn clone(&self) -> ConnectionStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for ConnectionStats

Source§

fn default() -> ConnectionStats

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.DecryptedInitial.html b/pr/451/docs/noq/struct.DecryptedInitial.html index d6bbc9bd8..91fbad1f1 100644 --- a/pr/451/docs/noq/struct.DecryptedInitial.html +++ b/pr/451/docs/noq/struct.DecryptedInitial.html @@ -1,7 +1,7 @@ -DecryptedInitial in noq - Rust

DecryptedInitial

Struct DecryptedInitial 

Source
pub struct DecryptedInitial(/* private fields */);
Expand description

Decrypted payload of a QUIC Initial packet

+DecryptedInitial in noq - Rust

DecryptedInitial

Struct DecryptedInitial 

Source
pub struct DecryptedInitial(/* private fields */);
Expand description

Decrypted payload of a QUIC Initial packet

Obtained via Incoming::decrypt. Can be used to extract information from the TLS ClientHello without completing the handshake.

-

Implementations§

Source§

impl DecryptedInitial

Source

pub fn alpns(&self) -> Option<IncomingAlpns>

Best-effort extraction of the ALPN protocols from the TLS ClientHello

+

Implementations§

Source§

impl DecryptedInitial

Source

pub fn alpns(&self) -> Option<IncomingAlpns>

Best-effort extraction of the ALPN protocols from the TLS ClientHello

Parses the CRYPTO frames to extract the ALPN extension. This is intended for routing and filtering; it is not guaranteed to succeed if the ClientHello spans multiple packets. Returns None if parsing fails.

diff --git a/pr/451/docs/noq/struct.Endpoint.html b/pr/451/docs/noq/struct.Endpoint.html index 07ad1fca1..8405561c6 100644 --- a/pr/451/docs/noq/struct.Endpoint.html +++ b/pr/451/docs/noq/struct.Endpoint.html @@ -1,8 +1,8 @@ -Endpoint in noq - Rust

Endpoint

Struct Endpoint 

Source
pub struct Endpoint { /* private fields */ }
Expand description

A QUIC endpoint.

+Endpoint in noq - Rust

Endpoint

Struct Endpoint 

Source
pub struct Endpoint { /* private fields */ }
Expand description

A QUIC endpoint.

An endpoint corresponds to a single UDP socket, may host many connections, and may act as both client and server for different connections.

May be cloned to obtain another handle to the same endpoint.

-

Implementations§

Source§

impl Endpoint

Source

pub fn client(addr: SocketAddr) -> Result<Self>

Helper to construct an endpoint for use with outgoing connections only

+

Implementations§

Source§

impl Endpoint

Source

pub fn client(addr: SocketAddr) -> Result<Self>

Helper to construct an endpoint for use with outgoing connections only

Note that addr is the local address to bind to, which should usually be a wildcard address like 0.0.0.0:0 or [::]:0, which allow communication with any reachable IPv4 or IPv6 address respectively from an OS-assigned port.

@@ -89,12 +89,20 @@ the same remote addresses.

Source

pub fn open_connections(&self) -> usize

Get the number of connections that are currently open

Source

pub fn close(&self, error_code: VarInt, reason: &[u8])

Close all of this endpoint’s connections immediately and cease accepting new connections.

See Connection::close() for details.

-
Source

pub async fn wait_idle(&self)

Wait for all connections on the endpoint to be cleanly shut down

+
Source

pub async fn wait_idle(&self)

Waits for all connections on the endpoint to be cleanly shut down and drained.

+

This is equivalent to wait_all_draining() with additionally waiting for the connections to be +drained. Please see its documentation for more information.

+

Use wait_idle() in favor of wait_all_draining() if you care about waiting for the +Connection structs to be dropped.

+
Source

pub async fn wait_all_draining(&self)

Waits for all connections on the endpoint to be ready for shutting down.

Waiting for this condition before exiting ensures that a good-faith effort is made to notify peers of recent connection closes, whereas exiting immediately could force them to wait out the idle timeout period.

Does not proactively close existing connections or cause incoming connections to be rejected. Consider calling close() if that is desired.

+

Unlike wait_idle(), this doesn’t wait for the full draining period, so it can’t be +used to wait for all now-idle Connections to be dropped.

+

See also this section in the QUIC RFC: https://datatracker.ietf.org/doc/html/rfc9000#section-10.2-6

Trait Implementations§

Source§

impl Clone for Endpoint

Source§

fn clone(&self) -> Endpoint

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Endpoint

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.EndpointConfig.html b/pr/451/docs/noq/struct.EndpointConfig.html index 525a6f015..795fbe4b8 100644 --- a/pr/451/docs/noq/struct.EndpointConfig.html +++ b/pr/451/docs/noq/struct.EndpointConfig.html @@ -1,4 +1,4 @@ -EndpointConfig in noq - Rust

EndpointConfig

Struct EndpointConfig 

Source
pub struct EndpointConfig { /* private fields */ }
Expand description

Global configuration for the endpoint, affecting all connections

+EndpointConfig in noq - Rust

EndpointConfig

Struct EndpointConfig 

Source
pub struct EndpointConfig { /* private fields */ }
Expand description

Global configuration for the endpoint, affecting all connections

Default values should be suitable for most internet applications.

Implementations§

Source§

impl EndpointConfig

Source

pub fn new(reset_key: Arc<dyn HmacKey>) -> EndpointConfig

Create a default config with a particular reset_key

Source

pub fn cid_generator( diff --git a/pr/451/docs/noq/struct.EndpointStats.html b/pr/451/docs/noq/struct.EndpointStats.html index 43ee2b0a9..011ac9b95 100644 --- a/pr/451/docs/noq/struct.EndpointStats.html +++ b/pr/451/docs/noq/struct.EndpointStats.html @@ -1,4 +1,4 @@ -EndpointStats in noq - Rust

EndpointStats

Struct EndpointStats 

Source
#[non_exhaustive]
pub struct EndpointStats { +EndpointStats in noq - Rust

EndpointStats

Struct EndpointStats 

Source
#[non_exhaustive]
pub struct EndpointStats { pub accepted_handshakes: u64, pub outgoing_handshakes: u64, pub refused_handshakes: u64, @@ -8,7 +8,7 @@
§outgoing_handshakes: u64

Cumulative number of Quic handshakes sent from this Endpoint

§refused_handshakes: u64

Cumulative number of Quic handshakes refused on this Endpoint

§ignored_handshakes: u64

Cumulative number of Quic handshakes ignored on this Endpoint

-

Trait Implementations§

Source§

impl Clone for EndpointStats

Source§

fn clone(&self) -> EndpointStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EndpointStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for EndpointStats

Source§

fn default() -> EndpointStats

Returns the “default value” for a type. Read more
Source§

impl Copy for EndpointStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for EndpointStats

Source§

fn clone(&self) -> EndpointStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EndpointStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for EndpointStats

Source§

fn default() -> EndpointStats

Returns the “default value” for a type. Read more
Source§

impl Copy for EndpointStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.FourTuple.html b/pr/451/docs/noq/struct.FourTuple.html new file mode 100644 index 000000000..f51196d56 --- /dev/null +++ b/pr/451/docs/noq/struct.FourTuple.html @@ -0,0 +1,44 @@ +FourTuple in noq - Rust

FourTuple

Struct FourTuple 

Source
pub struct FourTuple { /* private fields */ }
Expand description

Identifies a network path by the combination of remote and local addresses

+

Including the local ensures good behavior when the host has multiple IP addresses on the same +subnet and zero-length connection IDs are in use or when multipath is enabled and multiple +paths exist with the same remote, but different local IP interfaces.

+

FourTuple implements From<SocketAddr>, which expands to Self::from_remote.

+

Implementations§

Source§

impl FourTuple

Source

pub fn new(remote: SocketAddr, local_ip: Option<IpAddr>) -> FourTuple

Creates a new FourTuple.

+
Source

pub fn from_remote(remote: SocketAddr) -> FourTuple

Creates a new FourTuple without a known local address.

+
Source

pub fn remote(&self) -> SocketAddr

Returns the remote address of the network path.

+
Source

pub fn local_ip(&self) -> Option<IpAddr>

Returns the local address of the network path.

+

Trait Implementations§

Source§

impl Clone for FourTuple

Source§

fn clone(&self) -> FourTuple

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FourTuple

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for FourTuple

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl From<SocketAddr> for FourTuple

Source§

fn from(value: SocketAddr) -> FourTuple

Converts to this type from the input type.
Source§

impl Hash for FourTuple

Source§

fn hash<__H>(&self, state: &mut __H)
where + __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for FourTuple

Source§

fn eq(&self, other: &FourTuple) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for FourTuple

Source§

impl Eq for FourTuple

Source§

impl StructuralPartialEq for FourTuple

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where + T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where + T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where + T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an +Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where + U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> +if into_left is true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where + F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> +if into_left(&self) returns true. +Converts self into a Right variant of Either<Self, Self> +otherwise. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where + T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where + T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where + U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where + U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +[WithDispatch] wrapper. Read more
\ No newline at end of file diff --git a/pr/451/docs/noq/struct.FrameStats.html b/pr/451/docs/noq/struct.FrameStats.html index d9fe495e1..0c8803135 100644 --- a/pr/451/docs/noq/struct.FrameStats.html +++ b/pr/451/docs/noq/struct.FrameStats.html @@ -1,4 +1,4 @@ -FrameStats in noq - Rust

FrameStats

Struct FrameStats 

Source
#[non_exhaustive]
pub struct FrameStats {
Show 37 fields +FrameStats in noq - Rust

FrameStats

Struct FrameStats 

Source
#[non_exhaustive]
pub struct FrameStats {
Show 37 fields pub acks: u64, pub path_acks: u64, pub ack_frequency: u64, @@ -37,8 +37,8 @@ pub reach_out: u64, pub remove_address: u64,
}
Expand description

Number of frames transmitted or received of each frame type.

-

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§acks: u64§path_acks: u64§ack_frequency: u64§crypto: u64§connection_close: u64§data_blocked: u64§datagram: u64§handshake_done: u8§immediate_ack: u64§max_data: u64§max_stream_data: u64§max_streams_bidi: u64§max_streams_uni: u64§new_connection_id: u64§path_new_connection_id: u64§new_token: u64§path_challenge: u64§path_response: u64§ping: u64§reset_stream: u64§retire_connection_id: u64§path_retire_connection_id: u64§stream_data_blocked: u64§streams_blocked_bidi: u64§streams_blocked_uni: u64§stop_sending: u64§stream: u64§observed_addr: u64§path_abandon: u64§path_status_available: u64§path_status_backup: u64§max_path_id: u64§paths_blocked: u64§path_cids_blocked: u64§add_address: u64§reach_out: u64§remove_address: u64

Trait Implementations§

Source§

impl Add for FrameStats

Source§

type Output = FrameStats

The resulting type after applying the + operator.
Source§

fn add(self, __rhs: FrameStats) -> <FrameStats as Add>::Output

Performs the + operation. Read more
Source§

impl AddAssign for FrameStats

Source§

fn add_assign(&mut self, __rhs: FrameStats)

Performs the += operation. Read more
Source§

impl Clone for FrameStats

Source§

fn clone(&self) -> FrameStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FrameStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for FrameStats

Source§

fn default() -> FrameStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for FrameStats

Source§

fn eq(&self, other: &FrameStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for FrameStats

Source§

impl Eq for FrameStats

Source§

impl StructuralPartialEq for FrameStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§acks: u64§path_acks: u64§ack_frequency: u64§crypto: u64§connection_close: u64§data_blocked: u64§datagram: u64§handshake_done: u8§immediate_ack: u64§max_data: u64§max_stream_data: u64§max_streams_bidi: u64§max_streams_uni: u64§new_connection_id: u64§path_new_connection_id: u64§new_token: u64§path_challenge: u64§path_response: u64§ping: u64§reset_stream: u64§retire_connection_id: u64§path_retire_connection_id: u64§stream_data_blocked: u64§streams_blocked_bidi: u64§streams_blocked_uni: u64§stop_sending: u64§stream: u64§observed_addr: u64§path_abandon: u64§path_status_available: u64§path_status_backup: u64§max_path_id: u64§paths_blocked: u64§path_cids_blocked: u64§add_address: u64§reach_out: u64§remove_address: u64

Trait Implementations§

Source§

impl Add for FrameStats

Source§

type Output = FrameStats

The resulting type after applying the + operator.
Source§

fn add(self, __rhs: FrameStats) -> <FrameStats as Add>::Output

Performs the + operation. Read more
Source§

impl AddAssign for FrameStats

Source§

fn add_assign(&mut self, __rhs: FrameStats)

Performs the += operation. Read more
Source§

impl Clone for FrameStats

Source§

fn clone(&self) -> FrameStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FrameStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for FrameStats

Source§

fn default() -> FrameStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for FrameStats

Source§

fn eq(&self, other: &FrameStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for FrameStats

Source§

impl Eq for FrameStats

Source§

impl StructuralPartialEq for FrameStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.IdleTimeout.html b/pr/451/docs/noq/struct.IdleTimeout.html index 873dea7d8..69db79476 100644 --- a/pr/451/docs/noq/struct.IdleTimeout.html +++ b/pr/451/docs/noq/struct.IdleTimeout.html @@ -1,4 +1,4 @@ -IdleTimeout in noq - Rust

IdleTimeout

Struct IdleTimeout 

Source
pub struct IdleTimeout(/* private fields */);
Expand description

Maximum duration of inactivity to accept before timing out the connection

+IdleTimeout in noq - Rust

IdleTimeout

Struct IdleTimeout 

Source
pub struct IdleTimeout(/* private fields */);
Expand description

Maximum duration of inactivity to accept before timing out the connection

This wraps an underlying VarInt, representing the duration in milliseconds. Values can be constructed by converting directly from VarInt, or using TryFrom<Duration>.

@@ -6,19 +6,19 @@ constructed by converting directly from VarInt, or using TryF let timeout = IdleTimeout::from(VarInt::from_u32(10_000)); // Try to convert a `Duration` into a `VarInt`-encoded timeout -let timeout = IdleTimeout::try_from(Duration::from_secs(10))?;

Trait Implementations§

Source§

impl Clone for IdleTimeout

Source§

fn clone(&self) -> IdleTimeout

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for IdleTimeout

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for IdleTimeout

Source§

fn default() -> IdleTimeout

Returns the “default value” for a type. Read more
Source§

impl From<VarInt> for IdleTimeout

Source§

fn from(inner: VarInt) -> IdleTimeout

Converts to this type from the input type.
Source§

impl Hash for IdleTimeout

Source§

fn hash<__H>(&self, state: &mut __H)
where +let timeout = IdleTimeout::try_from(Duration::from_secs(10))?;

Trait Implementations§

Source§

impl Clone for IdleTimeout

Source§

fn clone(&self) -> IdleTimeout

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for IdleTimeout

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for IdleTimeout

Source§

fn default() -> IdleTimeout

Returns the “default value” for a type. Read more
Source§

impl From<VarInt> for IdleTimeout

Source§

fn from(inner: VarInt) -> IdleTimeout

Converts to this type from the input type.
Source§

impl Hash for IdleTimeout

Source§

fn hash<__H>(&self, state: &mut __H)
where __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for IdleTimeout

Source§

fn cmp(&self, other: &IdleTimeout) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for IdleTimeout

Source§

fn cmp(&self, other: &IdleTimeout) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for IdleTimeout

Source§

fn eq(&self, other: &IdleTimeout) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for IdleTimeout

Source§

fn partial_cmp(&self, other: &IdleTimeout) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for IdleTimeout

Source§

fn eq(&self, other: &IdleTimeout) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for IdleTimeout

Source§

fn partial_cmp(&self, other: &IdleTimeout) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl TryFrom<Duration> for IdleTimeout

Source§

type Error = VarIntBoundsExceeded

The type returned in the event of a conversion error.
Source§

fn try_from( +the >= operator. Read more

Source§

impl TryFrom<Duration> for IdleTimeout

Source§

type Error = VarIntBoundsExceeded

The type returned in the event of a conversion error.
Source§

fn try_from( timeout: Duration, -) -> Result<IdleTimeout, <IdleTimeout as TryFrom<Duration>>::Error>

Performs the conversion.
Source§

impl Copy for IdleTimeout

Source§

impl Eq for IdleTimeout

Source§

impl StructuralPartialEq for IdleTimeout

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T

Performs the conversion.
Source§

impl Copy for IdleTimeout

Source§

impl Eq for IdleTimeout

Source§

impl StructuralPartialEq for IdleTimeout

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.Incoming.html b/pr/451/docs/noq/struct.Incoming.html index 3f96ee9ac..4705896c7 100644 --- a/pr/451/docs/noq/struct.Incoming.html +++ b/pr/451/docs/noq/struct.Incoming.html @@ -1,4 +1,4 @@ -Incoming in noq - Rust

Incoming

Struct Incoming 

Source
pub struct Incoming(/* private fields */);
Expand description

An incoming connection for which the server has not yet begun its part of the handshake

+Incoming in noq - Rust

Incoming

Struct Incoming 

Source
pub struct Incoming(/* private fields */);
Expand description

An incoming connection for which the server has not yet begun its part of the handshake

Implementations§

Source§

impl Incoming

Source

pub fn accept(self) -> Result<Connecting, ConnectionError>

Attempt to accept this incoming connection (an error may still occur)

Source

pub fn accept_with( self, diff --git a/pr/451/docs/noq/struct.IncomingFuture.html b/pr/451/docs/noq/struct.IncomingFuture.html index b7adf40a4..cfb5ff360 100644 --- a/pr/451/docs/noq/struct.IncomingFuture.html +++ b/pr/451/docs/noq/struct.IncomingFuture.html @@ -1,4 +1,4 @@ -IncomingFuture in noq - Rust

IncomingFuture

Struct IncomingFuture 

Source
pub struct IncomingFuture(/* private fields */);
Expand description

Basic adapter to let Incoming be await-ed like a Connecting

+IncomingFuture in noq - Rust

IncomingFuture

Struct IncomingFuture 

Source
pub struct IncomingFuture(/* private fields */);
Expand description

Basic adapter to let Incoming be await-ed like a Connecting

Trait Implementations§

Source§

impl Debug for IncomingFuture

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Future for IncomingFuture

Source§

type Output = Result<Connection, ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering the current task for wakeup if the value is not yet available. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where diff --git a/pr/451/docs/noq/struct.InvalidCid.html b/pr/451/docs/noq/struct.InvalidCid.html index ee1b40acc..c9fa5dc70 100644 --- a/pr/451/docs/noq/struct.InvalidCid.html +++ b/pr/451/docs/noq/struct.InvalidCid.html @@ -1,4 +1,4 @@ -InvalidCid in noq - Rust

InvalidCid

Struct InvalidCid 

Source
pub struct InvalidCid;
Expand description

The connection ID was not recognized by the ConnectionIdGenerator

+InvalidCid in noq - Rust

InvalidCid

Struct InvalidCid 

Source
pub struct InvalidCid;
Expand description

The connection ID was not recognized by the ConnectionIdGenerator

Trait Implementations§

Source§

impl Clone for InvalidCid

Source§

fn clone(&self) -> InvalidCid

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for InvalidCid

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Copy for InvalidCid

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.Lagged.html b/pr/451/docs/noq/struct.Lagged.html index 645120f7f..3c4916664 100644 --- a/pr/451/docs/noq/struct.Lagged.html +++ b/pr/451/docs/noq/struct.Lagged.html @@ -1,4 +1,4 @@ -Lagged in noq - Rust

Lagged

Struct Lagged 

Source
pub struct Lagged(pub u64);
Expand description

The receiver lagged too far behind.

+Lagged in noq - Rust

Lagged

Struct Lagged 

Source
pub struct Lagged(pub u64);
Expand description

The receiver lagged too far behind.

Attempting to receive again will return the oldest message still retained by the channel.

Tuple Fields§

§0: u64

Trait Implementations§

Source§

impl Clone for Lagged

Source§

fn clone(&self) -> Lagged

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Lagged

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Lagged

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for Lagged

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for Lagged

Source§

fn eq(&self, other: &Lagged) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, diff --git a/pr/451/docs/noq/struct.MtuDiscoveryConfig.html b/pr/451/docs/noq/struct.MtuDiscoveryConfig.html index 451766089..e3389b780 100644 --- a/pr/451/docs/noq/struct.MtuDiscoveryConfig.html +++ b/pr/451/docs/noq/struct.MtuDiscoveryConfig.html @@ -1,4 +1,4 @@ -MtuDiscoveryConfig in noq - Rust

MtuDiscoveryConfig

Struct MtuDiscoveryConfig 

Source
pub struct MtuDiscoveryConfig { /* private fields */ }
Expand description

Parameters governing MTU discovery.

+MtuDiscoveryConfig in noq - Rust

MtuDiscoveryConfig

Struct MtuDiscoveryConfig 

Source
pub struct MtuDiscoveryConfig { /* private fields */ }
Expand description

Parameters governing MTU discovery.

§The why of MTU discovery

By design, QUIC ensures during the handshake that the network path between the client and the server is able to transmit unfragmented UDP packets with a body of 1200 bytes. In other words, @@ -42,25 +42,25 @@ timer that was set based on Implementations§

Source§

impl MtuDiscoveryConfig

Source

pub fn interval(&mut self, value: Duration) -> &mut MtuDiscoveryConfig

Specifies the time to wait after completing MTU discovery before starting a new MTU +

Implementations§

Source§

impl MtuDiscoveryConfig

Source

pub fn interval(&mut self, value: Duration) -> &mut MtuDiscoveryConfig

Specifies the time to wait after completing MTU discovery before starting a new MTU discovery run.

Defaults to 600 seconds, as recommended by RFC 8899.

-
Source

pub fn upper_bound(&mut self, value: u16) -> &mut MtuDiscoveryConfig

Specifies the upper bound to the max UDP payload size that MTU discovery will search for.

+
Source

pub fn upper_bound(&mut self, value: u16) -> &mut MtuDiscoveryConfig

Specifies the upper bound to the max UDP payload size that MTU discovery will search for.

Defaults to 1452, to stay within Ethernet’s MTU when using IPv4 and IPv6. The highest allowed value is 65527, which corresponds to the maximum permitted UDP payload on IPv6.

It is safe to use an arbitrarily high upper bound, regardless of the network path’s MTU. The only drawback is that MTU discovery might take more time to finish.

-
Source

pub fn black_hole_cooldown( +

Source

pub fn black_hole_cooldown( &mut self, value: Duration, ) -> &mut MtuDiscoveryConfig

Specifies the amount of time that MTU discovery should wait after a black hole was detected before running again. Defaults to one minute.

Black hole detection can be spuriously triggered in case of congestion, so it makes sense to try MTU discovery again after a short period of time.

-
Source

pub fn minimum_change(&mut self, value: u16) -> &mut MtuDiscoveryConfig

Specifies the minimum MTU change to stop the MTU discovery phase. +

Source

pub fn minimum_change(&mut self, value: u16) -> &mut MtuDiscoveryConfig

Specifies the minimum MTU change to stop the MTU discovery phase. Defaults to 20.

-

Trait Implementations§

Source§

impl Clone for MtuDiscoveryConfig

Source§

fn clone(&self) -> MtuDiscoveryConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MtuDiscoveryConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for MtuDiscoveryConfig

Source§

fn default() -> MtuDiscoveryConfig

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for MtuDiscoveryConfig

Source§

fn clone(&self) -> MtuDiscoveryConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MtuDiscoveryConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for MtuDiscoveryConfig

Source§

fn default() -> MtuDiscoveryConfig

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.NatTraversalUpdates.html b/pr/451/docs/noq/struct.NatTraversalUpdates.html index 89ac28187..cd5c4af42 100644 --- a/pr/451/docs/noq/struct.NatTraversalUpdates.html +++ b/pr/451/docs/noq/struct.NatTraversalUpdates.html @@ -1,4 +1,4 @@ -NatTraversalUpdates in noq - Rust

NatTraversalUpdates

Struct NatTraversalUpdates 

Source
pub struct NatTraversalUpdates { /* private fields */ }
Expand description

A stream of NAT traversal updates for a connection.

+NatTraversalUpdates in noq - Rust

NatTraversalUpdates

Struct NatTraversalUpdates 

Source
pub struct NatTraversalUpdates { /* private fields */ }
Expand description

A stream of NAT traversal updates for a connection.

Trait Implementations§

Source§

impl Debug for NatTraversalUpdates

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Stream for NatTraversalUpdates

Source§

type Item = Result<Event, Lagged>

Values yielded by the stream.
Source§

fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/pr/451/docs/noq/struct.NoneTokenLog.html b/pr/451/docs/noq/struct.NoneTokenLog.html index adfff5de5..2417b7491 100644 --- a/pr/451/docs/noq/struct.NoneTokenLog.html +++ b/pr/451/docs/noq/struct.NoneTokenLog.html @@ -1,4 +1,4 @@ -NoneTokenLog in noq - Rust

NoneTokenLog

Struct NoneTokenLog 

Source
pub struct NoneTokenLog;
Expand description

Null implementation of TokenLog, which never accepts tokens

+NoneTokenLog in noq - Rust

NoneTokenLog

Struct NoneTokenLog 

Source
pub struct NoneTokenLog;
Expand description

Null implementation of TokenLog, which never accepts tokens

Trait Implementations§

Source§

impl TokenLog for NoneTokenLog

Source§

fn check_and_insert( &self, _: u128, diff --git a/pr/451/docs/noq/struct.NoneTokenStore.html b/pr/451/docs/noq/struct.NoneTokenStore.html index 474592733..143a6c1fa 100644 --- a/pr/451/docs/noq/struct.NoneTokenStore.html +++ b/pr/451/docs/noq/struct.NoneTokenStore.html @@ -1,4 +1,4 @@ -NoneTokenStore in noq - Rust

NoneTokenStore

Struct NoneTokenStore 

Source
pub struct NoneTokenStore;
Expand description

Null implementation of TokenStore, which does not store any tokens

+NoneTokenStore in noq - Rust

NoneTokenStore

Struct NoneTokenStore 

Source
pub struct NoneTokenStore;
Expand description

Null implementation of TokenStore, which does not store any tokens

Trait Implementations§

Source§

impl TokenStore for NoneTokenStore

Source§

fn insert(&self, _: &str, _: Bytes)

Potentially store a token for later one-time use Read more
Source§

fn take(&self, _: &str) -> Option<Bytes>

Try to find and take a token that was stored with the given server name Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.ObservedExternalAddr.html b/pr/451/docs/noq/struct.ObservedExternalAddr.html index 63d343347..2bd87de68 100644 --- a/pr/451/docs/noq/struct.ObservedExternalAddr.html +++ b/pr/451/docs/noq/struct.ObservedExternalAddr.html @@ -1,4 +1,4 @@ -ObservedExternalAddr in noq - Rust

ObservedExternalAddr

Struct ObservedExternalAddr 

Source
pub struct ObservedExternalAddr { /* private fields */ }
Expand description

Watches the external address reported by the peer for this connection.

+ObservedExternalAddr in noq - Rust

ObservedExternalAddr

Struct ObservedExternalAddr 

Source
pub struct ObservedExternalAddr { /* private fields */ }
Expand description

Watches the external address reported by the peer for this connection.

Implements [Stream] yielding SocketAddr on each change. Use get to read the current value without waiting.

Implementations§

Source§

impl ObservedExternalAddr

Source

pub fn get(&self) -> Option<SocketAddr>

Returns the most recently observed external address.

diff --git a/pr/451/docs/noq/struct.OnClosed.html b/pr/451/docs/noq/struct.OnClosed.html index 9935b9f80..320b394ab 100644 --- a/pr/451/docs/noq/struct.OnClosed.html +++ b/pr/451/docs/noq/struct.OnClosed.html @@ -1,6 +1,6 @@ -OnClosed in noq - Rust

OnClosed

Struct OnClosed 

Source
pub struct OnClosed { /* private fields */ }
Expand description

Future returned by Connection::on_closed

+OnClosed in noq - Rust

OnClosed

Struct OnClosed 

Source
pub struct OnClosed { /* private fields */ }
Expand description

Future returned by Connection::on_closed

Resolves to Closed.

-

Trait Implementations§

Source§

impl Drop for OnClosed

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl Future for OnClosed

Source§

type Output = Closed

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering +

Trait Implementations§

Source§

impl Drop for OnClosed

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl Future for OnClosed

Source§

type Output = Closed

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering the current task for wakeup if the value is not yet available. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.OpenBi.html b/pr/451/docs/noq/struct.OpenBi.html index 722182738..64efa41b2 100644 --- a/pr/451/docs/noq/struct.OpenBi.html +++ b/pr/451/docs/noq/struct.OpenBi.html @@ -1,6 +1,6 @@ -OpenBi in noq - Rust

OpenBi

Struct OpenBi 

Source
pub struct OpenBi<'a> { /* private fields */ }
Expand description

Future produced by Connection::open_bi

-

Trait Implementations§

Source§

impl Future for OpenBi<'_>

Source§

type Output = Result<(SendStream, RecvStream), ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering -the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for OpenBi<'a>
where +OpenBi in noq - Rust

OpenBi

Struct OpenBi 

Source
pub struct OpenBi<'a> { /* private fields */ }
Expand description

Future produced by Connection::open_bi

+

Trait Implementations§

Source§

impl Future for OpenBi<'_>

Source§

type Output = Result<(SendStream, RecvStream), ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering +the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for OpenBi<'a>
where PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,

Auto Trait Implementations§

§

impl<'a> !Freeze for OpenBi<'a>

§

impl<'a> !RefUnwindSafe for OpenBi<'a>

§

impl<'a> Send for OpenBi<'a>

§

impl<'a> Sync for OpenBi<'a>

§

impl<'a> !UnwindSafe for OpenBi<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.OpenPath.html b/pr/451/docs/noq/struct.OpenPath.html index c62f031f0..960678c43 100644 --- a/pr/451/docs/noq/struct.OpenPath.html +++ b/pr/451/docs/noq/struct.OpenPath.html @@ -1,4 +1,4 @@ -OpenPath in noq - Rust

OpenPath

Struct OpenPath 

Source
pub struct OpenPath(/* private fields */);
Expand description

Future produced by crate::Connection::open_path

+OpenPath in noq - Rust

OpenPath

Struct OpenPath 

Source
pub struct OpenPath(/* private fields */);
Expand description

Future produced by crate::Connection::open_path

Implementations§

Source§

impl OpenPath

Source

pub fn path_id(&self) -> Option<PathId>

Returns the path ID of the new path being opened.

If an error occurred before a path ID was allocated, None is returned. In this case the future is ready and polling it will immediately yield the error.

diff --git a/pr/451/docs/noq/struct.OpenUni.html b/pr/451/docs/noq/struct.OpenUni.html index 90d7c0f41..303258138 100644 --- a/pr/451/docs/noq/struct.OpenUni.html +++ b/pr/451/docs/noq/struct.OpenUni.html @@ -1,6 +1,6 @@ -OpenUni in noq - Rust

OpenUni

Struct OpenUni 

Source
pub struct OpenUni<'a> { /* private fields */ }
Expand description

Future produced by Connection::open_uni

-

Trait Implementations§

Source§

impl Future for OpenUni<'_>

Source§

type Output = Result<SendStream, ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering -the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for OpenUni<'a>
where +OpenUni in noq - Rust

OpenUni

Struct OpenUni 

Source
pub struct OpenUni<'a> { /* private fields */ }
Expand description

Future produced by Connection::open_uni

+

Trait Implementations§

Source§

impl Future for OpenUni<'_>

Source§

type Output = Result<SendStream, ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering +the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for OpenUni<'a>
where PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,

Auto Trait Implementations§

§

impl<'a> !Freeze for OpenUni<'a>

§

impl<'a> !RefUnwindSafe for OpenUni<'a>

§

impl<'a> Send for OpenUni<'a>

§

impl<'a> Sync for OpenUni<'a>

§

impl<'a> !UnwindSafe for OpenUni<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.Path.html b/pr/451/docs/noq/struct.Path.html index 43031dc61..e0345bc51 100644 --- a/pr/451/docs/noq/struct.Path.html +++ b/pr/451/docs/noq/struct.Path.html @@ -1,7 +1,7 @@ -Path in noq - Rust

Path

Struct Path 

Source
pub struct Path { /* private fields */ }
Expand description

An open network transmission within a multipath-enabled connection.

+Path in noq - Rust

Path

Struct Path 

Source
pub struct Path { /* private fields */ }
Expand description

An open network transmission within a multipath-enabled connection.

As long as a Path or WeakPathHandle is alive, it is ensured that the PathStats for this path are not dropped even after the path is abandoned.

-

Implementations§

Source§

impl Path

Source

pub fn weak_handle(&self) -> WeakPathHandle

Returns a WeakPathHandle for this path.

+

Implementations§

Source§

impl Path

Source

pub fn weak_handle(&self) -> WeakPathHandle

Returns a WeakPathHandle for this path.

Holding a WeakPathHandle does not keep a connection alive, but ensures that the path’s stats are not dropped until the underlying connection is dropped, even if the path is abandoned.

@@ -35,8 +35,10 @@ returned.

Source

pub fn local_ip(&self) -> Result<Option<IpAddr>, ClosedPath>

The local IP used for this path, if known.

Returns Ok(None) for clients or when the platform does not expose this information; see noq_udp::RecvMeta::dst_ip for supported platforms.

-
Source

pub fn ping(&self) -> Result<(), ClosedPath>

Ping the remote endpoint over this path.

-

Trait Implementations§

Source§

impl Clone for Path

Source§

fn clone(&self) -> Path

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Path

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for Path

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl PartialEq for Path

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +
Source

pub fn network_path(&self) -> Result<FourTuple, ClosedPath>

The network path used for this path.

+

Returns a FourTuple, combining Self::remote_address and Self::local_ip.

+
Source

pub fn ping(&self) -> Result<(), ClosedPath>

Ping the remote endpoint over this path.

+

Trait Implementations§

Source§

impl Clone for Path

Source§

fn clone(&self) -> Path

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Path

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for Path

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl PartialEq for Path

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.

Auto Trait Implementations§

§

impl Freeze for Path

§

impl RefUnwindSafe for Path

§

impl Send for Path

§

impl Sync for Path

§

impl Unpin for Path

§

impl UnwindSafe for Path

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.PathEvents.html b/pr/451/docs/noq/struct.PathEvents.html index 0557005d2..4e3bd7435 100644 --- a/pr/451/docs/noq/struct.PathEvents.html +++ b/pr/451/docs/noq/struct.PathEvents.html @@ -1,4 +1,4 @@ -PathEvents in noq - Rust

PathEvents

Struct PathEvents 

Source
pub struct PathEvents { /* private fields */ }
Expand description

A stream of PathEvents for all paths in a connection.

+PathEvents in noq - Rust

PathEvents

Struct PathEvents 

Source
pub struct PathEvents { /* private fields */ }
Expand description

A stream of PathEvents for all paths in a connection.

Trait Implementations§

Source§

impl Debug for PathEvents

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Stream for PathEvents

Source§

type Item = Result<PathEvent, Lagged>

Values yielded by the stream.
Source§

fn poll_next( self: Pin<&mut Self>, cx: &mut Context<'_>, diff --git a/pr/451/docs/noq/struct.PathId.html b/pr/451/docs/noq/struct.PathId.html index e680d2b25..ab899648d 100644 --- a/pr/451/docs/noq/struct.PathId.html +++ b/pr/451/docs/noq/struct.PathId.html @@ -1,24 +1,24 @@ -PathId in noq - Rust

PathId

Struct PathId 

Source
pub struct PathId(/* private fields */);
Expand description

Id representing different paths when using multipath extension

-

Implementations§

Source§

impl PathId

Source

pub const MAX: PathId

The maximum path ID allowed.

-
Source

pub const ZERO: PathId

The 0 path id.

-
Source

pub fn saturating_add(self, rhs: impl Into<PathId>) -> PathId

Saturating integer addition. Computes self + rhs, saturating at the numeric bounds instead +PathId in noq - Rust

PathId

Struct PathId 

Source
pub struct PathId(/* private fields */);
Expand description

Id representing different paths when using multipath extension

+

Implementations§

Source§

impl PathId

Source

pub const MAX: PathId

The maximum path ID allowed.

+
Source

pub const ZERO: PathId

The 0 path id.

+
Source

pub fn saturating_add(self, rhs: impl Into<PathId>) -> PathId

Saturating integer addition. Computes self + rhs, saturating at the numeric bounds instead of overflowing.

-
Source

pub fn saturating_sub(self, rhs: impl Into<PathId>) -> PathId

Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds +

Source

pub fn saturating_sub(self, rhs: impl Into<PathId>) -> PathId

Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds instead of overflowing.

-

Trait Implementations§

Source§

impl Clone for PathId

Source§

fn clone(&self) -> PathId

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Decodable for PathId

Source§

fn decode<B>(r: &mut B) -> Result<PathId, UnexpectedEnd>
where - B: Buf,

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Default for PathId

Source§

fn default() -> PathId

Returns the “default value” for a type. Read more
Source§

impl Display for PathId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Encodable for PathId

Source§

fn encode<B>(&self, w: &mut B)
where - B: BufMut,

Append the encoding of self to the provided buffer.
Source§

impl<T> From<T> for PathId
where - T: Into<u32>,

Source§

fn from(source: T) -> PathId

Converts to this type from the input type.
Source§

impl Hash for PathId

Source§

fn hash<H>(&self, state: &mut H)
where +

Trait Implementations§

Source§

impl Clone for PathId

Source§

fn clone(&self) -> PathId

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Decodable for PathId

Source§

fn decode<B>(r: &mut B) -> Result<PathId, UnexpectedEnd>
where + B: Buf,

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Default for PathId

Source§

fn default() -> PathId

Returns the “default value” for a type. Read more
Source§

impl Display for PathId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Encodable for PathId

Source§

fn encode<B>(&self, w: &mut B)
where + B: BufMut,

Append the encoding of self to the provided buffer.
Source§

impl<T> From<T> for PathId
where + T: Into<u32>,

Source§

fn from(source: T) -> PathId

Converts to this type from the input type.
Source§

impl Hash for PathId

Source§

fn hash<H>(&self, state: &mut H)
where H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for PathId

Source§

fn cmp(&self, other: &PathId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for PathId

Source§

fn cmp(&self, other: &PathId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for PathId

Source§

fn eq(&self, other: &PathId) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for PathId

Source§

fn partial_cmp(&self, other: &PathId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for PathId

Source§

fn eq(&self, other: &PathId) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for PathId

Source§

fn partial_cmp(&self, other: &PathId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl Copy for PathId

Source§

impl Eq for PathId

Source§

impl IdentityHashable for PathId

Source§

impl StructuralPartialEq for PathId

Auto Trait Implementations§

§

impl Freeze for PathId

§

impl RefUnwindSafe for PathId

§

impl Send for PathId

§

impl Sync for PathId

§

impl Unpin for PathId

§

impl UnwindSafe for PathId

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl Copy for PathId

Source§

impl Eq for PathId

Source§

impl StructuralPartialEq for PathId

Auto Trait Implementations§

§

impl Freeze for PathId

§

impl RefUnwindSafe for PathId

§

impl Send for PathId

§

impl Sync for PathId

§

impl Unpin for PathId

§

impl UnwindSafe for PathId

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.PathStats.html b/pr/451/docs/noq/struct.PathStats.html index 51b362682..ccf51b28d 100644 --- a/pr/451/docs/noq/struct.PathStats.html +++ b/pr/451/docs/noq/struct.PathStats.html @@ -1,4 +1,4 @@ -PathStats in noq - Rust

PathStats

Struct PathStats 

Source
#[non_exhaustive]
pub struct PathStats {
Show 14 fields +PathStats in noq - Rust

PathStats

Struct PathStats 

Source
#[non_exhaustive]
pub struct PathStats {
Show 14 fields pub rtt: Duration, pub udp_tx: UdpStats, pub udp_rx: UdpStats, @@ -30,8 +30,8 @@

These are not included in Self::lost_packets and Self::lost_bytes.

§black_holes_detected: u64

The number of times a black hole was detected in the path.

§current_mtu: u16

Largest UDP payload size the path currently supports.

-

Trait Implementations§

Source§

impl Add<PathStats> for ConnectionStats

Source§

type Output = ConnectionStats

The resulting type after applying the + operator.
Source§

fn add(self, rhs: PathStats) -> <ConnectionStats as Add<PathStats>>::Output

Performs the + operation. Read more
Source§

impl AddAssign<PathStats> for ConnectionStats

Source§

fn add_assign(&mut self, rhs: PathStats)

Performs the += operation. Read more
Source§

impl Clone for PathStats

Source§

fn clone(&self) -> PathStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for PathStats

Source§

fn default() -> PathStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStats

Source§

fn eq(&self, other: &PathStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for PathStats

Source§

impl Eq for PathStats

Source§

impl StructuralPartialEq for PathStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Add<PathStats> for ConnectionStats

Source§

type Output = ConnectionStats

The resulting type after applying the + operator.
Source§

fn add(self, rhs: PathStats) -> <ConnectionStats as Add<PathStats>>::Output

Performs the + operation. Read more
Source§

impl AddAssign<PathStats> for ConnectionStats

Source§

fn add_assign(&mut self, rhs: PathStats)

Performs the += operation. Read more
Source§

impl Clone for PathStats

Source§

fn clone(&self) -> PathStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for PathStats

Source§

fn default() -> PathStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStats

Source§

fn eq(&self, other: &PathStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for PathStats

Source§

impl Eq for PathStats

Source§

impl StructuralPartialEq for PathStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.QlogConfig.html b/pr/451/docs/noq/struct.QlogConfig.html index 4f036077e..e30666ead 100644 --- a/pr/451/docs/noq/struct.QlogConfig.html +++ b/pr/451/docs/noq/struct.QlogConfig.html @@ -1,4 +1,4 @@ -QlogConfig in noq - Rust

QlogConfig

Struct QlogConfig 

Source
pub struct QlogConfig { /* private fields */ }
Expand description

Configuration for qlog trace logging.

+QlogConfig in noq - Rust

QlogConfig

Struct QlogConfig 

Source
pub struct QlogConfig { /* private fields */ }
Expand description

Configuration for qlog trace logging.

This struct is returned from QlogFactory::for_connection if qlog logging should be enabled for a connection. It allows to set metadata for the qlog trace.

The trace will be written to the provided writer in the [JSON-SEQ format] defined in the qlog spec.

diff --git a/pr/451/docs/noq/struct.QlogFileFactory.html b/pr/451/docs/noq/struct.QlogFileFactory.html index 0f5a7038f..766ddebdf 100644 --- a/pr/451/docs/noq/struct.QlogFileFactory.html +++ b/pr/451/docs/noq/struct.QlogFileFactory.html @@ -1,4 +1,4 @@ -QlogFileFactory in noq - Rust

QlogFileFactory

Struct QlogFileFactory 

Source
pub struct QlogFileFactory { /* private fields */ }
Expand description

Enables writing qlog traces to a directory.

+QlogFileFactory in noq - Rust

QlogFileFactory

Struct QlogFileFactory 

Source
pub struct QlogFileFactory { /* private fields */ }
Expand description

Enables writing qlog traces to a directory.

Implementations§

Source§

impl QlogFileFactory

Source

pub fn new(dir: PathBuf) -> QlogFileFactory

Creates a new qlog factory that writes files into the specified directory.

Source

pub fn from_env() -> QlogFileFactory

Creates a new qlog factory that writes files into QLOGDIR, if set.

If the environment variable QLOGDIR is set, qlog traces for all connections handled diff --git a/pr/451/docs/noq/struct.ReadDatagram.html b/pr/451/docs/noq/struct.ReadDatagram.html index 9d8069388..fd1f86a4d 100644 --- a/pr/451/docs/noq/struct.ReadDatagram.html +++ b/pr/451/docs/noq/struct.ReadDatagram.html @@ -1,6 +1,6 @@ -ReadDatagram in noq - Rust

ReadDatagram

Struct ReadDatagram 

Source
pub struct ReadDatagram<'a> { /* private fields */ }
Expand description

Future produced by Connection::read_datagram

-

Trait Implementations§

Source§

impl Future for ReadDatagram<'_>

Source§

type Output = Result<Bytes, ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering -the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for ReadDatagram<'a>
where +ReadDatagram in noq - Rust

ReadDatagram

Struct ReadDatagram 

Source
pub struct ReadDatagram<'a> { /* private fields */ }
Expand description

Future produced by Connection::read_datagram

+

Trait Implementations§

Source§

impl Future for ReadDatagram<'_>

Source§

type Output = Result<Bytes, ConnectionError>

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering +the current task for wakeup if the value is not yet available. Read more
Source§

impl<'__pin, 'a> Unpin for ReadDatagram<'a>
where PinnedFieldsOf<__Origin<'__pin, 'a>>: Unpin,

Auto Trait Implementations§

§

impl<'a> !Freeze for ReadDatagram<'a>

§

impl<'a> !RefUnwindSafe for ReadDatagram<'a>

§

impl<'a> Send for ReadDatagram<'a>

§

impl<'a> Sync for ReadDatagram<'a>

§

impl<'a> !UnwindSafe for ReadDatagram<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq/struct.RecvStream.html b/pr/451/docs/noq/struct.RecvStream.html index 7fc5eff9a..d281e6142 100644 --- a/pr/451/docs/noq/struct.RecvStream.html +++ b/pr/451/docs/noq/struct.RecvStream.html @@ -1,4 +1,4 @@ -RecvStream in noq - Rust

RecvStream

Struct RecvStream 

Source
pub struct RecvStream { /* private fields */ }
Expand description

A stream that can only be used to receive data

+RecvStream in noq - Rust

RecvStream

Struct RecvStream 

Source
pub struct RecvStream { /* private fields */ }
Expand description

A stream that can only be used to receive data

stop(0) is implicitly called on drop unless:

Source

pub fn keep_alive_interval( &mut self, value: Option<Duration>, ) -> &mut TransportConfig

Period of inactivity before sending a keep-alive packet

@@ -118,11 +118,11 @@ extension and may use it in other ways.

None to disable, which is the default. Only one side of any given connection needs keep-alive enabled for the connection to be preserved. Must be set lower than the idle_timeout of both peers to be effective.

-
Source

pub fn crypto_buffer_size(&mut self, value: usize) -> &mut TransportConfig

Maximum quantity of out-of-order crypto layer data to buffer

-
Source

pub fn allow_spin(&mut self, value: bool) -> &mut TransportConfig

Whether the implementation is permitted to set the spin bit on this connection

+
Source

pub fn crypto_buffer_size(&mut self, value: usize) -> &mut TransportConfig

Maximum quantity of out-of-order crypto layer data to buffer

+
Source

pub fn allow_spin(&mut self, value: bool) -> &mut TransportConfig

Whether the implementation is permitted to set the spin bit on this connection

This allows passive observers to easily judge the round trip time of a connection, which can be useful for network administration but sacrifices a small amount of privacy.

-
Source

pub fn datagram_receive_buffer_size( +

Source

pub fn datagram_receive_buffer_size( &mut self, value: Option<usize>, ) -> &mut TransportConfig

Maximum number of incoming application datagram bytes to buffer, or None to disable @@ -130,7 +130,7 @@ incoming datagrams

The peer is forbidden to send single datagrams larger than this size. If the aggregate size of all datagrams that have been received from the peer but not consumed by the application exceeds this value, old datagrams are dropped until it is no longer exceeded.

-
Source

pub fn datagram_send_buffer_size( +

Source

pub fn datagram_send_buffer_size( &mut self, value: usize, ) -> &mut TransportConfig

Maximum number of outgoing application datagram bytes to buffer

@@ -138,7 +138,7 @@ exceeds this value, old datagrams are dropped until it is no longer exceeded.

-
Source

pub fn congestion_controller_factory( +

Source

pub fn congestion_controller_factory( &mut self, factory: Arc<dyn ControllerFactory + Send + Sync>, ) -> &mut TransportConfig

How to construct new congestion::Controllers

@@ -146,7 +146,7 @@ sent, older datagrams are dropped until sufficient space is available.

e.g. a congestion::NewRenoConfig.

§Example
let mut config = TransportConfig::default();
-config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));
Source

pub fn enable_segmentation_offload( +config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));

Source

pub fn enable_segmentation_offload( &mut self, enabled: bool, ) -> &mut TransportConfig

Whether to use “Generic Segmentation Offload” to accelerate transmits, when supported by the @@ -157,13 +157,13 @@ headers, such as when transmitting bulk data on a connection. However, it is not by all network interface drivers or packet inspection tools. noq-udp will attempt to disable GSO automatically when unavailable, but this can lead to spurious packet loss at startup, temporarily degrading performance.

-
Source

pub fn send_observed_address_reports( +

Source

pub fn send_observed_address_reports( &mut self, enabled: bool, ) -> &mut TransportConfig

Whether to send observed address reports to peers.

This will aid peers in inferring their reachable address, which in most NATd networks will not be easily available to them.

-
Source

pub fn receive_observed_address_reports( +

Source

pub fn receive_observed_address_reports( &mut self, enabled: bool, ) -> &mut TransportConfig

Whether to receive observed address reports from other peers.

@@ -209,26 +209,39 @@ the Nat Traversal Extension for QUIC, see https://www.ietf.org/archive/id/draft-seemann-quic-nat-traversal-02.html

This implementation expects the multipath extension to be enabled as well. if not yet enabled via Self::max_concurrent_multipath_paths, then that setting is set to 8.

-
Source

pub fn qlog_factory( +

Source

pub fn server_handshake_migration( + &mut self, + allow_migration: bool, +) -> &mut TransportConfig

Sets whether the server is allowed to migrate once during the handshake.

+

Enabling this is not RFC9000 compliant.

+

Defaults to false.

+

Enabling this allows the server to migrate once during the handshake: it can send a +response from a different address than the client’s initial packet was sent to. Once +an authenticated Handshake packet is received the server can no longer migrate +during the handshake (or after the handshake if not other extension enables this).

+

This can be used to duplicate the client’s initial packet to multiple addresses for +the server and accept the fastest response. The server will discard all but the +first such initial, considering any remaining as duplicates.

+
Source

pub fn qlog_factory( &mut self, factory: Arc<dyn QlogFactory>, ) -> &mut TransportConfig

Configures qlog capturing by setting a QlogFactory.

This assigns a QlogFactory that produces qlog capture configurations for individual connections.

-
Source

pub fn qlog_from_env(&mut self, prefix: &str) -> &mut TransportConfig

Configures qlog capturing through the QLOGDIR environment variable.

+
Source

pub fn qlog_from_env(&mut self, prefix: &str) -> &mut TransportConfig

Configures qlog capturing through the QLOGDIR environment variable.

This uses QlogFileFactory::from_env to create a factory to write qlog traces into the directory set through the QLOGDIR environment variable.

If QLOGDIR is not set, no traces will be written. If QLOGDIR is set to a path that does not exist, it will be created.

The files will be prefixed with prefix.

-
Source

pub fn qlog_from_path( +

Source

pub fn qlog_from_path( &mut self, path: impl AsRef<Path>, prefix: &str, ) -> &mut TransportConfig

Configures qlog capturing into a directory.

This uses QlogFileFactory to create a factory to write qlog traces into the specified directory. The files will be prefixed with prefix.

-

Trait Implementations§

Source§

impl Clone for TransportConfig

Source§

fn clone(&self) -> TransportConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for TransportConfig

Source§

fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for TransportConfig

Source§

fn default() -> TransportConfig

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for TransportConfig

Source§

fn clone(&self) -> TransportConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for TransportConfig

Source§

fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for TransportConfig

Source§

fn default() -> TransportConfig

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.TransportErrorCode.html b/pr/451/docs/noq/struct.TransportErrorCode.html index 33617fda7..ce969f965 100644 --- a/pr/451/docs/noq/struct.TransportErrorCode.html +++ b/pr/451/docs/noq/struct.TransportErrorCode.html @@ -1,4 +1,4 @@ -TransportErrorCode in noq - Rust

TransportErrorCode

Struct TransportErrorCode 

Source
pub struct TransportErrorCode(/* private fields */);
Expand description

Transport-level error code

+TransportErrorCode in noq - Rust

TransportErrorCode

Struct TransportErrorCode 

Source
pub struct TransportErrorCode(/* private fields */);
Expand description

Transport-level error code

Implementations§

Source§

impl Code

Source

pub fn crypto(code: u8) -> Code

Create QUIC error code from TLS alert code

Source§

impl Code

Source

pub const NO_ERROR: Code

the connection is being closed abruptly in the absence of any error

Source

pub const INTERNAL_ERROR: Code

the endpoint encountered an internal error and cannot continue with the connection

diff --git a/pr/451/docs/noq/struct.UdpStats.html b/pr/451/docs/noq/struct.UdpStats.html index 1d5318a99..98b464d6c 100644 --- a/pr/451/docs/noq/struct.UdpStats.html +++ b/pr/451/docs/noq/struct.UdpStats.html @@ -1,4 +1,4 @@ -UdpStats in noq - Rust

UdpStats

Struct UdpStats 

Source
#[non_exhaustive]
pub struct UdpStats { +UdpStats in noq - Rust

UdpStats

Struct UdpStats 

Source
#[non_exhaustive]
pub struct UdpStats { pub datagrams: u64, pub bytes: u64, pub ios: u64, @@ -7,8 +7,8 @@ on a connection.

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§datagrams: u64

The number of UDP datagrams observed.

§bytes: u64

The total amount of bytes which have been transferred inside UDP datagrams.

-
§ios: u64

The number of I/O operations executed.

-

Can be less than datagrams when GSO, GRO, and/or batched system calls are in use.

+
§ios: u64
👎Deprecated since 1.0.2: IO counting can’t be meaningfully measured from this crate. See https://github.com/n0-computer/noq/issues/727

The number of I/O operations executed.

+

This can’t be measured from this crate and will always be 0

Trait Implementations§

Source§

impl Add for UdpStats

Source§

type Output = UdpStats

The resulting type after applying the + operator.
Source§

fn add(self, __rhs: UdpStats) -> <UdpStats as Add>::Output

Performs the + operation. Read more
Source§

impl AddAssign for UdpStats

Source§

fn add_assign(&mut self, __rhs: UdpStats)

Performs the += operation. Read more
Source§

impl Clone for UdpStats

Source§

fn clone(&self) -> UdpStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for UdpStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for UdpStats

Source§

fn default() -> UdpStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for UdpStats

Source§

fn eq(&self, other: &UdpStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for UdpStats

Source§

impl Eq for UdpStats

Source§

impl StructuralPartialEq for UdpStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where diff --git a/pr/451/docs/noq/struct.UnorderedRecvStream.html b/pr/451/docs/noq/struct.UnorderedRecvStream.html index 0346d209d..de57b1482 100644 --- a/pr/451/docs/noq/struct.UnorderedRecvStream.html +++ b/pr/451/docs/noq/struct.UnorderedRecvStream.html @@ -1,4 +1,4 @@ -UnorderedRecvStream in noq - Rust

UnorderedRecvStream

Struct UnorderedRecvStream 

Source
pub struct UnorderedRecvStream { /* private fields */ }
Expand description

A stream that can be used to receive data out-of-order.

+UnorderedRecvStream in noq - Rust

UnorderedRecvStream

Struct UnorderedRecvStream 

Source
pub struct UnorderedRecvStream { /* private fields */ }
Expand description

A stream that can be used to receive data out-of-order.

Obtained by converting a RecvStream via RecvStream::into_unordered.

This variant of RecvStream allows reading chunks of data exclusively out of order. Once you have done an unordered read, ordered reads are no diff --git a/pr/451/docs/noq/struct.ValidationTokenConfig.html b/pr/451/docs/noq/struct.ValidationTokenConfig.html index 3c6d9b8f2..222fe7db7 100644 --- a/pr/451/docs/noq/struct.ValidationTokenConfig.html +++ b/pr/451/docs/noq/struct.ValidationTokenConfig.html @@ -1,4 +1,4 @@ -ValidationTokenConfig in noq - Rust

ValidationTokenConfig

Struct ValidationTokenConfig 

Source
pub struct ValidationTokenConfig { /* private fields */ }
Expand description

Configuration for sending and handling validation tokens in incoming connections

+ValidationTokenConfig in noq - Rust

ValidationTokenConfig

Struct ValidationTokenConfig 

Source
pub struct ValidationTokenConfig { /* private fields */ }
Expand description

Configuration for sending and handling validation tokens in incoming connections

Default values should be suitable for most internet applications.

§QUIC Tokens

The QUIC protocol defines a concept of “address validation”. Essentially, one side of a diff --git a/pr/451/docs/noq/struct.VarInt.html b/pr/451/docs/noq/struct.VarInt.html index 019c86b4c..80eb2a038 100644 --- a/pr/451/docs/noq/struct.VarInt.html +++ b/pr/451/docs/noq/struct.VarInt.html @@ -1,4 +1,4 @@ -VarInt in noq - Rust

VarInt

Struct VarInt 

Source
pub struct VarInt(/* private fields */);
Expand description

An integer less than 2^62

+VarInt in noq - Rust

VarInt

Struct VarInt 

Source
pub struct VarInt(/* private fields */);
Expand description

An integer less than 2^62

Values of this type are suitable for encoding as QUIC variable-length integer.

Implementations§

Source§

impl VarInt

Source

pub const MAX: VarInt

The largest representable value

Source

pub const MAX_SIZE: usize = 8usize

The largest encoded value length

@@ -17,7 +17,7 @@ needs to construct itself. Read more
Result<(usize, Option<usize>), MaxRecursionReached>

Get a size hint for how many bytes out of an Unstructured this type needs to construct itself. Read more
Source§

impl Clone for VarInt

Source§

fn clone(&self) -> VarInt

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for VarInt

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Decodable for VarInt

Source§

fn decode<B>(r: &mut B) -> Result<VarInt, UnexpectedEnd>
where B: Buf,

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Default for VarInt

Source§

fn default() -> VarInt

Returns the “default value” for a type. Read more
Source§

impl Display for VarInt

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Encodable for VarInt

Source§

fn encode<B>(&self, w: &mut B)
where - B: BufMut,

Append the encoding of self to the provided buffer.
Source§

impl From<StreamId> for VarInt

Source§

fn from(x: StreamId) -> VarInt

Converts to this type from the input type.
Source§

impl From<Code> for VarInt

Source§

fn from(value: Code) -> VarInt

Converts to this type from the input type.
Source§

impl From<VarInt> for IdleTimeout

Source§

fn from(inner: VarInt) -> IdleTimeout

Converts to this type from the input type.
Source§

impl From<VarInt> for StreamId

Source§

fn from(v: VarInt) -> StreamId

Converts to this type from the input type.
Source§

impl From<VarInt> for Code

Source§

fn from(value: VarInt) -> Code

Converts to this type from the input type.
Source§

impl From<u16> for VarInt

Source§

fn from(x: u16) -> VarInt

Converts to this type from the input type.
Source§

impl From<u32> for VarInt

Source§

fn from(x: u32) -> VarInt

Converts to this type from the input type.
Source§

impl From<u8> for VarInt

Source§

fn from(x: u8) -> VarInt

Converts to this type from the input type.
Source§

impl Hash for VarInt

Source§

fn hash<__H>(&self, state: &mut __H)
where + B: BufMut,

Append the encoding of self to the provided buffer.
Source§

impl From<StreamId> for VarInt

Source§

fn from(x: StreamId) -> VarInt

Converts to this type from the input type.
Source§

impl From<Code> for VarInt

Source§

fn from(value: Code) -> VarInt

Converts to this type from the input type.
Source§

impl From<VarInt> for IdleTimeout

Source§

fn from(inner: VarInt) -> IdleTimeout

Converts to this type from the input type.
Source§

impl From<VarInt> for StreamId

Source§

fn from(v: VarInt) -> StreamId

Converts to this type from the input type.
Source§

impl From<VarInt> for Code

Source§

fn from(value: VarInt) -> Code

Converts to this type from the input type.
Source§

impl From<u16> for VarInt

Source§

fn from(x: u16) -> VarInt

Converts to this type from the input type.
Source§

impl From<u32> for VarInt

Source§

fn from(x: u32) -> VarInt

Converts to this type from the input type.
Source§

impl From<u8> for VarInt

Source§

fn from(x: u8) -> VarInt

Converts to this type from the input type.
Source§

impl Hash for VarInt

Source§

fn hash<__H>(&self, state: &mut __H)
where __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for VarInt

Source§

fn cmp(&self, other: &VarInt) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where diff --git a/pr/451/docs/noq/struct.VarIntBoundsExceeded.html b/pr/451/docs/noq/struct.VarIntBoundsExceeded.html index 932d303ac..450516348 100644 --- a/pr/451/docs/noq/struct.VarIntBoundsExceeded.html +++ b/pr/451/docs/noq/struct.VarIntBoundsExceeded.html @@ -1,4 +1,4 @@ -VarIntBoundsExceeded in noq - Rust

VarIntBoundsExceeded

Struct VarIntBoundsExceeded 

Source
pub struct VarIntBoundsExceeded;
Expand description

Error returned when constructing a VarInt from a value >= 2^62

+VarIntBoundsExceeded in noq - Rust

VarIntBoundsExceeded

Struct VarIntBoundsExceeded 

Source
pub struct VarIntBoundsExceeded;
Expand description

Error returned when constructing a VarInt from a value >= 2^62

Trait Implementations§

Source§

impl Clone for VarIntBoundsExceeded

Source§

fn clone(&self) -> VarIntBoundsExceeded

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for VarIntBoundsExceeded

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Display for VarIntBoundsExceeded

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Error for VarIntBoundsExceeded

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<VarIntBoundsExceeded> for ConfigError

Source§

fn from(_: VarIntBoundsExceeded) -> ConfigError

Converts to this type from the input type.
Source§

impl PartialEq for VarIntBoundsExceeded

Source§

fn eq(&self, other: &VarIntBoundsExceeded) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for VarIntBoundsExceeded

Source§

impl Eq for VarIntBoundsExceeded

Source§

impl StructuralPartialEq for VarIntBoundsExceeded

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where diff --git a/pr/451/docs/noq/struct.WeakConnectionHandle.html b/pr/451/docs/noq/struct.WeakConnectionHandle.html index 1564e2801..bcd745c5d 100644 --- a/pr/451/docs/noq/struct.WeakConnectionHandle.html +++ b/pr/451/docs/noq/struct.WeakConnectionHandle.html @@ -1,10 +1,10 @@ -WeakConnectionHandle in noq - Rust

WeakConnectionHandle

Struct WeakConnectionHandle 

Source
pub struct WeakConnectionHandle(/* private fields */);
Expand description

A handle to some connection internals, use with care.

+WeakConnectionHandle in noq - Rust

WeakConnectionHandle

Struct WeakConnectionHandle 

Source
pub struct WeakConnectionHandle(/* private fields */);
Expand description

A handle to some connection internals, use with care.

This contains a weak reference to the connection so will not itself keep the connection alive.

-

Implementations§

Source§

impl WeakConnectionHandle

Source

pub fn is_alive(&self) -> bool

Returns true if the Connection associated with this handle is still alive.

-
Source

pub fn upgrade(&self) -> Option<Connection>

Upgrade the handle to a full Connection

-
Source

pub fn is_same_connection(&self, other: &Self) -> bool

Returns true if the two WeakConnectionHandle point at the same connection.

-

Trait Implementations§

Source§

impl Clone for WeakConnectionHandle

Source§

fn clone(&self) -> WeakConnectionHandle

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for WeakConnectionHandle

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Implementations§

Source§

impl WeakConnectionHandle

Source

pub fn is_alive(&self) -> bool

Returns true if the Connection associated with this handle is still alive.

+
Source

pub fn upgrade(&self) -> Option<Connection>

Upgrade the handle to a full Connection

+
Source

pub fn is_same_connection(&self, other: &Self) -> bool

Returns true if the two WeakConnectionHandle point at the same connection.

+

Trait Implementations§

Source§

impl Clone for WeakConnectionHandle

Source§

fn clone(&self) -> WeakConnectionHandle

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for WeakConnectionHandle

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.WeakPathHandle.html b/pr/451/docs/noq/struct.WeakPathHandle.html index 9a89357ef..b0f516d6d 100644 --- a/pr/451/docs/noq/struct.WeakPathHandle.html +++ b/pr/451/docs/noq/struct.WeakPathHandle.html @@ -1,12 +1,12 @@ -WeakPathHandle in noq - Rust

WeakPathHandle

Struct WeakPathHandle 

Source
pub struct WeakPathHandle { /* private fields */ }
Expand description

Weak handle for a Path that does not keep the connection alive.

+WeakPathHandle in noq - Rust

WeakPathHandle

Struct WeakPathHandle 

Source
pub struct WeakPathHandle { /* private fields */ }
Expand description

Weak handle for a Path that does not keep the connection alive.

As long as a WeakPathHandle for a path exists, that path’s final stats will not be dropped even if the path was abandoned.

The WeakPathHandle can be upgraded to a Path as long as its Connection has not been dropped.

-

Implementations§

Source§

impl WeakPathHandle

Source

pub fn id(&self) -> PathId

Returns the PathId of this path.

-
Source

pub fn upgrade(&self) -> Option<Path>

Upgrades to a Path.

+

Implementations§

Source§

impl WeakPathHandle

Source

pub fn id(&self) -> PathId

Returns the PathId of this path.

+
Source

pub fn upgrade(&self) -> Option<Path>

Upgrades to a Path.

Returns None if the connection was dropped.

-

Trait Implementations§

Source§

impl Clone for WeakPathHandle

Source§

fn clone(&self) -> WeakPathHandle

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for WeakPathHandle

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for WeakPathHandle

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl PartialEq for WeakPathHandle

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for WeakPathHandle

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for WeakPathHandle

Source§

fn clone(&self) -> WeakPathHandle

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for WeakPathHandle

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for WeakPathHandle

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl PartialEq for WeakPathHandle

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for WeakPathHandle

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq/struct.ZeroRttAccepted.html b/pr/451/docs/noq/struct.ZeroRttAccepted.html index 619eea487..e24ff3314 100644 --- a/pr/451/docs/noq/struct.ZeroRttAccepted.html +++ b/pr/451/docs/noq/struct.ZeroRttAccepted.html @@ -1,4 +1,4 @@ -ZeroRttAccepted in noq - Rust

ZeroRttAccepted

Struct ZeroRttAccepted 

Source
pub struct ZeroRttAccepted(/* private fields */);
Expand description

Future that completes when a connection is fully established

+ZeroRttAccepted in noq - Rust

ZeroRttAccepted

Struct ZeroRttAccepted 

Source
pub struct ZeroRttAccepted(/* private fields */);
Expand description

Future that completes when a connection is fully established

For clients, the resulting value indicates if 0-RTT was accepted. For servers, the resulting value is meaningless.

Trait Implementations§

Source§

impl Future for ZeroRttAccepted

Source§

type Output = bool

The type of value produced on completion.
Source§

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>

Attempts to resolve the future to a final value, registering diff --git a/pr/451/docs/noq/trait.AsyncTimer.html b/pr/451/docs/noq/trait.AsyncTimer.html index 69a410798..cfb8effbb 100644 --- a/pr/451/docs/noq/trait.AsyncTimer.html +++ b/pr/451/docs/noq/trait.AsyncTimer.html @@ -1,4 +1,4 @@ -AsyncTimer in noq - Rust

AsyncTimer

Trait AsyncTimer 

Source
pub trait AsyncTimer:
+AsyncTimer in noq - Rust

AsyncTimer

Trait AsyncTimer 

Source
pub trait AsyncTimer:
     Send
     + Debug
     + 'static {
diff --git a/pr/451/docs/noq/trait.AsyncUdpSocket.html b/pr/451/docs/noq/trait.AsyncUdpSocket.html
index 5f1665cfe..8cbe51e67 100644
--- a/pr/451/docs/noq/trait.AsyncUdpSocket.html
+++ b/pr/451/docs/noq/trait.AsyncUdpSocket.html
@@ -1,4 +1,4 @@
-AsyncUdpSocket in noq - Rust

AsyncUdpSocket

Trait AsyncUdpSocket 

Source
pub trait AsyncUdpSocket:
+AsyncUdpSocket in noq - Rust

AsyncUdpSocket

Trait AsyncUdpSocket 

Source
pub trait AsyncUdpSocket:
     Send
     + Sync
     + Debug
diff --git a/pr/451/docs/noq/trait.ConnectionIdGenerator.html b/pr/451/docs/noq/trait.ConnectionIdGenerator.html
index 3f1194e89..36eee35f7 100644
--- a/pr/451/docs/noq/trait.ConnectionIdGenerator.html
+++ b/pr/451/docs/noq/trait.ConnectionIdGenerator.html
@@ -1,4 +1,4 @@
-ConnectionIdGenerator in noq - Rust

ConnectionIdGenerator

Trait ConnectionIdGenerator 

Source
pub trait ConnectionIdGenerator: Send + Sync {
+ConnectionIdGenerator in noq - Rust

ConnectionIdGenerator

Trait ConnectionIdGenerator 

Source
pub trait ConnectionIdGenerator: Send + Sync {
     // Required methods
     fn generate_cid(&mut self) -> ConnectionId;
     fn cid_len(&self) -> usize;
diff --git a/pr/451/docs/noq/trait.NetworkChangeHint.html b/pr/451/docs/noq/trait.NetworkChangeHint.html
index ecab8b037..321c09255 100644
--- a/pr/451/docs/noq/trait.NetworkChangeHint.html
+++ b/pr/451/docs/noq/trait.NetworkChangeHint.html
@@ -1,12 +1,12 @@
-NetworkChangeHint in noq - Rust

NetworkChangeHint

Trait NetworkChangeHint 

Source
pub trait NetworkChangeHint: Debug + 'static {
+NetworkChangeHint in noq - Rust

NetworkChangeHint

Trait NetworkChangeHint 

Source
pub trait NetworkChangeHint: Debug + 'static {
     // Required method
     fn is_path_recoverable(
         &self,
         path_id: PathId,
-        network_path: FourTuple,
+        network_path: FourTuple,
     ) -> bool;
 }
Expand description

Hints when the caller identifies a network change.

-

Required Methods§

Source

fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool

Inform the connection if a path may recover after a network change.

+

Required Methods§

Source

fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool

Inform the connection if a path may recover after a network change.

After network changes, paths may not be recoverable. In this case, waiting for the path to become idle may take longer than what is desirable. If Self::is_path_recoverable returns false, a multipath-enabled, client-side connection will establish a new path to diff --git a/pr/451/docs/noq/trait.QlogFactory.html b/pr/451/docs/noq/trait.QlogFactory.html index dacb6954d..a0d82eff4 100644 --- a/pr/451/docs/noq/trait.QlogFactory.html +++ b/pr/451/docs/noq/trait.QlogFactory.html @@ -1,4 +1,4 @@ -QlogFactory in noq - Rust

QlogFactory

Trait QlogFactory 

Source
pub trait QlogFactory:
+QlogFactory in noq - Rust

QlogFactory

Trait QlogFactory 

Source
pub trait QlogFactory:
     Send
     + Sync
     + 'static {
diff --git a/pr/451/docs/noq/trait.Runtime.html b/pr/451/docs/noq/trait.Runtime.html
index 6a41acc6a..8d852247a 100644
--- a/pr/451/docs/noq/trait.Runtime.html
+++ b/pr/451/docs/noq/trait.Runtime.html
@@ -1,4 +1,4 @@
-Runtime in noq - Rust

Runtime

Trait Runtime 

Source
pub trait Runtime:
+Runtime in noq - Rust

Runtime

Trait Runtime 

Source
pub trait Runtime:
     Send
     + Sync
     + Debug
diff --git a/pr/451/docs/noq/trait.TimeSource.html b/pr/451/docs/noq/trait.TimeSource.html
index 50381acdd..1a0366faa 100644
--- a/pr/451/docs/noq/trait.TimeSource.html
+++ b/pr/451/docs/noq/trait.TimeSource.html
@@ -1,4 +1,4 @@
-TimeSource in noq - Rust

TimeSource

Trait TimeSource 

Source
pub trait TimeSource: Send + Sync {
+TimeSource in noq - Rust

TimeSource

Trait TimeSource 

Source
pub trait TimeSource: Send + Sync {
     // Required method
     fn now(&self) -> SystemTime;
 }
Expand description

Object to get current SystemTime

diff --git a/pr/451/docs/noq/trait.TokenLog.html b/pr/451/docs/noq/trait.TokenLog.html index 1c9909080..25870c9e7 100644 --- a/pr/451/docs/noq/trait.TokenLog.html +++ b/pr/451/docs/noq/trait.TokenLog.html @@ -1,4 +1,4 @@ -TokenLog in noq - Rust

TokenLog

Trait TokenLog 

Source
pub trait TokenLog: Send + Sync {
+TokenLog in noq - Rust

TokenLog

Trait TokenLog 

Source
pub trait TokenLog: Send + Sync {
     // Required method
     fn check_and_insert(
         &self,
diff --git a/pr/451/docs/noq/trait.TokenStore.html b/pr/451/docs/noq/trait.TokenStore.html
index 5aab57592..72d37c117 100644
--- a/pr/451/docs/noq/trait.TokenStore.html
+++ b/pr/451/docs/noq/trait.TokenStore.html
@@ -1,4 +1,4 @@
-TokenStore in noq - Rust

TokenStore

Trait TokenStore 

Source
pub trait TokenStore: Send + Sync {
+TokenStore in noq - Rust

TokenStore

Trait TokenStore 

Source
pub trait TokenStore: Send + Sync {
     // Required methods
     fn insert(&self, server_name: &str, token: Bytes);
     fn take(&self, server_name: &str) -> Option<Bytes>;
diff --git a/pr/451/docs/noq/trait.UdpSender.html b/pr/451/docs/noq/trait.UdpSender.html
index 815e79037..4c5408da4 100644
--- a/pr/451/docs/noq/trait.UdpSender.html
+++ b/pr/451/docs/noq/trait.UdpSender.html
@@ -1,4 +1,4 @@
-UdpSender in noq - Rust

UdpSender

Trait UdpSender 

Source
pub trait UdpSender:
+UdpSender in noq - Rust

UdpSender

Trait UdpSender 

Source
pub trait UdpSender:
     Send
     + Sync
     + Debug
diff --git a/pr/451/docs/noq_proto/all.html b/pr/451/docs/noq_proto/all.html
index 47122df23..c044b32ad 100644
--- a/pr/451/docs/noq_proto/all.html
+++ b/pr/451/docs/noq_proto/all.html
@@ -1 +1 @@
-List of all items in this crate

All

List of all items

Structs

Enums

Traits

Functions

Type Aliases

Constants

\ No newline at end of file +List of all items in this crate

All

List of all items

Structs

Enums

Traits

Functions

Type Aliases

Constants

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/bench_exports/index.html b/pr/451/docs/noq_proto/bench_exports/index.html index 979bc1d0c..7ee104d88 100644 --- a/pr/451/docs/noq_proto/bench_exports/index.html +++ b/pr/451/docs/noq_proto/bench_exports/index.html @@ -1,2 +1,2 @@ -noq_proto::bench_exports - Rust

Module bench_exports

Module bench_exports 

Source
Expand description

Exports for benchmarks

+noq_proto::bench_exports - Rust

Module bench_exports

Module bench_exports 

Source
Expand description

Exports for benchmarks

Modules§

send_buffer_benches
Bench fns for SendBuffer
\ No newline at end of file diff --git a/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/fn.get_into_many_segments.html b/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/fn.get_into_many_segments.html index 4a9e578b1..bace2d69e 100644 --- a/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/fn.get_into_many_segments.html +++ b/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/fn.get_into_many_segments.html @@ -1,2 +1,2 @@ -get_into_many_segments in noq_proto::bench_exports::send_buffer_benches - Rust

get_into_many_segments

Function get_into_many_segments 

Source
pub fn get_into_many_segments(criterion: &mut Criterion)
Expand description

Pathological case: many segments, get from end

+get_into_many_segments in noq_proto::bench_exports::send_buffer_benches - Rust

get_into_many_segments

Function get_into_many_segments 

Source
pub fn get_into_many_segments(criterion: &mut Criterion)
Expand description

Pathological case: many segments, get from end

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/fn.get_loop_many_segments.html b/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/fn.get_loop_many_segments.html index 5e22a090a..96c34bcd5 100644 --- a/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/fn.get_loop_many_segments.html +++ b/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/fn.get_loop_many_segments.html @@ -1,2 +1,2 @@ -get_loop_many_segments in noq_proto::bench_exports::send_buffer_benches - Rust

get_loop_many_segments

Function get_loop_many_segments 

Source
pub fn get_loop_many_segments(criterion: &mut Criterion)
Expand description

Get segments in the old way, using a loop of get calls

+get_loop_many_segments in noq_proto::bench_exports::send_buffer_benches - Rust

get_loop_many_segments

Function get_loop_many_segments 

Source
pub fn get_loop_many_segments(criterion: &mut Criterion)
Expand description

Get segments in the old way, using a loop of get calls

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/index.html b/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/index.html index 79793736f..3cf4d1e0e 100644 --- a/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/index.html +++ b/pr/451/docs/noq_proto/bench_exports/send_buffer_benches/index.html @@ -1,4 +1,4 @@ -noq_proto::bench_exports::send_buffer_benches - Rust

Module send_buffer_benches

Module send_buffer_benches 

Source
Expand description

Bench fns for SendBuffer

+noq_proto::bench_exports::send_buffer_benches - Rust

Module send_buffer_benches

Module send_buffer_benches 

Source
Expand description

Bench fns for SendBuffer

These are defined here and re-exported via bench_exports in lib.rs, so we can access the private SendBuffer struct.

Functions§

get_into_many_segments
Pathological case: many segments, get from end
get_loop_many_segments
Get segments in the old way, using a loop of get calls
\ No newline at end of file diff --git a/pr/451/docs/noq_proto/coding/index.html b/pr/451/docs/noq_proto/coding/index.html index f1afc845f..2e058db4a 100644 --- a/pr/451/docs/noq_proto/coding/index.html +++ b/pr/451/docs/noq_proto/coding/index.html @@ -1,2 +1,2 @@ -noq_proto::coding - Rust

Module coding

Module coding 

Source
Expand description

Coding related traits.

+noq_proto::coding - Rust

Module coding

Module coding 

Source
Expand description

Coding related traits.

Structs§

UnexpectedEnd
Error indicating that the provided buffer was too small

Traits§

Decodable
Decoding of QUIC primitives.
Encodable
Infallible encoding of QUIC primitives.

Type Aliases§

Result
Coding result type
\ No newline at end of file diff --git a/pr/451/docs/noq_proto/coding/struct.UnexpectedEnd.html b/pr/451/docs/noq_proto/coding/struct.UnexpectedEnd.html index 566244dec..e9d2550b9 100644 --- a/pr/451/docs/noq_proto/coding/struct.UnexpectedEnd.html +++ b/pr/451/docs/noq_proto/coding/struct.UnexpectedEnd.html @@ -1,4 +1,4 @@ -UnexpectedEnd in noq_proto::coding - Rust

UnexpectedEnd

Struct UnexpectedEnd 

Source
pub struct UnexpectedEnd;
Expand description

Error indicating that the provided buffer was too small

+UnexpectedEnd in noq_proto::coding - Rust

UnexpectedEnd

Struct UnexpectedEnd 

Source
pub struct UnexpectedEnd;
Expand description

Error indicating that the provided buffer was too small

Trait Implementations§

Source§

impl Clone for UnexpectedEnd

Source§

fn clone(&self) -> UnexpectedEnd

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for UnexpectedEnd

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for UnexpectedEnd

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for UnexpectedEnd

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<UnexpectedEnd> for Error

Source§

fn from(_: UnexpectedEnd) -> Self

Converts to this type from the input type.
Source§

impl From<UnexpectedEnd> for PacketDecodeError

Source§

fn from(_: UnexpectedEnd) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for UnexpectedEnd

Source§

fn eq(&self, other: &UnexpectedEnd) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for UnexpectedEnd

Source§

impl Eq for UnexpectedEnd

Source§

impl StructuralPartialEq for UnexpectedEnd

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where diff --git a/pr/451/docs/noq_proto/coding/trait.Decodable.html b/pr/451/docs/noq_proto/coding/trait.Decodable.html index a41385918..5c01c55c1 100644 --- a/pr/451/docs/noq_proto/coding/trait.Decodable.html +++ b/pr/451/docs/noq_proto/coding/trait.Decodable.html @@ -1,6 +1,6 @@ -Decodable in noq_proto::coding - Rust

Decodable

Trait Decodable 

Source
pub trait Decodable: Sized {
+Decodable in noq_proto::coding - Rust

Decodable

Trait Decodable 

Source
pub trait Decodable: Sized {
     // Required method
     fn decode<B: Buf>(buf: &mut B) -> Result<Self>;
 }
Expand description

Decoding of QUIC primitives.

Required Methods§

Source

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Decode a Self from the provided buffer, if the buffer is large enough

-

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementations on Foreign Types§

Source§

impl Decodable for u8

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for u16

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for u32

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for u64

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for Ipv4Addr

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for Ipv6Addr

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Implementors§

\ No newline at end of file +

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementations on Foreign Types§

Source§

impl Decodable for u8

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for u16

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for u32

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for u64

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for Ipv4Addr

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Source§

impl Decodable for Ipv6Addr

Source§

fn decode<B: Buf>(buf: &mut B) -> Result<Self>

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/coding/trait.Encodable.html b/pr/451/docs/noq_proto/coding/trait.Encodable.html index 3afc587dc..8724f4b33 100644 --- a/pr/451/docs/noq_proto/coding/trait.Encodable.html +++ b/pr/451/docs/noq_proto/coding/trait.Encodable.html @@ -1,6 +1,6 @@ -Encodable in noq_proto::coding - Rust

Encodable

Trait Encodable 

Source
pub trait Encodable {
+Encodable in noq_proto::coding - Rust

Encodable

Trait Encodable 

Source
pub trait Encodable {
     // Required method
     fn encode<B: BufMut>(&self, buf: &mut B);
 }
Expand description

Infallible encoding of QUIC primitives.

Required Methods§

Source

fn encode<B: BufMut>(&self, buf: &mut B)

Append the encoding of self to the provided buffer.

-

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementations on Foreign Types§

Source§

impl Encodable for u8

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for u16

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for u32

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for u64

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for Ipv4Addr

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for Ipv6Addr

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Implementors§

\ No newline at end of file +

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementations on Foreign Types§

Source§

impl Encodable for u8

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for u16

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for u32

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for u64

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for Ipv4Addr

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Source§

impl Encodable for Ipv6Addr

Source§

fn encode<B: BufMut>(&self, buf: &mut B)

Implementors§

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/coding/type.Result.html b/pr/451/docs/noq_proto/coding/type.Result.html index e17ede1f4..b5bc9913e 100644 --- a/pr/451/docs/noq_proto/coding/type.Result.html +++ b/pr/451/docs/noq_proto/coding/type.Result.html @@ -1,4 +1,4 @@ -Result in noq_proto::coding - Rust

Result

Type Alias Result 

Source
pub type Result<T> = Result<T, UnexpectedEnd>;
Expand description

Coding result type

+Result in noq_proto::coding - Rust

Result

Type Alias Result 

Source
pub type Result<T> = Result<T, UnexpectedEnd>;
Expand description

Coding result type

Aliased Type§

pub enum Result<T> {
     Ok(T),
     Err(UnexpectedEnd),
diff --git a/pr/451/docs/noq_proto/congestion/index.html b/pr/451/docs/noq_proto/congestion/index.html
index ee312440e..5dfd23738 100644
--- a/pr/451/docs/noq_proto/congestion/index.html
+++ b/pr/451/docs/noq_proto/congestion/index.html
@@ -1,4 +1,4 @@
-noq_proto::congestion - Rust

Module congestion

Module congestion 

Source
Expand description

Logic for controlling the rate at which data is sent

+noq_proto::congestion - Rust

Module congestion

Module congestion 

Source
Expand description

Logic for controlling the rate at which data is sent

Structs§

Bbr3
Experimental! Use at your own risk.
Bbr3Config
Configuration for the Bbr3 congestion controller
ControllerMetrics
Common congestion controller metrics used both for logging purposes but also to alter the pacing of the connection with pacing_rate and send_quantum
Cubic
The RFC8312 congestion controller, as widely used for TCP
CubicConfig
Configuration for the Cubic congestion controller
NewReno
A simple, standard congestion controller
NewRenoConfig
Configuration for the NewReno congestion controller

Traits§

Controller
Common interface for different congestion controllers
ControllerFactory
Constructs controllers on demand
\ No newline at end of file diff --git a/pr/451/docs/noq_proto/congestion/struct.Bbr3.html b/pr/451/docs/noq_proto/congestion/struct.Bbr3.html index dbb5082d5..4de2afd26 100644 --- a/pr/451/docs/noq_proto/congestion/struct.Bbr3.html +++ b/pr/451/docs/noq_proto/congestion/struct.Bbr3.html @@ -1,4 +1,4 @@ -Bbr3 in noq_proto::congestion - Rust

Bbr3

Struct Bbr3 

Source
pub struct Bbr3 { /* private fields */ }
Expand description

Experimental! Use at your own risk.

+Bbr3 in noq_proto::congestion - Rust

Bbr3

Struct Bbr3 

Source
pub struct Bbr3 { /* private fields */ }
Expand description

Experimental! Use at your own risk.

Aims for reduced buffer bloat and improved performance over high bandwidth-delay product networks. Based on https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-05.html equivalent to a combination of BBR and C states diff --git a/pr/451/docs/noq_proto/congestion/struct.Bbr3Config.html b/pr/451/docs/noq_proto/congestion/struct.Bbr3Config.html index 6ee9ef72b..53d72ef98 100644 --- a/pr/451/docs/noq_proto/congestion/struct.Bbr3Config.html +++ b/pr/451/docs/noq_proto/congestion/struct.Bbr3Config.html @@ -1,4 +1,4 @@ -Bbr3Config in noq_proto::congestion - Rust

Bbr3Config

Struct Bbr3Config 

Source
pub struct Bbr3Config { /* private fields */ }
Expand description

Configuration for the Bbr3 congestion controller

+Bbr3Config in noq_proto::congestion - Rust

Bbr3Config

Struct Bbr3Config 

Source
pub struct Bbr3Config { /* private fields */ }
Expand description

Configuration for the Bbr3 congestion controller

Different pacing_gains can be set to modify the multiplier used to increase the sending rates. Different cwnd_gains can be set to modify the multiplier used to increase diff --git a/pr/451/docs/noq_proto/congestion/struct.ControllerMetrics.html b/pr/451/docs/noq_proto/congestion/struct.ControllerMetrics.html index 83b9686be..320760ee1 100644 --- a/pr/451/docs/noq_proto/congestion/struct.ControllerMetrics.html +++ b/pr/451/docs/noq_proto/congestion/struct.ControllerMetrics.html @@ -1,4 +1,4 @@ -ControllerMetrics in noq_proto::congestion - Rust

ControllerMetrics

Struct ControllerMetrics 

Source
#[non_exhaustive]
pub struct ControllerMetrics { +ControllerMetrics in noq_proto::congestion - Rust

ControllerMetrics

Struct ControllerMetrics 

Source
#[non_exhaustive]
pub struct ControllerMetrics { pub congestion_window: u64, pub ssthresh: Option<u64>, pub pacing_rate: Option<u64>, diff --git a/pr/451/docs/noq_proto/congestion/struct.Cubic.html b/pr/451/docs/noq_proto/congestion/struct.Cubic.html index 2011b0107..5ef9f2957 100644 --- a/pr/451/docs/noq_proto/congestion/struct.Cubic.html +++ b/pr/451/docs/noq_proto/congestion/struct.Cubic.html @@ -1,4 +1,4 @@ -Cubic in noq_proto::congestion - Rust

Cubic

Struct Cubic 

Source
pub struct Cubic { /* private fields */ }
Expand description

The RFC8312 congestion controller, as widely used for TCP

+Cubic in noq_proto::congestion - Rust

Cubic

Struct Cubic 

Source
pub struct Cubic { /* private fields */ }
Expand description

The RFC8312 congestion controller, as widely used for TCP

Implementations§

Source§

impl Cubic

Source

pub fn new(config: Arc<CubicConfig>, _now: Instant, current_mtu: u16) -> Self

Construct a state using the given config and current time now

Trait Implementations§

Source§

impl Clone for Cubic

Source§

fn clone(&self) -> Cubic

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Controller for Cubic

Source§

fn on_ack( &mut self, diff --git a/pr/451/docs/noq_proto/congestion/struct.CubicConfig.html b/pr/451/docs/noq_proto/congestion/struct.CubicConfig.html index fd0aaf23c..bc951a1ad 100644 --- a/pr/451/docs/noq_proto/congestion/struct.CubicConfig.html +++ b/pr/451/docs/noq_proto/congestion/struct.CubicConfig.html @@ -1,4 +1,4 @@ -CubicConfig in noq_proto::congestion - Rust

CubicConfig

Struct CubicConfig 

Source
pub struct CubicConfig { /* private fields */ }
Expand description

Configuration for the Cubic congestion controller

+CubicConfig in noq_proto::congestion - Rust

CubicConfig

Struct CubicConfig 

Source
pub struct CubicConfig { /* private fields */ }
Expand description

Configuration for the Cubic congestion controller

Implementations§

Source§

impl CubicConfig

Source

pub fn initial_window(&mut self, value: u64) -> &mut Self

Default limit on the amount of outstanding data in bytes.

Recommended value: min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))

Trait Implementations§

Source§

impl Clone for CubicConfig

Source§

fn clone(&self) -> CubicConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl ControllerFactory for CubicConfig

Source§

fn build(self: Arc<Self>, now: Instant, current_mtu: u16) -> Box<dyn Controller>

Construct a fresh Controller
Source§

impl Debug for CubicConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for CubicConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where diff --git a/pr/451/docs/noq_proto/congestion/struct.NewReno.html b/pr/451/docs/noq_proto/congestion/struct.NewReno.html index 87e0e4fc8..05c96e04d 100644 --- a/pr/451/docs/noq_proto/congestion/struct.NewReno.html +++ b/pr/451/docs/noq_proto/congestion/struct.NewReno.html @@ -1,4 +1,4 @@ -NewReno in noq_proto::congestion - Rust

NewReno

Struct NewReno 

Source
pub struct NewReno { /* private fields */ }
Expand description

A simple, standard congestion controller

+NewReno in noq_proto::congestion - Rust

NewReno

Struct NewReno 

Source
pub struct NewReno { /* private fields */ }
Expand description

A simple, standard congestion controller

Implementations§

Source§

impl NewReno

Source

pub fn new(config: Arc<NewRenoConfig>, now: Instant, current_mtu: u16) -> Self

Construct a state using the given config and current time now

Trait Implementations§

Source§

impl Clone for NewReno

Source§

fn clone(&self) -> NewReno

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Controller for NewReno

Source§

fn on_ack( &mut self, diff --git a/pr/451/docs/noq_proto/congestion/struct.NewRenoConfig.html b/pr/451/docs/noq_proto/congestion/struct.NewRenoConfig.html index d840b20d5..4e97d7bf4 100644 --- a/pr/451/docs/noq_proto/congestion/struct.NewRenoConfig.html +++ b/pr/451/docs/noq_proto/congestion/struct.NewRenoConfig.html @@ -1,4 +1,4 @@ -NewRenoConfig in noq_proto::congestion - Rust

NewRenoConfig

Struct NewRenoConfig 

Source
pub struct NewRenoConfig { /* private fields */ }
Expand description

Configuration for the NewReno congestion controller

+NewRenoConfig in noq_proto::congestion - Rust

NewRenoConfig

Struct NewRenoConfig 

Source
pub struct NewRenoConfig { /* private fields */ }
Expand description

Configuration for the NewReno congestion controller

Implementations§

Source§

impl NewRenoConfig

Source

pub fn initial_window(&mut self, value: u64) -> &mut Self

Default limit on the amount of outstanding data in bytes.

Recommended value: min(10 * max_datagram_size, max(2 * max_datagram_size, 14720))

Source

pub fn loss_reduction_factor(&mut self, value: f32) -> &mut Self

Reduction in congestion window when a new loss event is detected.

diff --git a/pr/451/docs/noq_proto/congestion/trait.Controller.html b/pr/451/docs/noq_proto/congestion/trait.Controller.html index 7a8d1b47a..98717e8f2 100644 --- a/pr/451/docs/noq_proto/congestion/trait.Controller.html +++ b/pr/451/docs/noq_proto/congestion/trait.Controller.html @@ -1,4 +1,4 @@ -Controller in noq_proto::congestion - Rust

Controller

Trait Controller 

Source
pub trait Controller:
+Controller in noq_proto::congestion - Rust

Controller

Trait Controller 

Source
pub trait Controller:
     Send
     + Sync
     + Debug {
diff --git a/pr/451/docs/noq_proto/congestion/trait.ControllerFactory.html b/pr/451/docs/noq_proto/congestion/trait.ControllerFactory.html
index f4defe94a..197911365 100644
--- a/pr/451/docs/noq_proto/congestion/trait.ControllerFactory.html
+++ b/pr/451/docs/noq_proto/congestion/trait.ControllerFactory.html
@@ -1,4 +1,4 @@
-ControllerFactory in noq_proto::congestion - Rust

ControllerFactory

Trait ControllerFactory 

Source
pub trait ControllerFactory {
+ControllerFactory in noq_proto::congestion - Rust

ControllerFactory

Trait ControllerFactory 

Source
pub trait ControllerFactory {
     // Required method
     fn build(
         self: Arc<Self>,
diff --git a/pr/451/docs/noq_proto/constant.DEFAULT_SUPPORTED_VERSIONS.html b/pr/451/docs/noq_proto/constant.DEFAULT_SUPPORTED_VERSIONS.html
index 056e4ed87..5035942c3 100644
--- a/pr/451/docs/noq_proto/constant.DEFAULT_SUPPORTED_VERSIONS.html
+++ b/pr/451/docs/noq_proto/constant.DEFAULT_SUPPORTED_VERSIONS.html
@@ -1,2 +1,2 @@
-DEFAULT_SUPPORTED_VERSIONS in noq_proto - Rust

DEFAULT_SUPPORTED_VERSIONS

Constant DEFAULT_SUPPORTED_VERSIONS 

Source
pub const DEFAULT_SUPPORTED_VERSIONS: &[u32];
Expand description

The QUIC protocol version implemented.

+DEFAULT_SUPPORTED_VERSIONS in noq_proto - Rust

DEFAULT_SUPPORTED_VERSIONS

Constant DEFAULT_SUPPORTED_VERSIONS 

Source
pub const DEFAULT_SUPPORTED_VERSIONS: &[u32];
Expand description

The QUIC protocol version implemented.

\ No newline at end of file diff --git a/pr/451/docs/noq_proto/crypto/index.html b/pr/451/docs/noq_proto/crypto/index.html index 72a4880e1..0344875d3 100644 --- a/pr/451/docs/noq_proto/crypto/index.html +++ b/pr/451/docs/noq_proto/crypto/index.html @@ -1,4 +1,4 @@ -noq_proto::crypto - Rust

Module crypto

Module crypto 

Source
Expand description

Traits and implementations for the QUIC cryptography protocol

+noq_proto::crypto - Rust

Module crypto

Module crypto 

Source
Expand description

Traits and implementations for the QUIC cryptography protocol

The protocol logic in noq is contained in types that abstract over the actual cryptographic protocol used. This module contains the traits used for this abstraction layer as well as a single implementation of these traits that uses diff --git a/pr/451/docs/noq_proto/crypto/rustls/enum.Error.html b/pr/451/docs/noq_proto/crypto/rustls/enum.Error.html index 04bd994c9..eb4ad3fab 100644 --- a/pr/451/docs/noq_proto/crypto/rustls/enum.Error.html +++ b/pr/451/docs/noq_proto/crypto/rustls/enum.Error.html @@ -1,4 +1,4 @@ -Error in noq_proto::crypto::rustls - Rust

Error

Enum Error 

#[non_exhaustive]
pub enum Error { +Error in noq_proto::crypto::rustls - Rust

Error

Enum Error 

#[non_exhaustive]
pub enum Error {
Show 22 variants InappropriateMessage { expect_types: Vec<ContentType>, got_type: ContentType, diff --git a/pr/451/docs/noq_proto/crypto/rustls/index.html b/pr/451/docs/noq_proto/crypto/rustls/index.html index c684d0969..558675ca0 100644 --- a/pr/451/docs/noq_proto/crypto/rustls/index.html +++ b/pr/451/docs/noq_proto/crypto/rustls/index.html @@ -1,2 +1,2 @@ -noq_proto::crypto::rustls - Rust

Module rustls

Module rustls 

Source
Expand description

TLS interface based on rustls

+noq_proto::crypto::rustls - Rust

Module rustls

Module rustls 

Source
Expand description

TLS interface based on rustls

Structs§

HandshakeData
Authentication data for (rustls) TLS session
NoInitialCipherSuite
The initial cipher suite (AES-128-GCM-SHA256) is not available
QuicClientConfig
A QUIC-compatible TLS client configuration
QuicServerConfig
A QUIC-compatible TLS server configuration
TlsSession
A rustls TLS session

Enums§

Error
rustls reports protocol errors using this type.
\ No newline at end of file diff --git a/pr/451/docs/noq_proto/crypto/rustls/struct.HandshakeData.html b/pr/451/docs/noq_proto/crypto/rustls/struct.HandshakeData.html index 1b2e811e7..e84629f04 100644 --- a/pr/451/docs/noq_proto/crypto/rustls/struct.HandshakeData.html +++ b/pr/451/docs/noq_proto/crypto/rustls/struct.HandshakeData.html @@ -1,4 +1,4 @@ -HandshakeData in noq_proto::crypto::rustls - Rust

HandshakeData

Struct HandshakeData 

Source
#[non_exhaustive]
pub struct HandshakeData { +HandshakeData in noq_proto::crypto::rustls - Rust

HandshakeData

Struct HandshakeData 

Source
#[non_exhaustive]
pub struct HandshakeData { pub protocol: Option<Vec<u8>>, pub server_name: Option<String>, pub negotiated_key_exchange_group: Option<NamedGroup>, diff --git a/pr/451/docs/noq_proto/crypto/rustls/struct.NoInitialCipherSuite.html b/pr/451/docs/noq_proto/crypto/rustls/struct.NoInitialCipherSuite.html index 9d63550e6..1355b1454 100644 --- a/pr/451/docs/noq_proto/crypto/rustls/struct.NoInitialCipherSuite.html +++ b/pr/451/docs/noq_proto/crypto/rustls/struct.NoInitialCipherSuite.html @@ -1,4 +1,4 @@ -NoInitialCipherSuite in noq_proto::crypto::rustls - Rust

NoInitialCipherSuite

Struct NoInitialCipherSuite 

Source
pub struct NoInitialCipherSuite { /* private fields */ }
Expand description

The initial cipher suite (AES-128-GCM-SHA256) is not available

+NoInitialCipherSuite in noq_proto::crypto::rustls - Rust

NoInitialCipherSuite

Struct NoInitialCipherSuite 

Source
pub struct NoInitialCipherSuite { /* private fields */ }
Expand description

The initial cipher suite (AES-128-GCM-SHA256) is not available

When the cipher suite is supplied with_initial(), it must be [CipherSuite::TLS13_AES_128_GCM_SHA256]. When the cipher suite is derived from a config’s CryptoProvider, that provider must reference a cipher suite with the same ID.

diff --git a/pr/451/docs/noq_proto/crypto/rustls/struct.QuicClientConfig.html b/pr/451/docs/noq_proto/crypto/rustls/struct.QuicClientConfig.html index f1e661327..9b2cf0296 100644 --- a/pr/451/docs/noq_proto/crypto/rustls/struct.QuicClientConfig.html +++ b/pr/451/docs/noq_proto/crypto/rustls/struct.QuicClientConfig.html @@ -1,4 +1,4 @@ -QuicClientConfig in noq_proto::crypto::rustls - Rust

QuicClientConfig

Struct QuicClientConfig 

Source
pub struct QuicClientConfig { /* private fields */ }
Expand description

A QUIC-compatible TLS client configuration

+QuicClientConfig in noq_proto::crypto::rustls - Rust

QuicClientConfig

Struct QuicClientConfig 

Source
pub struct QuicClientConfig { /* private fields */ }
Expand description

A QUIC-compatible TLS client configuration

noq implicitly constructs a QuicClientConfig with reasonable defaults within ClientConfig::with_root_certificates() and ClientConfig::try_with_platform_verifier(). Alternatively, QuicClientConfig’s TryFrom implementation can be used to wrap around a diff --git a/pr/451/docs/noq_proto/crypto/rustls/struct.QuicServerConfig.html b/pr/451/docs/noq_proto/crypto/rustls/struct.QuicServerConfig.html index 36765b5e8..7f0e2e4eb 100644 --- a/pr/451/docs/noq_proto/crypto/rustls/struct.QuicServerConfig.html +++ b/pr/451/docs/noq_proto/crypto/rustls/struct.QuicServerConfig.html @@ -1,4 +1,4 @@ -QuicServerConfig in noq_proto::crypto::rustls - Rust

QuicServerConfig

Struct QuicServerConfig 

Source
pub struct QuicServerConfig { /* private fields */ }
Expand description

A QUIC-compatible TLS server configuration

+QuicServerConfig in noq_proto::crypto::rustls - Rust

QuicServerConfig

Struct QuicServerConfig 

Source
pub struct QuicServerConfig { /* private fields */ }
Expand description

A QUIC-compatible TLS server configuration

noq implicitly constructs a QuicServerConfig with reasonable defaults within ServerConfig::with_single_cert(). Alternatively, QuicServerConfig’s TryFrom implementation or with_initial method can be used to wrap around a custom diff --git a/pr/451/docs/noq_proto/crypto/rustls/struct.TlsSession.html b/pr/451/docs/noq_proto/crypto/rustls/struct.TlsSession.html index 60bb11931..1da208b03 100644 --- a/pr/451/docs/noq_proto/crypto/rustls/struct.TlsSession.html +++ b/pr/451/docs/noq_proto/crypto/rustls/struct.TlsSession.html @@ -1,4 +1,4 @@ -TlsSession in noq_proto::crypto::rustls - Rust

TlsSession

Struct TlsSession 

Source
pub struct TlsSession { /* private fields */ }
Expand description

A rustls TLS session

+TlsSession in noq_proto::crypto::rustls - Rust

TlsSession

Struct TlsSession 

Source
pub struct TlsSession { /* private fields */ }
Expand description

A rustls TLS session

Trait Implementations§

Source§

impl Session for TlsSession

Source§

fn peer_identity(&self) -> Option<Box<dyn Any>>

For the rustls TlsSession, the Any type is Vec<rustls::pki_types::CertificateDer>

Source§

fn initial_keys(&self, dst_cid: ConnectionId, side: Side) -> Keys

Create the initial set of keys given the client’s initial destination ConnectionId
Source§

fn handshake_data(&self) -> Option<Box<dyn Any>>

Get data negotiated during the handshake, if available Read more
Source§

fn early_crypto(&self) -> Option<(Box<dyn HeaderKey>, Box<dyn PacketKey>)>

Get the 0-RTT keys if available (clients only) Read more
Source§

fn early_data_accepted(&self) -> Option<bool>

If the 0-RTT-encrypted data has been accepted by the peer
Source§

fn is_handshaking(&self) -> bool

Returns true until the connection is fully established.
Source§

fn read_handshake(&mut self, buf: &[u8]) -> Result<bool, TransportError>

Read bytes of handshake data Read more
Source§

fn transport_parameters( &self, diff --git a/pr/451/docs/noq_proto/crypto/struct.CryptoError.html b/pr/451/docs/noq_proto/crypto/struct.CryptoError.html index 210ea40f9..e4f12cf49 100644 --- a/pr/451/docs/noq_proto/crypto/struct.CryptoError.html +++ b/pr/451/docs/noq_proto/crypto/struct.CryptoError.html @@ -1,4 +1,4 @@ -CryptoError in noq_proto::crypto - Rust

CryptoError

Struct CryptoError 

Source
pub struct CryptoError;
Expand description

Generic crypto errors

+CryptoError in noq_proto::crypto - Rust

CryptoError

Struct CryptoError 

Source
pub struct CryptoError;
Expand description

Generic crypto errors

Trait Implementations§

Source§

impl Debug for CryptoError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq_proto/crypto/struct.ExportKeyingMaterialError.html b/pr/451/docs/noq_proto/crypto/struct.ExportKeyingMaterialError.html index d54cca3e8..eccdae3f2 100644 --- a/pr/451/docs/noq_proto/crypto/struct.ExportKeyingMaterialError.html +++ b/pr/451/docs/noq_proto/crypto/struct.ExportKeyingMaterialError.html @@ -1,4 +1,4 @@ -ExportKeyingMaterialError in noq_proto::crypto - Rust

ExportKeyingMaterialError

Struct ExportKeyingMaterialError 

Source
pub struct ExportKeyingMaterialError;
Expand description

Error returned by Session::export_keying_material.

+ExportKeyingMaterialError in noq_proto::crypto - Rust

ExportKeyingMaterialError

Struct ExportKeyingMaterialError 

Source
pub struct ExportKeyingMaterialError;
Expand description

Error returned by Session::export_keying_material.

This error occurs if the requested output length is too large.

Trait Implementations§

Source§

impl Debug for ExportKeyingMaterialError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for ExportKeyingMaterialError

Source§

fn eq(&self, other: &ExportKeyingMaterialError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Eq for ExportKeyingMaterialError

Source§

impl StructuralPartialEq for ExportKeyingMaterialError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where diff --git a/pr/451/docs/noq_proto/crypto/struct.KeyPair.html b/pr/451/docs/noq_proto/crypto/struct.KeyPair.html index 8c22779ba..f972fb92d 100644 --- a/pr/451/docs/noq_proto/crypto/struct.KeyPair.html +++ b/pr/451/docs/noq_proto/crypto/struct.KeyPair.html @@ -1,4 +1,4 @@ -KeyPair in noq_proto::crypto - Rust

KeyPair

Struct KeyPair 

Source
pub struct KeyPair<T> {
+KeyPair in noq_proto::crypto - Rust

KeyPair

Struct KeyPair 

Source
pub struct KeyPair<T> {
     pub local: T,
     pub remote: T,
 }
Expand description

A pair of keys for bidirectional communication

diff --git a/pr/451/docs/noq_proto/crypto/struct.Keys.html b/pr/451/docs/noq_proto/crypto/struct.Keys.html index 137d8a10f..d869ac0e5 100644 --- a/pr/451/docs/noq_proto/crypto/struct.Keys.html +++ b/pr/451/docs/noq_proto/crypto/struct.Keys.html @@ -1,4 +1,4 @@ -Keys in noq_proto::crypto - Rust

Keys

Struct Keys 

Source
pub struct Keys {
+Keys in noq_proto::crypto - Rust

Keys

Struct Keys 

Source
pub struct Keys {
     pub header: KeyPair<Box<dyn HeaderKey>>,
     pub packet: KeyPair<Box<dyn PacketKey>>,
 }
Expand description

A complete set of keys for a certain packet space

diff --git a/pr/451/docs/noq_proto/crypto/struct.UnsupportedVersion.html b/pr/451/docs/noq_proto/crypto/struct.UnsupportedVersion.html index 5e1f49729..665dd235f 100644 --- a/pr/451/docs/noq_proto/crypto/struct.UnsupportedVersion.html +++ b/pr/451/docs/noq_proto/crypto/struct.UnsupportedVersion.html @@ -1,4 +1,4 @@ -UnsupportedVersion in noq_proto::crypto - Rust

UnsupportedVersion

Struct UnsupportedVersion 

Source
pub struct UnsupportedVersion;
Expand description

Error indicating that the specified QUIC version is not supported

+UnsupportedVersion in noq_proto::crypto - Rust

UnsupportedVersion

Struct UnsupportedVersion 

Source
pub struct UnsupportedVersion;
Expand description

Error indicating that the specified QUIC version is not supported

Trait Implementations§

Source§

impl Debug for UnsupportedVersion

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<UnsupportedVersion> for ConnectError

Source§

fn from(_: UnsupportedVersion) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq_proto/crypto/trait.ClientConfig.html b/pr/451/docs/noq_proto/crypto/trait.ClientConfig.html index 2ee668b52..21ce1a285 100644 --- a/pr/451/docs/noq_proto/crypto/trait.ClientConfig.html +++ b/pr/451/docs/noq_proto/crypto/trait.ClientConfig.html @@ -1,4 +1,4 @@ -ClientConfig in noq_proto::crypto - Rust

ClientConfig

Trait ClientConfig 

Source
pub trait ClientConfig: Send + Sync {
+ClientConfig in noq_proto::crypto - Rust

ClientConfig

Trait ClientConfig 

Source
pub trait ClientConfig: Send + Sync {
     // Required method
     fn start_session(
         &self,
diff --git a/pr/451/docs/noq_proto/crypto/trait.HandshakeTokenKey.html b/pr/451/docs/noq_proto/crypto/trait.HandshakeTokenKey.html
index 0aebdede2..fa134a669 100644
--- a/pr/451/docs/noq_proto/crypto/trait.HandshakeTokenKey.html
+++ b/pr/451/docs/noq_proto/crypto/trait.HandshakeTokenKey.html
@@ -1,4 +1,4 @@
-HandshakeTokenKey in noq_proto::crypto - Rust

HandshakeTokenKey

Trait HandshakeTokenKey 

Source
pub trait HandshakeTokenKey: Send + Sync {
+HandshakeTokenKey in noq_proto::crypto - Rust

HandshakeTokenKey

Trait HandshakeTokenKey 

Source
pub trait HandshakeTokenKey: Send + Sync {
     // Required methods
     fn seal(
         &self,
diff --git a/pr/451/docs/noq_proto/crypto/trait.HeaderKey.html b/pr/451/docs/noq_proto/crypto/trait.HeaderKey.html
index 0e3bdd651..ed5f13d17 100644
--- a/pr/451/docs/noq_proto/crypto/trait.HeaderKey.html
+++ b/pr/451/docs/noq_proto/crypto/trait.HeaderKey.html
@@ -1,4 +1,4 @@
-HeaderKey in noq_proto::crypto - Rust

HeaderKey

Trait HeaderKey 

Source
pub trait HeaderKey: Send + Sync {
+HeaderKey in noq_proto::crypto - Rust

HeaderKey

Trait HeaderKey 

Source
pub trait HeaderKey: Send + Sync {
     // Required methods
     fn decrypt(&self, pn_offset: usize, packet: &mut [u8]);
     fn encrypt(&self, pn_offset: usize, packet: &mut [u8]);
diff --git a/pr/451/docs/noq_proto/crypto/trait.HmacKey.html b/pr/451/docs/noq_proto/crypto/trait.HmacKey.html
index 5fae9f16e..ac506c1b2 100644
--- a/pr/451/docs/noq_proto/crypto/trait.HmacKey.html
+++ b/pr/451/docs/noq_proto/crypto/trait.HmacKey.html
@@ -1,4 +1,4 @@
-HmacKey in noq_proto::crypto - Rust

HmacKey

Trait HmacKey 

Source
pub trait HmacKey: Send + Sync {
+HmacKey in noq_proto::crypto - Rust

HmacKey

Trait HmacKey 

Source
pub trait HmacKey: Send + Sync {
     // Required methods
     fn sign(&self, data: &[u8], signature_out: &mut [u8]);
     fn signature_len(&self) -> usize;
diff --git a/pr/451/docs/noq_proto/crypto/trait.PacketKey.html b/pr/451/docs/noq_proto/crypto/trait.PacketKey.html
index 329f611a5..a22ec3b63 100644
--- a/pr/451/docs/noq_proto/crypto/trait.PacketKey.html
+++ b/pr/451/docs/noq_proto/crypto/trait.PacketKey.html
@@ -1,4 +1,4 @@
-PacketKey in noq_proto::crypto - Rust

PacketKey

Trait PacketKey 

Source
pub trait PacketKey: Send + Sync {
+PacketKey in noq_proto::crypto - Rust

PacketKey

Trait PacketKey 

Source
pub trait PacketKey: Send + Sync {
     // Required methods
     fn encrypt(
         &self,
diff --git a/pr/451/docs/noq_proto/crypto/trait.ServerConfig.html b/pr/451/docs/noq_proto/crypto/trait.ServerConfig.html
index 1d910fe9d..8f75f2b3b 100644
--- a/pr/451/docs/noq_proto/crypto/trait.ServerConfig.html
+++ b/pr/451/docs/noq_proto/crypto/trait.ServerConfig.html
@@ -1,4 +1,4 @@
-ServerConfig in noq_proto::crypto - Rust

ServerConfig

Trait ServerConfig 

Source
pub trait ServerConfig: Send + Sync {
+ServerConfig in noq_proto::crypto - Rust

ServerConfig

Trait ServerConfig 

Source
pub trait ServerConfig: Send + Sync {
     // Required methods
     fn initial_keys(
         &self,
diff --git a/pr/451/docs/noq_proto/crypto/trait.Session.html b/pr/451/docs/noq_proto/crypto/trait.Session.html
index cbef4f7e0..40f6b65f9 100644
--- a/pr/451/docs/noq_proto/crypto/trait.Session.html
+++ b/pr/451/docs/noq_proto/crypto/trait.Session.html
@@ -1,4 +1,4 @@
-Session in noq_proto::crypto - Rust

Session

Trait Session 

Source
pub trait Session:
+Session in noq_proto::crypto - Rust

Session

Trait Session 

Source
pub trait Session:
     Send
     + Sync
     + 'static {
diff --git a/pr/451/docs/noq_proto/enum.ClosePathError.html b/pr/451/docs/noq_proto/enum.ClosePathError.html
index 2e7167172..45e893609 100644
--- a/pr/451/docs/noq_proto/enum.ClosePathError.html
+++ b/pr/451/docs/noq_proto/enum.ClosePathError.html
@@ -1,4 +1,4 @@
-ClosePathError in noq_proto - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
+ClosePathError in noq_proto - Rust

ClosePathError

Enum ClosePathError 

Source
pub enum ClosePathError {
     MultipathNotNegotiated,
     ClosedPath,
     LastOpenPath,
@@ -7,8 +7,8 @@
 
§

ClosedPath

The path is already closed or was never opened

§

LastOpenPath

Cannot close the last remaining open path via the local API.

Use Connection::close to end the connection instead.

-

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ClosePathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosePathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosePathError

Source§

fn eq(&self, other: &ClosePathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ClosePathError

Source§

fn clone(&self) -> ClosePathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosePathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ClosePathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosePathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosePathError

Source§

fn eq(&self, other: &ClosePathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ClosePathError

Source§

impl StructuralPartialEq for ClosePathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.ConfigError.html b/pr/451/docs/noq_proto/enum.ConfigError.html index 3f8b299fe..0324ceded 100644 --- a/pr/451/docs/noq_proto/enum.ConfigError.html +++ b/pr/451/docs/noq_proto/enum.ConfigError.html @@ -1,4 +1,4 @@ -ConfigError in noq_proto - Rust

ConfigError

Enum ConfigError 

Source
#[non_exhaustive]
pub enum ConfigError { +ConfigError in noq_proto - Rust

ConfigError

Enum ConfigError 

Source
#[non_exhaustive]
pub enum ConfigError { OutOfBounds, }
Expand description

Errors in the configuration of an endpoint

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

OutOfBounds

Value exceeds supported bounds

diff --git a/pr/451/docs/noq_proto/enum.ConnectError.html b/pr/451/docs/noq_proto/enum.ConnectError.html index 29c5d6a71..f070a802e 100644 --- a/pr/451/docs/noq_proto/enum.ConnectError.html +++ b/pr/451/docs/noq_proto/enum.ConnectError.html @@ -1,4 +1,4 @@ -ConnectError in noq_proto - Rust

ConnectError

Enum ConnectError 

Source
pub enum ConnectError {
+ConnectError in noq_proto - Rust

ConnectError

Enum ConnectError 

Source
pub enum ConnectError {
     EndpointStopping,
     CidsExhausted,
     InvalidServerName(String),
@@ -17,8 +17,8 @@
 
§

NoDefaultClientConfig

No default client configuration was set up

Use Endpoint::connect_with to specify a client configuration.

§

UnsupportedVersion

The local endpoint does not support the QUIC version specified in the client configuration

-

Trait Implementations§

Source§

impl Clone for ConnectError

Source§

fn clone(&self) -> ConnectError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<UnsupportedVersion> for ConnectError

Source§

fn from(_: UnsupportedVersion) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectError

Source§

fn eq(&self, other: &ConnectError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ConnectError

Source§

impl StructuralPartialEq for ConnectError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectError

Source§

fn clone(&self) -> ConnectError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<UnsupportedVersion> for ConnectError

Source§

fn from(_: UnsupportedVersion) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectError

Source§

fn eq(&self, other: &ConnectError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ConnectError

Source§

impl StructuralPartialEq for ConnectError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.ConnectionError.html b/pr/451/docs/noq_proto/enum.ConnectionError.html index c1c3a8edc..64523e84b 100644 --- a/pr/451/docs/noq_proto/enum.ConnectionError.html +++ b/pr/451/docs/noq_proto/enum.ConnectionError.html @@ -1,4 +1,4 @@ -ConnectionError in noq_proto - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
+ConnectionError in noq_proto - Rust

ConnectionError

Enum ConnectionError 

Source
pub enum ConnectionError {
     VersionMismatch,
     TransportError(TransportError),
     ConnectionClosed(ConnectionClose),
@@ -20,8 +20,8 @@ and §

LocallyClosed

The local application closed the connection

§

CidsExhausted

The connection could not be created because not enough of the CID space is available

Try using longer connection IDs.

-

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectionError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectionError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ConnectionError> for Error

Source§

fn from(x: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

fn eq(&self, other: &ConnectionError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for ConnectionError

Source§

fn clone(&self) -> ConnectionError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ConnectionError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ConnectionError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ConnectionError> for Error

Source§

fn from(x: ConnectionError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ConnectionError

Source§

fn eq(&self, other: &ConnectionError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ConnectionError

Source§

impl StructuralPartialEq for ConnectionError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.DatagramEvent.html b/pr/451/docs/noq_proto/enum.DatagramEvent.html index e741fb6f0..5ee311fa4 100644 --- a/pr/451/docs/noq_proto/enum.DatagramEvent.html +++ b/pr/451/docs/noq_proto/enum.DatagramEvent.html @@ -1,4 +1,4 @@ -DatagramEvent in noq_proto - Rust

DatagramEvent

Enum DatagramEvent 

Source
pub enum DatagramEvent {
+DatagramEvent in noq_proto - Rust

DatagramEvent

Enum DatagramEvent 

Source
pub enum DatagramEvent {
     ConnectionEvent(ConnectionHandle, ConnectionEvent),
     NewConnection(Incoming),
     Response(Transmit),
diff --git a/pr/451/docs/noq_proto/enum.Dir.html b/pr/451/docs/noq_proto/enum.Dir.html
index 6e6be725a..995b6d2f7 100644
--- a/pr/451/docs/noq_proto/enum.Dir.html
+++ b/pr/451/docs/noq_proto/enum.Dir.html
@@ -1,24 +1,24 @@
-Dir in noq_proto - Rust

Dir

Enum Dir 

Source
pub enum Dir {
+Dir in noq_proto - Rust

Dir

Enum Dir 

Source
pub enum Dir {
     Bi = 0,
     Uni = 1,
 }
Expand description

Whether a stream communicates data in both directions or only from the initiator

Variants§

§

Bi = 0

Data flows in both directions

§

Uni = 1

Data flows only from the stream’s initiator

-

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for Dir

Source§

fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self>

Generate an arbitrary value of Self from the given unstructured data. Read more
Source§

fn arbitrary_take_rest(u: Unstructured<'arbitrary>) -> Result<Self>

Generate an arbitrary value of Self from the entirety of the given -unstructured data. Read more
Source§

fn size_hint(depth: usize) -> (usize, Option<usize>)

Get a size hint for how many bytes out of an Unstructured this type +

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for Dir

Source§

fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self>

Generate an arbitrary value of Self from the given unstructured data. Read more
Source§

fn arbitrary_take_rest(u: Unstructured<'arbitrary>) -> Result<Self>

Generate an arbitrary value of Self from the entirety of the given +unstructured data. Read more
Source§

fn size_hint(depth: usize) -> (usize, Option<usize>)

Get a size hint for how many bytes out of an Unstructured this type needs to construct itself. Read more
§

fn try_size_hint( depth: usize, ) -> Result<(usize, Option<usize>), MaxRecursionReached>

Get a size hint for how many bytes out of an Unstructured this type -needs to construct itself. Read more
Source§

impl Clone for Dir

Source§

fn clone(&self) -> Dir

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Dir

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Dir

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<Dir> for StreamType

Available on crate feature qlog only.
Source§

fn from(value: Dir) -> Self

Converts to this type from the input type.
Source§

impl Hash for Dir

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where +needs to construct itself. Read more

Source§

impl Clone for Dir

Source§

fn clone(&self) -> Dir

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Dir

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Dir

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<Dir> for StreamType

Available on crate feature qlog only.
Source§

fn from(value: Dir) -> Self

Converts to this type from the input type.
Source§

impl Hash for Dir

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for Dir

Source§

fn cmp(&self, other: &Dir) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for Dir

Source§

fn cmp(&self, other: &Dir) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for Dir

Source§

fn eq(&self, other: &Dir) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for Dir

Source§

fn partial_cmp(&self, other: &Dir) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more

Source§

impl PartialEq for Dir

Source§

fn eq(&self, other: &Dir) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for Dir

Source§

fn partial_cmp(&self, other: &Dir) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl Copy for Dir

Source§

impl Eq for Dir

Source§

impl StructuralPartialEq for Dir

Auto Trait Implementations§

§

impl Freeze for Dir

§

impl RefUnwindSafe for Dir

§

impl Send for Dir

§

impl Sync for Dir

§

impl Unpin for Dir

§

impl UnwindSafe for Dir

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl Copy for Dir

Source§

impl Eq for Dir

Source§

impl StructuralPartialEq for Dir

Auto Trait Implementations§

§

impl Freeze for Dir

§

impl RefUnwindSafe for Dir

§

impl Send for Dir

§

impl Sync for Dir

§

impl Unpin for Dir

§

impl UnwindSafe for Dir

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.EcnCodepoint.html b/pr/451/docs/noq_proto/enum.EcnCodepoint.html index 92b339977..1183928a6 100644 --- a/pr/451/docs/noq_proto/enum.EcnCodepoint.html +++ b/pr/451/docs/noq_proto/enum.EcnCodepoint.html @@ -1,4 +1,4 @@ -EcnCodepoint in noq_proto - Rust

EcnCodepoint

Enum EcnCodepoint 

Source
#[repr(u8)]
pub enum EcnCodepoint { +EcnCodepoint in noq_proto - Rust

EcnCodepoint

Enum EcnCodepoint 

Source
#[repr(u8)]
pub enum EcnCodepoint { Ect0 = 2, Ect1 = 1, Ce = 3, @@ -6,10 +6,10 @@

Variants§

§

Ect0 = 2

The ECT(0) codepoint, indicating that an endpoint is ECN-capable

§

Ect1 = 1

The ECT(1) codepoint, indicating that an endpoint is ECN-capable

§

Ce = 3

The CE codepoint, signalling that congestion was experienced

-

Implementations§

Source§

impl EcnCodepoint

Source

pub fn from_bits(x: u8) -> Option<Self>

Create new object from the given bits

-
Source

pub fn is_ce(self) -> bool

Returns whether the codepoint is a CE, signalling that congestion was experienced

-

Trait Implementations§

Source§

impl Clone for EcnCodepoint

Source§

fn clone(&self) -> EcnCodepoint

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EcnCodepoint

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for EcnCodepoint

Source§

fn eq(&self, other: &EcnCodepoint) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for EcnCodepoint

Source§

impl Eq for EcnCodepoint

Source§

impl StructuralPartialEq for EcnCodepoint

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Implementations§

Source§

impl EcnCodepoint

Source

pub fn from_bits(x: u8) -> Option<Self>

Create new object from the given bits

+
Source

pub fn is_ce(self) -> bool

Returns whether the codepoint is a CE, signalling that congestion was experienced

+

Trait Implementations§

Source§

impl Clone for EcnCodepoint

Source§

fn clone(&self) -> EcnCodepoint

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EcnCodepoint

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for EcnCodepoint

Source§

fn eq(&self, other: &EcnCodepoint) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for EcnCodepoint

Source§

impl Eq for EcnCodepoint

Source§

impl StructuralPartialEq for EcnCodepoint

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.Event.html b/pr/451/docs/noq_proto/enum.Event.html index d94bb13ab..c1dbb5f97 100644 --- a/pr/451/docs/noq_proto/enum.Event.html +++ b/pr/451/docs/noq_proto/enum.Event.html @@ -1,4 +1,4 @@ -Event in noq_proto - Rust

Event

Enum Event 

Source
pub enum Event {
+Event in noq_proto - Rust

Event

Enum Event 

Source
pub enum Event {
     HandshakeDataReady,
     Connected,
     HandshakeConfirmed,
@@ -25,7 +25,7 @@ fail with §

DatagramsUnblocked

One or more application datagrams have been sent after blocking

§

Path(PathEvent)

(Multi)Path events

§

NatTraversal(Event)

n0’s nat traversal events

-

Trait Implementations§

Source§

impl Debug for Event

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !Freeze for Event

§

impl !RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl !UnwindSafe for Event

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Event

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !Freeze for Event

§

impl !RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl !UnwindSafe for Event

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/451/docs/noq_proto/enum.FinishError.html b/pr/451/docs/noq_proto/enum.FinishError.html index 813eaad94..a28ae2d24 100644 --- a/pr/451/docs/noq_proto/enum.FinishError.html +++ b/pr/451/docs/noq_proto/enum.FinishError.html @@ -1,4 +1,4 @@ -FinishError in noq_proto - Rust

FinishError

Enum FinishError 

Source
pub enum FinishError {
+FinishError in noq_proto - Rust

FinishError

Enum FinishError 

Source
pub enum FinishError {
     Stopped(VarInt),
     ClosedStream,
 }
Expand description

Reasons why attempting to finish a stream might fail

diff --git a/pr/451/docs/noq_proto/enum.FrameType.html b/pr/451/docs/noq_proto/enum.FrameType.html index 047a105c4..93c1b9f4b 100644 --- a/pr/451/docs/noq_proto/enum.FrameType.html +++ b/pr/451/docs/noq_proto/enum.FrameType.html @@ -1,4 +1,4 @@ -FrameType in noq_proto - Rust

FrameType

Enum FrameType 

Source
pub enum FrameType {
+FrameType in noq_proto - Rust

FrameType

Enum FrameType 

Source
pub enum FrameType {
 
Show 44 variants Padding, Ping, Ack, diff --git a/pr/451/docs/noq_proto/enum.LongType.html b/pr/451/docs/noq_proto/enum.LongType.html index 77ee4f3ff..9c7068747 100644 --- a/pr/451/docs/noq_proto/enum.LongType.html +++ b/pr/451/docs/noq_proto/enum.LongType.html @@ -1,4 +1,4 @@ -LongType in noq_proto - Rust

LongType

Enum LongType 

Source
pub enum LongType {
+LongType in noq_proto - Rust

LongType

Enum LongType 

Source
pub enum LongType {
     Handshake,
     ZeroRtt,
 }
Expand description

Long packet types with uniform header structure

diff --git a/pr/451/docs/noq_proto/enum.MaybeFrame.html b/pr/451/docs/noq_proto/enum.MaybeFrame.html index 5951bf116..fcd61aa49 100644 --- a/pr/451/docs/noq_proto/enum.MaybeFrame.html +++ b/pr/451/docs/noq_proto/enum.MaybeFrame.html @@ -1,4 +1,4 @@ -MaybeFrame in noq_proto - Rust

MaybeFrame

Enum MaybeFrame 

Source
pub enum MaybeFrame {
+MaybeFrame in noq_proto - Rust

MaybeFrame

Enum MaybeFrame 

Source
pub enum MaybeFrame {
     None,
     Unknown(u64),
     Known(FrameType),
diff --git a/pr/451/docs/noq_proto/enum.PacketDecodeError.html b/pr/451/docs/noq_proto/enum.PacketDecodeError.html
index 35066327b..a9e4c3cd5 100644
--- a/pr/451/docs/noq_proto/enum.PacketDecodeError.html
+++ b/pr/451/docs/noq_proto/enum.PacketDecodeError.html
@@ -1,4 +1,4 @@
-PacketDecodeError in noq_proto - Rust

PacketDecodeError

Enum PacketDecodeError 

Source
pub enum PacketDecodeError {
+PacketDecodeError in noq_proto - Rust

PacketDecodeError

Enum PacketDecodeError 

Source
pub enum PacketDecodeError {
     UnsupportedVersion {
         src_cid: ConnectionId,
         dst_cid: ConnectionId,
diff --git a/pr/451/docs/noq_proto/enum.PathAbandonReason.html b/pr/451/docs/noq_proto/enum.PathAbandonReason.html
index af7ea07a1..d7d350927 100644
--- a/pr/451/docs/noq_proto/enum.PathAbandonReason.html
+++ b/pr/451/docs/noq_proto/enum.PathAbandonReason.html
@@ -1,11 +1,10 @@
-PathAbandonReason in noq_proto - Rust

PathAbandonReason

Enum PathAbandonReason 

Source
pub enum PathAbandonReason {
+PathAbandonReason in noq_proto - Rust

PathAbandonReason

Enum PathAbandonReason 

Source
pub enum PathAbandonReason {
     ApplicationClosed {
         error_code: VarInt,
     },
     ValidationFailed,
     TimedOut,
     UnusableAfterNetworkChange,
-    NatTraversalRoundEnded,
     RemoteAbandoned {
         error_code: VarInt,
     },
@@ -15,11 +14,10 @@
 
§

ValidationFailed

We didn’t receive a path response in time after opening this path.

§

TimedOut

We didn’t receive any data from the remote within the path’s idle timeout.

§

UnusableAfterNetworkChange

The path became unusable after a local network change.

-
§

NatTraversalRoundEnded

The path was opened in a NAT traversal round which was terminated.

§

RemoteAbandoned

The remote closed the path.

Fields

§error_code: VarInt

The error that was sent with the abandon frame.

-

Trait Implementations§

Source§

impl Clone for PathAbandonReason

Source§

fn clone(&self) -> PathAbandonReason

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathAbandonReason

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for PathAbandonReason

Source§

fn eq(&self, other: &PathAbandonReason) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for PathAbandonReason

Source§

impl StructuralPartialEq for PathAbandonReason

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathAbandonReason

Source§

fn clone(&self) -> PathAbandonReason

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathAbandonReason

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for PathAbandonReason

Source§

fn eq(&self, other: &PathAbandonReason) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for PathAbandonReason

Source§

impl StructuralPartialEq for PathAbandonReason

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.PathError.html b/pr/451/docs/noq_proto/enum.PathError.html index 759f245a2..f5a8031d6 100644 --- a/pr/451/docs/noq_proto/enum.PathError.html +++ b/pr/451/docs/noq_proto/enum.PathError.html @@ -1,4 +1,4 @@ -PathError in noq_proto - Rust

PathError

Enum PathError 

Source
pub enum PathError {
+PathError in noq_proto - Rust

PathError

Enum PathError 

Source
pub enum PathError {
     MultipathNotNegotiated,
     ServerSideNotAllowed,
     MaxPathIdReached,
@@ -12,8 +12,8 @@
 
§

RemoteCidsExhausted

No remote CIDs available to open a new path

§

ValidationFailed

Path could not be validated and will be abandoned

§

InvalidRemoteAddress(SocketAddr)

The remote address for the path is not supported by the endpoint

-

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for PathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for PathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for PathError

Source§

fn eq(&self, other: &PathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathError

Source§

fn clone(&self) -> PathError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for PathError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for PathError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for PathError

Source§

fn eq(&self, other: &PathError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for PathError

Source§

impl Eq for PathError

Source§

impl StructuralPartialEq for PathError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.PathEvent.html b/pr/451/docs/noq_proto/enum.PathEvent.html index 7b0341c06..c3c049431 100644 --- a/pr/451/docs/noq_proto/enum.PathEvent.html +++ b/pr/451/docs/noq_proto/enum.PathEvent.html @@ -1,46 +1,48 @@ -PathEvent in noq_proto - Rust

PathEvent

Enum PathEvent 

Source
pub enum PathEvent {
-    Established {
+PathEvent in noq_proto - Rust

PathEvent

Enum PathEvent 

Source
#[non_exhaustive]
pub enum PathEvent { +
#[non_exhaustive]
Established { id: PathId, }, - Abandoned { +
#[non_exhaustive]
Abandoned { id: PathId, reason: PathAbandonReason, }, - Discarded { +
#[non_exhaustive]
Discarded { id: PathId, path_stats: Box<PathStats>, }, - RemoteStatus { +
#[non_exhaustive]
RemoteStatus { id: PathId, status: PathStatus, }, - ObservedAddr { +
#[non_exhaustive]
ObservedAddr { id: PathId, addr: SocketAddr, }, }
Expand description

Application events about paths

-

Variants§

§

Established

A new path has established connection with the peer.

-

Fields

§id: PathId

The path which can now be used for application data.

-
§

Abandoned

A path was abandoned and is no longer usable.

+

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

#[non_exhaustive]
Established

A new path has established connection with the peer.

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

The path which can now be used for application data.

+
§

#[non_exhaustive]
Abandoned

A path was abandoned and is no longer usable.

+

Note that this may be the first event for a path: If a path is abandoned +before having been established, no Self::Established event is emitted.

This event will always be followed by Self::Discarded after some time.

-

Fields

§id: PathId

With path was abandoned.

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

The path that was abandoned.

§reason: PathAbandonReason

Reason why this path was abandoned.

-
§

Discarded

A path was discarded and all remaining state for it has been removed.

+
§

#[non_exhaustive]
Discarded

A path was discarded and all remaining state for it has been removed.

This event is the last event for a path, and is always emitted after Self::Abandoned.

-

Fields

§id: PathId

Which path had its state dropped

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

Which path had its state dropped

§path_stats: Box<PathStats>

The final path stats, they are no longer available via Connection::stats

-
§

RemoteStatus

The remote changed the status of the path

+
§

#[non_exhaustive]
RemoteStatus

The remote changed the status of the path

The local status is not changed because of this event. It is up to the application to update the local status, which is used for packet scheduling, when the remote changes the status.

-

Fields

§id: PathId

Path which has changed status

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

Path which has changed status

§status: PathStatus

The new status set by the remote

-
§

ObservedAddr

Received an observation of our external address from the peer.

-

Fields

§id: PathId

Path over which the observed address was reported, PathId::ZERO when multipath is +

§

#[non_exhaustive]
ObservedAddr

Received an observation of our external address from the peer.

+

Fields

This variant is marked as non-exhaustive
Non-exhaustive enum variants could have additional fields added in future. Therefore, non-exhaustive enum variants cannot be constructed in external crates and cannot be matched against.
§id: PathId

Path over which the observed address was reported, PathId::ZERO when multipath is not negotiated

§addr: SocketAddr

The address observed by the remote over this path

-

Trait Implementations§

Source§

impl Clone for PathEvent

Source§

fn clone(&self) -> PathEvent

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for PathEvent

Source§

fn eq(&self, other: &PathEvent) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathEvent

Source§

fn clone(&self) -> PathEvent

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<PathEvent> for Event

Source§

fn from(source: PathEvent) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for PathEvent

Source§

fn eq(&self, other: &PathEvent) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for PathEvent

Source§

impl StructuralPartialEq for PathEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.PathStatus.html b/pr/451/docs/noq_proto/enum.PathStatus.html index 38ab81b67..0fa4d8f3e 100644 --- a/pr/451/docs/noq_proto/enum.PathStatus.html +++ b/pr/451/docs/noq_proto/enum.PathStatus.html @@ -1,4 +1,4 @@ -PathStatus in noq_proto - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
+PathStatus in noq_proto - Rust

PathStatus

Enum PathStatus 

Source
pub enum PathStatus {
     Available,
     Backup,
 }
Expand description

The QUIC-MULTIPATH path status

@@ -10,8 +10,8 @@ capacity.

§

Backup

Paths marked as backup will only be used if there are no available paths

If the max_idle_timeout is specified the path will be kept alive so that it does not expire.

-

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PathStatus

Source§

fn default() -> PathStatus

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStatus

Source§

fn eq(&self, other: &PathStatus) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for PathStatus

Source§

fn clone(&self) -> PathStatus

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStatus

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PathStatus

Source§

fn default() -> PathStatus

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStatus

Source§

fn eq(&self, other: &PathStatus) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for PathStatus

Source§

impl Eq for PathStatus

Source§

impl StructuralPartialEq for PathStatus

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.ProtectedHeader.html b/pr/451/docs/noq_proto/enum.ProtectedHeader.html index a1b9dbb82..311db83fc 100644 --- a/pr/451/docs/noq_proto/enum.ProtectedHeader.html +++ b/pr/451/docs/noq_proto/enum.ProtectedHeader.html @@ -1,4 +1,4 @@ -ProtectedHeader in noq_proto - Rust

ProtectedHeader

Enum ProtectedHeader 

Source
pub enum ProtectedHeader {
+ProtectedHeader in noq_proto - Rust

ProtectedHeader

Enum ProtectedHeader 

Source
pub enum ProtectedHeader {
     Initial(ProtectedInitialHeader),
     Long {
         ty: LongType,
diff --git a/pr/451/docs/noq_proto/enum.ReadError.html b/pr/451/docs/noq_proto/enum.ReadError.html
index 6d5d42590..4aa49c05f 100644
--- a/pr/451/docs/noq_proto/enum.ReadError.html
+++ b/pr/451/docs/noq_proto/enum.ReadError.html
@@ -1,4 +1,4 @@
-ReadError in noq_proto - Rust

ReadError

Enum ReadError 

Source
pub enum ReadError {
+ReadError in noq_proto - Rust

ReadError

Enum ReadError 

Source
pub enum ReadError {
     Blocked,
     Reset(VarInt),
 }
Expand description

Errors triggered when reading from a recv stream

diff --git a/pr/451/docs/noq_proto/enum.ReadableError.html b/pr/451/docs/noq_proto/enum.ReadableError.html index f90d4ba0c..2e5a9a845 100644 --- a/pr/451/docs/noq_proto/enum.ReadableError.html +++ b/pr/451/docs/noq_proto/enum.ReadableError.html @@ -1,4 +1,4 @@ -ReadableError in noq_proto - Rust

ReadableError

Enum ReadableError 

Source
pub enum ReadableError {
+ReadableError in noq_proto - Rust

ReadableError

Enum ReadableError 

Source
pub enum ReadableError {
     ClosedStream,
     IllegalOrderedRead,
 }
Expand description

Errors triggered when opening a recv stream for reading

diff --git a/pr/451/docs/noq_proto/enum.SendDatagramError.html b/pr/451/docs/noq_proto/enum.SendDatagramError.html index c29ae2a77..3cca022a8 100644 --- a/pr/451/docs/noq_proto/enum.SendDatagramError.html +++ b/pr/451/docs/noq_proto/enum.SendDatagramError.html @@ -1,4 +1,4 @@ -SendDatagramError in noq_proto - Rust

SendDatagramError

Enum SendDatagramError 

Source
pub enum SendDatagramError {
+SendDatagramError in noq_proto - Rust

SendDatagramError

Enum SendDatagramError 

Source
pub enum SendDatagramError {
     UnsupportedByPeer,
     Disabled,
     TooLarge,
diff --git a/pr/451/docs/noq_proto/enum.SetPathStatusError.html b/pr/451/docs/noq_proto/enum.SetPathStatusError.html
index 577cc27a3..30309a743 100644
--- a/pr/451/docs/noq_proto/enum.SetPathStatusError.html
+++ b/pr/451/docs/noq_proto/enum.SetPathStatusError.html
@@ -1,11 +1,11 @@
-SetPathStatusError in noq_proto - Rust

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
+SetPathStatusError in noq_proto - Rust

SetPathStatusError

Enum SetPathStatusError 

Source
pub enum SetPathStatusError {
     ClosedPath,
     MultipathNotNegotiated,
 }
Expand description

Error from setting path status

Variants§

§

ClosedPath

Error indicating that a path has not been opened or has already been abandoned

§

MultipathNotNegotiated

Error indicating that this operation requires multipath to be negotiated whereas it hasn’t been

-

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for SetPathStatusError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for SetPathStatusError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for SetPathStatusError

Source§

fn eq(&self, other: &SetPathStatusError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for SetPathStatusError

Source§

fn clone(&self) -> SetPathStatusError

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SetPathStatusError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for SetPathStatusError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for SetPathStatusError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for SetPathStatusError

Source§

fn eq(&self, other: &SetPathStatusError) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for SetPathStatusError

Source§

impl StructuralPartialEq for SetPathStatusError

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.Side.html b/pr/451/docs/noq_proto/enum.Side.html index 60e140589..4ee51d5f6 100644 --- a/pr/451/docs/noq_proto/enum.Side.html +++ b/pr/451/docs/noq_proto/enum.Side.html @@ -1,26 +1,26 @@ -Side in noq_proto - Rust

Side

Enum Side 

Source
pub enum Side {
+Side in noq_proto - Rust

Side

Enum Side 

Source
pub enum Side {
     Client = 0,
     Server = 1,
 }
Expand description

Whether an endpoint was the initiator of a connection

Variants§

§

Client = 0

The initiator of a connection

§

Server = 1

The acceptor of a connection

-

Implementations§

Source§

impl Side

Source

pub fn is_client(self) -> bool

Shorthand for self == Side::Client

-
Source

pub fn is_server(self) -> bool

Shorthand for self == Side::Server

-

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for Side

Source§

fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self>

Generate an arbitrary value of Self from the given unstructured data. Read more
Source§

fn arbitrary_take_rest(u: Unstructured<'arbitrary>) -> Result<Self>

Generate an arbitrary value of Self from the entirety of the given -unstructured data. Read more
Source§

fn size_hint(depth: usize) -> (usize, Option<usize>)

Get a size hint for how many bytes out of an Unstructured this type +

Implementations§

Source§

impl Side

Source

pub fn is_client(self) -> bool

Shorthand for self == Side::Client

+
Source

pub fn is_server(self) -> bool

Shorthand for self == Side::Server

+

Trait Implementations§

Source§

impl<'arbitrary> Arbitrary<'arbitrary> for Side

Source§

fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self>

Generate an arbitrary value of Self from the given unstructured data. Read more
Source§

fn arbitrary_take_rest(u: Unstructured<'arbitrary>) -> Result<Self>

Generate an arbitrary value of Self from the entirety of the given +unstructured data. Read more
Source§

fn size_hint(depth: usize) -> (usize, Option<usize>)

Get a size hint for how many bytes out of an Unstructured this type needs to construct itself. Read more
§

fn try_size_hint( depth: usize, ) -> Result<(usize, Option<usize>), MaxRecursionReached>

Get a size hint for how many bytes out of an Unstructured this type -needs to construct itself. Read more
Source§

impl Clone for Side

Source§

fn clone(&self) -> Side

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Side

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<Side> for Side

Source§

fn from(s: Side) -> Self

Converts to this type from the input type.
Source§

impl Hash for Side

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where +needs to construct itself. Read more

Source§

impl Clone for Side

Source§

fn clone(&self) -> Side

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Side

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<Side> for Side

Source§

fn from(s: Side) -> Self

Converts to this type from the input type.
Source§

impl Hash for Side

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Not for Side

Source§

type Output = Side

The resulting type after applying the ! operator.
Source§

fn not(self) -> Self

Performs the unary ! operation. Read more
Source§

impl Ord for Side

Source§

fn cmp(&self, other: &Side) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Not for Side

Source§

type Output = Side

The resulting type after applying the ! operator.
Source§

fn not(self) -> Self

Performs the unary ! operation. Read more
Source§

impl Ord for Side

Source§

fn cmp(&self, other: &Side) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for Side

Source§

fn eq(&self, other: &Side) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for Side

Source§

fn partial_cmp(&self, other: &Side) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for Side

Source§

fn eq(&self, other: &Side) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for Side

Source§

fn partial_cmp(&self, other: &Side) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl Copy for Side

Source§

impl Eq for Side

Source§

impl StructuralPartialEq for Side

Auto Trait Implementations§

§

impl Freeze for Side

§

impl RefUnwindSafe for Side

§

impl Send for Side

§

impl Sync for Side

§

impl Unpin for Side

§

impl UnwindSafe for Side

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl Copy for Side

Source§

impl Eq for Side

Source§

impl StructuralPartialEq for Side

Auto Trait Implementations§

§

impl Freeze for Side

§

impl RefUnwindSafe for Side

§

impl Send for Side

§

impl Sync for Side

§

impl Unpin for Side

§

impl UnwindSafe for Side

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/enum.StreamEvent.html b/pr/451/docs/noq_proto/enum.StreamEvent.html index 016fbe344..e90e5e691 100644 --- a/pr/451/docs/noq_proto/enum.StreamEvent.html +++ b/pr/451/docs/noq_proto/enum.StreamEvent.html @@ -1,4 +1,4 @@ -StreamEvent in noq_proto - Rust

StreamEvent

Enum StreamEvent 

Source
pub enum StreamEvent {
+StreamEvent in noq_proto - Rust

StreamEvent

Enum StreamEvent 

Source
pub enum StreamEvent {
     Opened {
         dir: Dir,
     },
@@ -33,8 +33,8 @@
 
§error_code: VarInt

Error code supplied by the peer

§

Available

At least one new stream of a certain directionality may be opened

Fields

§dir: Dir

Directionality for which streams are newly available

-

Trait Implementations§

Source§

impl Debug for StreamEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for StreamEvent

Source§

fn eq(&self, other: &StreamEvent) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for StreamEvent

Source§

impl StructuralPartialEq for StreamEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for StreamEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for StreamEvent

Source§

fn eq(&self, other: &StreamEvent) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for StreamEvent

Source§

impl StructuralPartialEq for StreamEvent

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<Q, K> Equivalent<K> for Q
where diff --git a/pr/451/docs/noq_proto/enum.WriteError.html b/pr/451/docs/noq_proto/enum.WriteError.html index daf5f6ffb..a0b4a3ca0 100644 --- a/pr/451/docs/noq_proto/enum.WriteError.html +++ b/pr/451/docs/noq_proto/enum.WriteError.html @@ -1,4 +1,4 @@ -WriteError in noq_proto - Rust

WriteError

Enum WriteError 

Source
pub enum WriteError {
+WriteError in noq_proto - Rust

WriteError

Enum WriteError 

Source
pub enum WriteError {
     Blocked,
     Stopped(VarInt),
     ClosedStream,
diff --git a/pr/451/docs/noq_proto/index.html b/pr/451/docs/noq_proto/index.html
index b5abd6492..77b6c1e2a 100644
--- a/pr/451/docs/noq_proto/index.html
+++ b/pr/451/docs/noq_proto/index.html
@@ -1,4 +1,4 @@
-noq_proto - Rust

Crate noq_proto

Crate noq_proto 

Source
Expand description

Low-level protocol logic for the QUIC protoocol

+noq_proto - Rust

Crate noq_proto

Crate noq_proto 

Source
Expand description

Low-level protocol logic for the QUIC protoocol

noq-proto contains a fully deterministic implementation of QUIC protocol logic. It contains no networking code and does not get any relevant timestamps from the operating system. Most users may want to use the futures-based noq API instead.

diff --git a/pr/451/docs/noq_proto/n0_nat_traversal/enum.Error.html b/pr/451/docs/noq_proto/n0_nat_traversal/enum.Error.html index 2a505f88a..db12f5ef4 100644 --- a/pr/451/docs/noq_proto/n0_nat_traversal/enum.Error.html +++ b/pr/451/docs/noq_proto/n0_nat_traversal/enum.Error.html @@ -1,4 +1,4 @@ -Error in noq_proto::n0_nat_traversal - Rust

Error

Enum Error 

Source
pub enum Error {
+Error in noq_proto::n0_nat_traversal - Rust

Error

Enum Error 

Source
pub enum Error {
     TooManyAddresses,
     WrongConnectionSide,
     ExtensionNotNegotiated,
diff --git a/pr/451/docs/noq_proto/n0_nat_traversal/enum.Event.html b/pr/451/docs/noq_proto/n0_nat_traversal/enum.Event.html
index 0ec00c454..1d8c0e54f 100644
--- a/pr/451/docs/noq_proto/n0_nat_traversal/enum.Event.html
+++ b/pr/451/docs/noq_proto/n0_nat_traversal/enum.Event.html
@@ -1,4 +1,4 @@
-Event in noq_proto::n0_nat_traversal - Rust

Event

Enum Event 

Source
pub enum Event {
+Event in noq_proto::n0_nat_traversal - Rust

Event

Enum Event 

Source
pub enum Event {
     AddressAdded(SocketAddr),
     AddressRemoved(SocketAddr),
 }
Expand description

Event emitted when the client receives ADD_ADDRESS or REMOVE_ADDRESS frames.

diff --git a/pr/451/docs/noq_proto/n0_nat_traversal/index.html b/pr/451/docs/noq_proto/n0_nat_traversal/index.html index 0ab258d9b..87f832a22 100644 --- a/pr/451/docs/noq_proto/n0_nat_traversal/index.html +++ b/pr/451/docs/noq_proto/n0_nat_traversal/index.html @@ -1,2 +1,2 @@ -noq_proto::n0_nat_traversal - Rust

Module n0_nat_traversal

Module n0_nat_traversal 

Source
Expand description

n0’s (https://n0.computer) NAT Traversal protocol implementation.

+noq_proto::n0_nat_traversal - Rust

Module n0_nat_traversal

Module n0_nat_traversal 

Source
Expand description

n0’s (https://n0.computer) NAT Traversal protocol implementation.

Enums§

Error
Errors that the nat traversal state might encounter.
Event
Event emitted when the client receives ADD_ADDRESS or REMOVE_ADDRESS frames.
\ No newline at end of file diff --git a/pr/451/docs/noq_proto/struct.AcceptError.html b/pr/451/docs/noq_proto/struct.AcceptError.html index 308d5fcaf..9d1f45736 100644 --- a/pr/451/docs/noq_proto/struct.AcceptError.html +++ b/pr/451/docs/noq_proto/struct.AcceptError.html @@ -1,10 +1,10 @@ -AcceptError in noq_proto - Rust

AcceptError

Struct AcceptError 

Source
pub struct AcceptError {
+AcceptError in noq_proto - Rust

AcceptError

Struct AcceptError 

Source
pub struct AcceptError {
     pub cause: ConnectionError,
     pub response: Option<Transmit>,
 }
Expand description

Error type for attempting to accept an Incoming

Fields§

§cause: ConnectionError

Underlying error describing reason for failure

§response: Option<Transmit>

Optional response to transmit back

-

Trait Implementations§

Source§

impl Debug for AcceptError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for AcceptError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/451/docs/noq_proto/struct.AckFrequencyConfig.html b/pr/451/docs/noq_proto/struct.AckFrequencyConfig.html index b16a2f361..2faeb5da6 100644 --- a/pr/451/docs/noq_proto/struct.AckFrequencyConfig.html +++ b/pr/451/docs/noq_proto/struct.AckFrequencyConfig.html @@ -1,4 +1,4 @@ -AckFrequencyConfig in noq_proto - Rust

AckFrequencyConfig

Struct AckFrequencyConfig 

Source
pub struct AckFrequencyConfig { /* private fields */ }
Expand description

Parameters for controlling the peer’s acknowledgement frequency

+AckFrequencyConfig in noq_proto - Rust

AckFrequencyConfig

Struct AckFrequencyConfig 

Source
pub struct AckFrequencyConfig { /* private fields */ }
Expand description

Parameters for controlling the peer’s acknowledgement frequency

The parameters provided in this config will be sent to the peer at the beginning of the connection, so it can take them into account when sending acknowledgements (see each parameter’s description for details on how it influences acknowledgement frequency).

@@ -7,21 +7,21 @@ description for details on how it influences acknowledgement frequency).

The defaults produce behavior slightly different than the behavior without this extension, because they change the way reordered packets are handled (see AckFrequencyConfig::reordering_threshold for details).

-

Implementations§

Source§

impl AckFrequencyConfig

Source

pub fn ack_eliciting_threshold(&mut self, value: VarInt) -> &mut Self

The ack-eliciting threshold we will request the peer to use

+

Implementations§

Source§

impl AckFrequencyConfig

Source

pub fn ack_eliciting_threshold(&mut self, value: VarInt) -> &mut Self

The ack-eliciting threshold we will request the peer to use

This threshold represents the number of ack-eliciting packets an endpoint may receive without immediately sending an ACK.

The remote peer should send at least one ACK frame when more than this number of ack-eliciting packets have been received. A value of 0 results in a receiver immediately acknowledging every ack-eliciting packet.

Defaults to 1, which sends ACK frames for every other ack-eliciting packet.

-
Source

pub fn max_ack_delay(&mut self, value: Option<Duration>) -> &mut Self

The max_ack_delay we will request the peer to use

+
Source

pub fn max_ack_delay(&mut self, value: Option<Duration>) -> &mut Self

The max_ack_delay we will request the peer to use

This parameter represents the maximum amount of time that an endpoint waits before sending an ACK when the ack-eliciting threshold hasn’t been reached.

The effective max_ack_delay will be clamped to be at least the peer’s min_ack_delay transport parameter, and at most the greater of the current path RTT or 25ms.

Defaults to None, in which case the peer’s original max_ack_delay will be used, as obtained from its transport parameters.

-
Source

pub fn reordering_threshold(&mut self, value: VarInt) -> &mut Self

The reordering threshold we will request the peer to use

+
Source

pub fn reordering_threshold(&mut self, value: VarInt) -> &mut Self

The reordering threshold we will request the peer to use

This threshold represents the amount of out-of-order packets that will trigger an endpoint to send an ACK, without waiting for ack_eliciting_threshold to be exceeded or for max_ack_delay to be elapsed.

@@ -31,7 +31,7 @@ behavior when the extension is disabled).

It is recommended to set this value to TransportConfig::packet_threshold minus one. Since the default value for TransportConfig::packet_threshold is 3, this value defaults to 2.

-

Trait Implementations§

Source§

impl Clone for AckFrequencyConfig

Source§

fn clone(&self) -> AckFrequencyConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for AckFrequencyConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for AckFrequencyConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for AckFrequencyConfig

Source§

fn clone(&self) -> AckFrequencyConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for AckFrequencyConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for AckFrequencyConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.ApplicationClose.html b/pr/451/docs/noq_proto/struct.ApplicationClose.html index 2d38fcb75..62729256b 100644 --- a/pr/451/docs/noq_proto/struct.ApplicationClose.html +++ b/pr/451/docs/noq_proto/struct.ApplicationClose.html @@ -1,4 +1,4 @@ -ApplicationClose in noq_proto - Rust

ApplicationClose

Struct ApplicationClose 

Source
pub struct ApplicationClose {
+ApplicationClose in noq_proto - Rust

ApplicationClose

Struct ApplicationClose 

Source
pub struct ApplicationClose {
     pub error_code: VarInt,
     pub reason: Bytes,
 }
Expand description

Reason given by an application for closing the connection

diff --git a/pr/451/docs/noq_proto/struct.BloomTokenLog.html b/pr/451/docs/noq_proto/struct.BloomTokenLog.html index db432addb..cf46fe881 100644 --- a/pr/451/docs/noq_proto/struct.BloomTokenLog.html +++ b/pr/451/docs/noq_proto/struct.BloomTokenLog.html @@ -1,4 +1,4 @@ -BloomTokenLog in noq_proto - Rust

BloomTokenLog

Struct BloomTokenLog 

Source
pub struct BloomTokenLog(/* private fields */);
Expand description

Bloom filter-based TokenLog

+BloomTokenLog in noq_proto - Rust

BloomTokenLog

Struct BloomTokenLog 

Source
pub struct BloomTokenLog(/* private fields */);
Expand description

Bloom filter-based TokenLog

Parameterizable over an approximate maximum number of bytes to allocate. Starts out by storing used tokens in a hash set. Once the hash set becomes too large, converts it to a bloom filter. This achieves a memory profile of linear growth with an upper bound.

diff --git a/pr/451/docs/noq_proto/struct.Chunk.html b/pr/451/docs/noq_proto/struct.Chunk.html index 7eda38bc5..d49239045 100644 --- a/pr/451/docs/noq_proto/struct.Chunk.html +++ b/pr/451/docs/noq_proto/struct.Chunk.html @@ -1,4 +1,4 @@ -Chunk in noq_proto - Rust

Chunk

Struct Chunk 

Source
pub struct Chunk {
+Chunk in noq_proto - Rust

Chunk

Struct Chunk 

Source
pub struct Chunk {
     pub offset: u64,
     pub bytes: Bytes,
 }
Expand description

A chunk of data from the receive stream

diff --git a/pr/451/docs/noq_proto/struct.Chunks.html b/pr/451/docs/noq_proto/struct.Chunks.html index ee465f92f..1934e2b0f 100644 --- a/pr/451/docs/noq_proto/struct.Chunks.html +++ b/pr/451/docs/noq_proto/struct.Chunks.html @@ -1,4 +1,4 @@ -Chunks in noq_proto - Rust

Chunks

Struct Chunks 

Source
pub struct Chunks<'a> { /* private fields */ }
Expand description

Chunks returned from RecvStream::read().

+Chunks in noq_proto - Rust

Chunks

Struct Chunks 

Source
pub struct Chunks<'a> { /* private fields */ }
Expand description

Chunks returned from RecvStream::read().

§Note: Finalization Needed

Bytes read from the stream are not released from the congestion window until either Self::finalize() is called, or this type is dropped.

diff --git a/pr/451/docs/noq_proto/struct.ClientConfig.html b/pr/451/docs/noq_proto/struct.ClientConfig.html index b23dbf153..7cdb954c5 100644 --- a/pr/451/docs/noq_proto/struct.ClientConfig.html +++ b/pr/451/docs/noq_proto/struct.ClientConfig.html @@ -1,4 +1,4 @@ -ClientConfig in noq_proto - Rust

ClientConfig

Struct ClientConfig 

Source
#[non_exhaustive]
pub struct ClientConfig { /* private fields */ }
Expand description

Configuration for outgoing connections

+ClientConfig in noq_proto - Rust

ClientConfig

Struct ClientConfig 

Source
#[non_exhaustive]
pub struct ClientConfig { /* private fields */ }
Expand description

Configuration for outgoing connections

Default values should be suitable for most internet applications.

Implementations§

Source§

impl ClientConfig

Source

pub fn new(crypto: Arc<dyn ClientConfig>) -> Self

Create a default config with a particular cryptographic config

Source

pub fn initial_dst_cid_provider( diff --git a/pr/451/docs/noq_proto/struct.ClosedPath.html b/pr/451/docs/noq_proto/struct.ClosedPath.html index efc287838..b169bf2da 100644 --- a/pr/451/docs/noq_proto/struct.ClosedPath.html +++ b/pr/451/docs/noq_proto/struct.ClosedPath.html @@ -1,6 +1,6 @@ -ClosedPath in noq_proto - Rust

ClosedPath

Struct ClosedPath 

Source
pub struct ClosedPath { /* private fields */ }
Expand description

Error indicating that a path has not been opened or has already been abandoned

-

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedPath

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedPath

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosedPath

Source§

fn eq(&self, other: &ClosedPath) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +ClosedPath in noq_proto - Rust

ClosedPath

Struct ClosedPath 

Source
pub struct ClosedPath { /* private fields */ }
Expand description

Error indicating that a path has not been opened or has already been abandoned

+

Trait Implementations§

Source§

impl Clone for ClosedPath

Source§

fn clone(&self) -> ClosedPath

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedPath

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedPath

Source§

fn default() -> ClosedPath

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedPath

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedPath

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for ClosedPath

Source§

fn eq(&self, other: &ClosedPath) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ClosedPath

Source§

impl StructuralPartialEq for ClosedPath

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.ClosedStream.html b/pr/451/docs/noq_proto/struct.ClosedStream.html index a3e015f5a..19345a55c 100644 --- a/pr/451/docs/noq_proto/struct.ClosedStream.html +++ b/pr/451/docs/noq_proto/struct.ClosedStream.html @@ -1,6 +1,6 @@ -ClosedStream in noq_proto - Rust

ClosedStream

Struct ClosedStream 

Source
pub struct ClosedStream { /* private fields */ }
Expand description

Error indicating that a stream has not been opened or has already been finished or reset

-

Trait Implementations§

Source§

impl Clone for ClosedStream

Source§

fn clone(&self) -> ClosedStream

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedStream

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedStream

Source§

fn default() -> ClosedStream

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedStream

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedStream

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ClosedStream> for Error

Source§

fn from(x: ClosedStream) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ClosedStream

Source§

fn eq(&self, other: &ClosedStream) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Eq for ClosedStream

Source§

impl StructuralPartialEq for ClosedStream

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +ClosedStream in noq_proto - Rust

ClosedStream

Struct ClosedStream 

Source
pub struct ClosedStream { /* private fields */ }
Expand description

Error indicating that a stream has not been opened or has already been finished or reset

+

Trait Implementations§

Source§

impl Clone for ClosedStream

Source§

fn clone(&self) -> ClosedStream

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ClosedStream

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ClosedStream

Source§

fn default() -> ClosedStream

Returns the “default value” for a type. Read more
Source§

impl Display for ClosedStream

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for ClosedStream

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<ClosedStream> for Error

Source§

fn from(x: ClosedStream) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for ClosedStream

Source§

fn eq(&self, other: &ClosedStream) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Eq for ClosedStream

Source§

impl StructuralPartialEq for ClosedStream

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.Connection.html b/pr/451/docs/noq_proto/struct.Connection.html index 0f23fdf1a..9846fc5a9 100644 --- a/pr/451/docs/noq_proto/struct.Connection.html +++ b/pr/451/docs/noq_proto/struct.Connection.html @@ -1,4 +1,4 @@ -Connection in noq_proto - Rust

Connection

Struct Connection 

Source
pub struct Connection { /* private fields */ }
Expand description

Protocol state and logic for a single QUIC connection

+Connection in noq_proto - Rust

Connection

Struct Connection 

Source
pub struct Connection { /* private fields */ }
Expand description

Protocol state and logic for a single QUIC connection

Objects of this type receive ConnectionEvents and emit EndpointEvents and application Events to make progress. To handle timeouts, a Connection returns timer updates and expects timeouts through various methods. A number of simple getter methods are exposed @@ -33,7 +33,7 @@ increasing time. Specifically, calling Instant may be interleaved in any order with a call to handle_event at that same instant; however events or timeouts with different instants must not be interleaved.

-

Implementations§

Source§

impl Connection

Source

pub fn poll_timeout(&mut self) -> Option<Instant>

Returns the next time at which handle_timeout should be called

+

Implementations§

Source§

impl Connection

Source

pub fn poll_timeout(&mut self) -> Option<Instant>

Returns the next time at which handle_timeout should be called

The value returned may change after:

  • the application performed some I/O on the connection
  • @@ -41,31 +41,33 @@ events or timeouts with different instants must not be interleaved.

  • a call to poll_transmit returned Some
  • a call was made to handle_timeout
-
Source

pub fn poll(&mut self) -> Option<Event>

Returns application-facing events

+
Source

pub fn poll(&mut self) -> Option<Event>

Returns application-facing events

Connections should be polled for events after:

  • a call was made to handle_event
  • a call was made to handle_timeout
-
Source

pub fn poll_endpoint_events(&mut self) -> Option<EndpointEvent>

Return endpoint-facing events

-
Source

pub fn streams(&mut self) -> Streams<'_>

Provide control over streams

-
Source

pub fn recv_stream(&mut self, id: StreamId) -> RecvStream<'_>

Provide control over streams

-
Source

pub fn send_stream(&mut self, id: StreamId) -> SendStream<'_>

Provide control over streams

-
Source

pub fn open_path_ensure( +

Source

pub fn poll_endpoint_events(&mut self) -> Option<EndpointEvent>

Return endpoint-facing events

+
Source

pub fn streams(&mut self) -> Streams<'_>

Provide control over streams

+
Source

pub fn recv_stream(&mut self, id: StreamId) -> RecvStream<'_>

Provide control over streams

+
Source

pub fn send_stream(&mut self, id: StreamId) -> SendStream<'_>

Provide control over streams

+
Source

pub fn open_path_ensure( &mut self, network_path: FourTuple, initial_status: PathStatus, now: Instant, ) -> Result<(PathId, bool), PathError>

Opens a new path only if no path on the same network path currently exists.

-

This comparison will use FourTuple::is_probably_same_path on the given network_path -and pass it existing path’s network paths.

-

This means that you can pass local_ip: None to make the comparison only compare -remote addresses.

-

This avoids having to guess which local interface will be used to communicate with the -remote, should it not be known yet. We assume that if we already have a path to the remote, -the OS is likely to use the same interface to talk to said remote.

-

See also open_path. Returns (path_id, true) if the path already existed. (path_id, false) if was opened.

-
Source

pub fn open_path( +

Returns (path_id, true) if the path already existed, or (path_id, false) +if was opened.

+

If network_path has no local IP set, then this will open a new path +if no path exists for this remote address, independent of the existing +path’s local IP. If a local IP is set, it will match against the full +four-tuple of existing paths. Not setting the local IP avoids having to +guess which local interface will be used to communicate with the remote, +should it not be known yet. We assume that if we already have a path to +the remote, the OS is likely to use the same interface to talk to said remote.

+

See also open_path.

+

Source

pub fn open_path( &mut self, network_path: FourTuple, initial_status: PathStatus, @@ -74,7 +76,7 @@ the OS is likely to use the same interface to talk to said remote.

Further errors might occur and they will be emitted in PathEvent::Abandoned events with this path id. Once the path is opened and can carry application data it will be reported using a PathEvent::Established event.

-

Source

pub fn close_path( +

Source

pub fn close_path( &mut self, now: Instant, path_id: PathId, @@ -83,18 +85,18 @@ will be reported using a Returns ClosePathError::LastOpenPath if this is the last open path. It does allow closing paths which have not yet been opened, as e.g. is the case when receiving a PATH_ABANDON from the peer for a path that was never opened locally.

-

Source

pub fn paths(&self) -> Vec<PathId>

Returns all known paths.

+
Source

pub fn paths(&self) -> Vec<PathId>

Returns all known paths.

There is no guarantee any of these paths are open or usable.

-
Source

pub fn path_status(&self, path_id: PathId) -> Result<PathStatus, ClosedPath>

Gets the local PathStatus for a known PathId

-
Source

pub fn network_path(&self, path_id: PathId) -> Result<FourTuple, ClosedPath>

Returns the path’s network path represented as a 4-tuple.

-
Source

pub fn set_path_status( +

Source

pub fn path_status(&self, path_id: PathId) -> Result<PathStatus, ClosedPath>

Gets the local PathStatus for a known PathId

+
Source

pub fn network_path(&self, path_id: PathId) -> Result<FourTuple, ClosedPath>

Returns the path’s network path represented as a 4-tuple.

+
Source

pub fn set_path_status( &mut self, path_id: PathId, status: PathStatus, ) -> Result<PathStatus, SetPathStatusError>

Sets the PathStatus for a known PathId

Returns the previous path status on success.

-
Source

pub fn remote_path_status(&self, path_id: PathId) -> Option<PathStatus>

Returns the remote path status

-
Source

pub fn set_path_max_idle_timeout( +

Source

pub fn remote_path_status(&self, path_id: PathId) -> Option<PathStatus>

Returns the remote path status

+
Source

pub fn set_path_max_idle_timeout( &mut self, now: Instant, path_id: PathId, @@ -104,14 +106,14 @@ when receiving a PATH_ABANDON from the peer for a path that was never opened loc idle time. Setting None disables the timeout and stops the timer.

See TransportConfig::default_path_max_idle_timeout for details.

Returns the previous value of the setting.

-

Source

pub fn set_path_keep_alive_interval( +

Source

pub fn set_path_keep_alive_interval( &mut self, path_id: PathId, interval: Option<Duration>, ) -> Result<Option<Duration>, ClosedPath>

Sets the keep_alive_interval for a specific path

See TransportConfig::default_path_keep_alive_interval for details.

Returns the previous value of the setting.

-
Source

pub fn poll_transmit( +

Source

pub fn poll_transmit( &mut self, now: Instant, max_datagrams: NonZeroUsize, @@ -125,42 +127,42 @@ idle time. Setting None disables the timeout and stops the timer.

max_datagrams specifies how many datagrams can be returned inside a single Transmit using GSO. This must be at least 1.

-

Source

pub fn handle_event(&mut self, event: ConnectionEvent)

Process ConnectionEvents generated by the associated Endpoint

+
Source

pub fn handle_event(&mut self, event: ConnectionEvent)

Process ConnectionEvents generated by the associated Endpoint

Will execute protocol logic upon receipt of a connection event, in turn preparing signals (including application Events, EndpointEvents and outgoing datagrams) that should be extracted through the relevant methods.

-
Source

pub fn handle_timeout(&mut self, now: Instant)

Process timer expirations

+
Source

pub fn handle_timeout(&mut self, now: Instant)

Process timer expirations

Executes protocol logic, potentially preparing signals (including application Events, EndpointEvents and outgoing datagrams) that should be extracted through the relevant methods.

It is most efficient to call this immediately after the system clock reaches the latest Instant that was output by poll_timeout; however spurious extra calls will simply no-op and therefore are safe.

-
Source

pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes)

Close a connection immediately

+
Source

pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes)

Close a connection immediately

This does not ensure delivery of outstanding data. It is the application’s responsibility to call this only when all important communications have been completed, e.g. by calling SendStream::finish on outstanding streams and waiting for the corresponding StreamEvent::Finished event.

If Streams::send_streams returns 0, all outstanding stream data has been delivered. There may still be data from the peer that has not been received.

-
Source

pub fn datagrams(&mut self) -> Datagrams<'_>

Control datagrams

-
Source

pub fn stats(&mut self) -> ConnectionStats

Returns connection statistics

-
Source

pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats>

Returns path statistics

-
Source

pub fn ping(&mut self)

Ping the remote endpoint

+
Source

pub fn datagrams(&mut self) -> Datagrams<'_>

Control datagrams

+
Source

pub fn stats(&mut self) -> ConnectionStats

Returns connection statistics

+
Source

pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats>

Returns path statistics

+
Source

pub fn ping(&mut self)

Ping the remote endpoint

Causes an ACK-eliciting packet to be transmitted on the connection.

-
Source

pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath>

Ping the remote endpoint over a specific path

+
Source

pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath>

Ping the remote endpoint over a specific path

Causes an ACK-eliciting packet to be transmitted on the path.

-
Source

pub fn force_key_update(&mut self)

Update traffic keys spontaneously

+
Source

pub fn force_key_update(&mut self)

Update traffic keys spontaneously

This can be useful for testing key updates, as they otherwise only happen infrequently.

-
Source

pub fn crypto_session(&self) -> &dyn Session

Get a session reference

-
Source

pub fn is_handshaking(&self) -> bool

Whether the connection is in the process of being established

+
Source

pub fn crypto_session(&self) -> &dyn Session

Get a session reference

+
Source

pub fn is_handshaking(&self) -> bool

Whether the connection is in the process of being established

If this returns false, the connection may be either established or closed, signaled by the emission of a Connected or ConnectionLost event respectively. Note that locally-initiated closes via close() do not emit a ConnectionLost event.

For an established connection this essentially means the handshake is completed, but not necessarily yet confirmed.

-
Source

pub fn is_closed(&self) -> bool

Whether the connection is closed

+
Source

pub fn is_closed(&self) -> bool

Whether the connection is closed

Closed connections cannot transport any further data. A connection becomes closed when either peer application intentionally closes it, or when either transport layer detects an error such as a time-out or certificate validation failure.

@@ -168,24 +170,24 @@ error such as a time-out or certificate validation failure.

connection is closed by the peer or due to an error. When the local application closes the connection via close(), no ConnectionLost event is emitted; instead, pending operations fail with ConnectionError::LocallyClosed.

-
Source

pub fn is_drained(&self) -> bool

Whether there is no longer any need to keep the connection around

+
Source

pub fn is_drained(&self) -> bool

Whether there is no longer any need to keep the connection around

Closed connections become drained after a brief timeout to absorb any remaining in-flight packets from the peer. All drained connections have been closed.

-
Source

pub fn accepted_0rtt(&self) -> bool

For clients, if the peer accepted the 0-RTT data packets

+
Source

pub fn accepted_0rtt(&self) -> bool

For clients, if the peer accepted the 0-RTT data packets

The value is meaningless until after the handshake completes.

-
Source

pub fn has_0rtt(&self) -> bool

Whether 0-RTT is/was possible during the handshake

-
Source

pub fn has_pending_retransmits(&self) -> bool

Whether there are any pending retransmits

-
Source

pub fn side(&self) -> Side

Look up whether we’re the client or server of this Connection

-
Source

pub fn path_observed_address( +

Source

pub fn has_0rtt(&self) -> bool

Whether 0-RTT is/was possible during the handshake

+
Source

pub fn has_pending_retransmits(&self) -> bool

Whether there are any pending retransmits

+
Source

pub fn side(&self) -> Side

Look up whether we’re the client or server of this Connection

+
Source

pub fn path_observed_address( &self, path_id: PathId, ) -> Result<Option<SocketAddr>, ClosedPath>

Get the address observed by the remote over the given path

-
Source

pub fn rtt(&self, path_id: PathId) -> Option<Duration>

Current best estimate of this connection’s latency (round-trip-time)

-
Source

pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller>

Current state of this connection’s congestion controller, for debugging purposes

-
Source

pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt)

Modify the number of remotely initiated streams that may be concurrently open

+
Source

pub fn rtt(&self, path_id: PathId) -> Option<Duration>

Current best estimate of this connection’s latency (round-trip-time)

+
Source

pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller>

Current state of this connection’s congestion controller, for debugging purposes

+
Source

pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt)

Modify the number of remotely initiated streams that may be concurrently open

No streams may be opened by the peer unless fewer than count are already open. Large counts increase both minimum and worst-case memory consumption.

-
Source

pub fn set_max_concurrent_paths( +

Source

pub fn set_max_concurrent_paths( &mut self, now: Instant, count: NonZeroU32, @@ -196,16 +198,16 @@ actively reduce paths they must be closed using TransportConfig) this can not enable multipath and will fail.

-

Source

pub fn max_concurrent_streams(&self, dir: Dir) -> u64

Current number of remotely initiated streams that may be concurrently open

+
Source

pub fn max_concurrent_streams(&self, dir: Dir) -> u64

Current number of remotely initiated streams that may be concurrently open

If the target for this limit is reduced using set_max_concurrent_streams, it will not change immediately, even if fewer streams are open. Instead, it will decrement by one for each time a remotely initiated stream of matching directionality is closed.

-
Source

pub fn set_send_window(&mut self, send_window: u64)

Source

pub fn set_receive_window(&mut self, receive_window: VarInt)

Source

pub fn is_multipath_negotiated(&self) -> bool

Whether the Multipath for QUIC extension is enabled.

+
Source

pub fn set_send_window(&mut self, send_window: u64)

Source

pub fn set_receive_window(&mut self, receive_window: VarInt)

Source

pub fn is_multipath_negotiated(&self) -> bool

Whether the Multipath for QUIC extension is enabled.

Multipath is only enabled after the handshake is completed and if it was enabled by both peers.

-
Source

pub fn handle_network_change( +

Source

pub fn handle_network_change( &mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant, @@ -220,26 +222,26 @@ paths as non recoverable when necessary accelerates connectivity re-establishmen allow it altogether.

The optional hint allows callers to indicate when paths are non-recoverable and should be migrated to new a PathId.

-

Source

pub fn current_mtu(&self) -> u16

Storage size required for the largest packet that can be transmitted on all currently +

Source

pub fn current_mtu(&self) -> u16

Storage size required for the largest packet that can be transmitted on all currently available paths

Buffers passed to Connection::poll_transmit should be at least this large.

When multipath is enabled, this value is the minimum MTU across all available paths.

-
Source

pub fn add_nat_traversal_address( +

Source

pub fn add_nat_traversal_address( &mut self, address: SocketAddr, ) -> Result<(), Error>

Add addresses the local endpoint considers are reachable for nat traversal.

-
Source

pub fn remove_nat_traversal_address( +

Source

pub fn remove_nat_traversal_address( &mut self, address: SocketAddr, ) -> Result<(), Error>

Removes an address the endpoing no longer considers reachable for nat traversal

Addresses not present in the set will be silently ignored.

-
Source

pub fn get_local_nat_traversal_addresses( +

Source

pub fn get_local_nat_traversal_addresses( &self, ) -> Result<Vec<SocketAddr>, Error>

Get the current local nat traversal addresses

-
Source

pub fn get_remote_nat_traversal_addresses( +

Source

pub fn get_remote_nat_traversal_addresses( &self, ) -> Result<Vec<SocketAddr>, Error>

Get the currently advertised nat traversal addresses by the server

-
Source

pub fn initiate_nat_traversal_round( +

Source

pub fn initiate_nat_traversal_round( &mut self, now: Instant, ) -> Result<Vec<SocketAddr>, Error>

Initiates a new nat traversal round

@@ -250,7 +252,7 @@ round is initiated, the previous one is cancelled.

4-tuple.

Returns the server addresses that are now being probed. If addresses fail due to spurious errors, these might succeed later and not be returned in this set.

-

Trait Implementations§

Source§

impl Debug for Connection

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Connection

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/451/docs/noq_proto/struct.ConnectionClose.html b/pr/451/docs/noq_proto/struct.ConnectionClose.html index 628b5a5c8..36ac1c308 100644 --- a/pr/451/docs/noq_proto/struct.ConnectionClose.html +++ b/pr/451/docs/noq_proto/struct.ConnectionClose.html @@ -1,4 +1,4 @@ -ConnectionClose in noq_proto - Rust

ConnectionClose

Struct ConnectionClose 

Source
pub struct ConnectionClose {
+ConnectionClose in noq_proto - Rust

ConnectionClose

Struct ConnectionClose 

Source
pub struct ConnectionClose {
     pub error_code: TransportErrorCode,
     pub frame_type: MaybeFrame,
     pub reason: Bytes,
diff --git a/pr/451/docs/noq_proto/struct.ConnectionEvent.html b/pr/451/docs/noq_proto/struct.ConnectionEvent.html
index f64a215b7..ff879b168 100644
--- a/pr/451/docs/noq_proto/struct.ConnectionEvent.html
+++ b/pr/451/docs/noq_proto/struct.ConnectionEvent.html
@@ -1,4 +1,4 @@
-ConnectionEvent in noq_proto - Rust

ConnectionEvent

Struct ConnectionEvent 

Source
pub struct ConnectionEvent(/* private fields */);
Expand description

Events sent from an Endpoint to a Connection

+ConnectionEvent in noq_proto - Rust

ConnectionEvent

Struct ConnectionEvent 

Source
pub struct ConnectionEvent(/* private fields */);
Expand description

Events sent from an Endpoint to a Connection

Trait Implementations§

Source§

impl Debug for ConnectionEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq_proto/struct.ConnectionHandle.html b/pr/451/docs/noq_proto/struct.ConnectionHandle.html index b5f203192..783ab523a 100644 --- a/pr/451/docs/noq_proto/struct.ConnectionHandle.html +++ b/pr/451/docs/noq_proto/struct.ConnectionHandle.html @@ -1,14 +1,14 @@ -ConnectionHandle in noq_proto - Rust

ConnectionHandle

Struct ConnectionHandle 

Source
pub struct ConnectionHandle(pub usize);
Expand description

Internal identifier for a Connection currently associated with an endpoint

-

Tuple Fields§

§0: usize

Trait Implementations§

Source§

impl Clone for ConnectionHandle

Source§

fn clone(&self) -> ConnectionHandle

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionHandle

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<ConnectionHandle> for usize

Source§

fn from(x: ConnectionHandle) -> Self

Converts to this type from the input type.
Source§

impl Hash for ConnectionHandle

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where +ConnectionHandle in noq_proto - Rust

ConnectionHandle

Struct ConnectionHandle 

Source
pub struct ConnectionHandle(pub usize);
Expand description

Internal identifier for a Connection currently associated with an endpoint

+

Tuple Fields§

§0: usize

Trait Implementations§

Source§

impl Clone for ConnectionHandle

Source§

fn clone(&self) -> ConnectionHandle

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionHandle

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<ConnectionHandle> for usize

Source§

fn from(x: ConnectionHandle) -> Self

Converts to this type from the input type.
Source§

impl Hash for ConnectionHandle

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Index<ConnectionHandle> for Slab<ConnectionMeta>

Source§

type Output = ConnectionMeta

The returned type after indexing.
Source§

fn index(&self, ch: ConnectionHandle) -> &ConnectionMeta

Performs the indexing (container[index]) operation. Read more
Source§

impl IndexMut<ConnectionHandle> for Slab<ConnectionMeta>

Source§

fn index_mut(&mut self, ch: ConnectionHandle) -> &mut ConnectionMeta

Performs the mutable indexing (container[index]) operation. Read more
Source§

impl Ord for ConnectionHandle

Source§

fn cmp(&self, other: &ConnectionHandle) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Index<ConnectionHandle> for Slab<ConnectionMeta>

Source§

type Output = ConnectionMeta

The returned type after indexing.
Source§

fn index(&self, ch: ConnectionHandle) -> &ConnectionMeta

Performs the indexing (container[index]) operation. Read more
Source§

impl IndexMut<ConnectionHandle> for Slab<ConnectionMeta>

Source§

fn index_mut(&mut self, ch: ConnectionHandle) -> &mut ConnectionMeta

Performs the mutable indexing (container[index]) operation. Read more
Source§

impl Ord for ConnectionHandle

Source§

fn cmp(&self, other: &ConnectionHandle) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for ConnectionHandle

Source§

fn eq(&self, other: &ConnectionHandle) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for ConnectionHandle

Source§

fn partial_cmp(&self, other: &ConnectionHandle) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for ConnectionHandle

Source§

fn eq(&self, other: &ConnectionHandle) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for ConnectionHandle

Source§

fn partial_cmp(&self, other: &ConnectionHandle) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl Copy for ConnectionHandle

Source§

impl Eq for ConnectionHandle

Source§

impl StructuralPartialEq for ConnectionHandle

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl Copy for ConnectionHandle

Source§

impl Eq for ConnectionHandle

Source§

impl StructuralPartialEq for ConnectionHandle

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.ConnectionId.html b/pr/451/docs/noq_proto/struct.ConnectionId.html index ae022ba89..238be260d 100644 --- a/pr/451/docs/noq_proto/struct.ConnectionId.html +++ b/pr/451/docs/noq_proto/struct.ConnectionId.html @@ -1,7 +1,7 @@ -ConnectionId in noq_proto - Rust

ConnectionId

Struct ConnectionId 

Source
pub struct ConnectionId { /* private fields */ }
Expand description

Protocol-level identifier for a connection.

+ConnectionId in noq_proto - Rust

ConnectionId

Struct ConnectionId 

Source
pub struct ConnectionId { /* private fields */ }
Expand description

Protocol-level identifier for a connection.

Mainly useful for identifying this connection’s packets on the wire with tools like Wireshark.

-

Implementations§

Source§

impl ConnectionId

Source

pub fn new(bytes: &[u8]) -> Self

Construct cid from byte array

-
Source

pub fn from_buf(buf: &mut (impl Buf + ?Sized), len: usize) -> Self

Constructs cid by reading len bytes from a Buf

+

Implementations§

Source§

impl ConnectionId

Source

pub fn new(bytes: &[u8]) -> Self

Construct cid from byte array

+
Source

pub fn from_buf(buf: &mut (impl Buf + ?Sized), len: usize) -> Self

Constructs cid by reading len bytes from a Buf

Callers need to assure that buf.remaining() >= len

Methods from Deref<Target = [u8]>§

1.23.0 · Source

pub fn is_ascii(&self) -> bool

Checks if all bytes in this slice are within the ASCII range.

Source

pub fn as_ascii(&self) -> Option<&[AsciiChar]>

🔬This is a nightly-only experimental API. (ascii_char)

If this slice is_ascii, returns it as a slice of @@ -2422,16 +2422,16 @@ is mapped to its ASCII lower case equivalent.

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, but non-ASCII letters are unchanged.

To lowercase the value in-place, use make_ascii_lowercase.

-

Trait Implementations§

Source§

impl Clone for ConnectionId

Source§

fn clone(&self) -> ConnectionId

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Deref for ConnectionId

Source§

type Target = [u8]

The resulting type after dereferencing.
Source§

fn deref(&self) -> &[u8]

Dereferences the value.
Source§

impl DerefMut for ConnectionId

Source§

fn deref_mut(&mut self) -> &mut [u8]

Mutably dereferences the value.
Source§

impl Display for ConnectionId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Hash for ConnectionId

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where +

Trait Implementations§

Source§

impl Clone for ConnectionId

Source§

fn clone(&self) -> ConnectionId

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Deref for ConnectionId

Source§

type Target = [u8]

The resulting type after dereferencing.
Source§

fn deref(&self) -> &[u8]

Dereferences the value.
Source§

impl DerefMut for ConnectionId

Source§

fn deref_mut(&mut self) -> &mut [u8]

Mutably dereferences the value.
Source§

impl Display for ConnectionId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Hash for ConnectionId

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for ConnectionId

Source§

fn cmp(&self, other: &ConnectionId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for ConnectionId

Source§

fn cmp(&self, other: &ConnectionId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for ConnectionId

Source§

fn eq(&self, other: &ConnectionId) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for ConnectionId

Source§

fn partial_cmp(&self, other: &ConnectionId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for ConnectionId

Source§

fn eq(&self, other: &ConnectionId) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for ConnectionId

Source§

fn partial_cmp(&self, other: &ConnectionId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl Copy for ConnectionId

Source§

impl Eq for ConnectionId

Source§

impl StructuralPartialEq for ConnectionId

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl Copy for ConnectionId

Source§

impl Eq for ConnectionId

Source§

impl StructuralPartialEq for ConnectionId

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.ConnectionStats.html b/pr/451/docs/noq_proto/struct.ConnectionStats.html index 78f941775..e13a7f26a 100644 --- a/pr/451/docs/noq_proto/struct.ConnectionStats.html +++ b/pr/451/docs/noq_proto/struct.ConnectionStats.html @@ -1,4 +1,4 @@ -ConnectionStats in noq_proto - Rust

ConnectionStats

Struct ConnectionStats 

Source
#[non_exhaustive]
pub struct ConnectionStats { +ConnectionStats in noq_proto - Rust

ConnectionStats

Struct ConnectionStats 

Source
#[non_exhaustive]
pub struct ConnectionStats { pub udp_tx: UdpStats, pub udp_rx: UdpStats, pub frame_tx: FrameStats, @@ -14,7 +14,7 @@ paths that exist as well as all paths that previously existed.

§frame_rx: FrameStats

Statistics about frames received on the connection.

§lost_packets: u64

The number of packets lost on the connection.

§lost_bytes: u64

The number of bytes lost on the connection.

-

Trait Implementations§

Source§

impl Add<PathStats> for ConnectionStats

Source§

type Output = ConnectionStats

The resulting type after applying the + operator.
Source§

fn add(self, rhs: PathStats) -> Self::Output

Performs the + operation. Read more
Source§

impl AddAssign<PathStats> for ConnectionStats

Source§

fn add_assign(&mut self, rhs: PathStats)

Performs the += operation. Read more
Source§

impl Clone for ConnectionStats

Source§

fn clone(&self) -> ConnectionStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ConnectionStats

Source§

fn default() -> ConnectionStats

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Add<PathStats> for ConnectionStats

Source§

type Output = ConnectionStats

The resulting type after applying the + operator.
Source§

fn add(self, rhs: PathStats) -> Self::Output

Performs the + operation. Read more
Source§

impl AddAssign<PathStats> for ConnectionStats

Source§

fn add_assign(&mut self, rhs: PathStats)

Performs the += operation. Read more
Source§

impl Clone for ConnectionStats

Source§

fn clone(&self) -> ConnectionStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ConnectionStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ConnectionStats

Source§

fn default() -> ConnectionStats

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.Datagram.html b/pr/451/docs/noq_proto/struct.Datagram.html index 632739b3a..10047a797 100644 --- a/pr/451/docs/noq_proto/struct.Datagram.html +++ b/pr/451/docs/noq_proto/struct.Datagram.html @@ -1,4 +1,4 @@ -Datagram in noq_proto - Rust

Datagram

Struct Datagram 

Source
pub struct Datagram {
+Datagram in noq_proto - Rust

Datagram

Struct Datagram 

Source
pub struct Datagram {
     pub data: Bytes,
 }
Expand description

An unreliable datagram

Fields§

§data: Bytes

Payload

diff --git a/pr/451/docs/noq_proto/struct.DatagramInfo.html b/pr/451/docs/noq_proto/struct.DatagramInfo.html index d3970a5d0..c9d128a88 100644 --- a/pr/451/docs/noq_proto/struct.DatagramInfo.html +++ b/pr/451/docs/noq_proto/struct.DatagramInfo.html @@ -1,4 +1,4 @@ -DatagramInfo in noq_proto - Rust

DatagramInfo

Struct DatagramInfo 

Source
pub struct DatagramInfo(/* private fields */);

Trait Implementations§

Source§

impl Clone for DatagramInfo

Source§

fn clone(&self) -> DatagramInfo

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for DatagramInfo

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for DatagramInfo

Source§

fn fmt(&self, __derive_more_f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for DatagramInfo

Source§

fn eq(&self, other: &DatagramInfo) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +DatagramInfo in noq_proto - Rust

DatagramInfo

Struct DatagramInfo 

Source
pub struct DatagramInfo(/* private fields */);

Trait Implementations§

Source§

impl Clone for DatagramInfo

Source§

fn clone(&self) -> DatagramInfo

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for DatagramInfo

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for DatagramInfo

Source§

fn fmt(&self, __derive_more_f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for DatagramInfo

Source§

fn eq(&self, other: &DatagramInfo) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for DatagramInfo

Source§

impl Eq for DatagramInfo

Source§

impl StructuralPartialEq for DatagramInfo

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq_proto/struct.Datagrams.html b/pr/451/docs/noq_proto/struct.Datagrams.html index 858631338..a843c2c11 100644 --- a/pr/451/docs/noq_proto/struct.Datagrams.html +++ b/pr/451/docs/noq_proto/struct.Datagrams.html @@ -1,4 +1,4 @@ -Datagrams in noq_proto - Rust

Datagrams

Struct Datagrams 

Source
pub struct Datagrams<'a> { /* private fields */ }
Expand description

API to control datagram traffic

+Datagrams in noq_proto - Rust

Datagrams

Struct Datagrams 

Source
pub struct Datagrams<'a> { /* private fields */ }
Expand description

API to control datagram traffic

Implementations§

Source§

impl Datagrams<'_>

Source

pub fn send(&mut self, data: Bytes, drop: bool) -> Result<(), SendDatagramError>

Queue an unreliable, unordered datagram for immediate transmission

If drop is true, previously queued datagrams which are still unsent may be discarded to make space for this datagram, in order of oldest to newest. If drop is false, and there diff --git a/pr/451/docs/noq_proto/struct.DecryptedInitial.html b/pr/451/docs/noq_proto/struct.DecryptedInitial.html index f5883e266..7d99c78e6 100644 --- a/pr/451/docs/noq_proto/struct.DecryptedInitial.html +++ b/pr/451/docs/noq_proto/struct.DecryptedInitial.html @@ -1,7 +1,7 @@ -DecryptedInitial in noq_proto - Rust

DecryptedInitial

Struct DecryptedInitial 

Source
pub struct DecryptedInitial(/* private fields */);
Expand description

Decrypted payload of a QUIC Initial packet

+DecryptedInitial in noq_proto - Rust

DecryptedInitial

Struct DecryptedInitial 

Source
pub struct DecryptedInitial(/* private fields */);
Expand description

Decrypted payload of a QUIC Initial packet

Obtained via Incoming::decrypt. Can be used to extract information from the TLS ClientHello without completing the handshake.

-

Implementations§

Source§

impl DecryptedInitial

Source

pub fn alpns(&self) -> Option<IncomingAlpns>

Best-effort extraction of the ALPN protocols from the TLS ClientHello

+

Implementations§

Source§

impl DecryptedInitial

Source

pub fn alpns(&self) -> Option<IncomingAlpns>

Best-effort extraction of the ALPN protocols from the TLS ClientHello

Parses the CRYPTO frames to extract the ALPN extension. This is intended for routing and filtering; it is not guaranteed to succeed if the ClientHello spans multiple packets. Returns None if parsing fails.

diff --git a/pr/451/docs/noq_proto/struct.Endpoint.html b/pr/451/docs/noq_proto/struct.Endpoint.html index 5918ae37f..39052b217 100644 --- a/pr/451/docs/noq_proto/struct.Endpoint.html +++ b/pr/451/docs/noq_proto/struct.Endpoint.html @@ -1,7 +1,7 @@ -Endpoint in noq_proto - Rust

Endpoint

Struct Endpoint 

Source
pub struct Endpoint { /* private fields */ }
Expand description

The main entry point to the library

+Endpoint in noq_proto - Rust

Endpoint

Struct Endpoint 

Source
pub struct Endpoint { /* private fields */ }
Expand description

The main entry point to the library

This object performs no I/O whatsoever. Instead, it consumes incoming packets and connection-generated events via handle and handle_event.

-

Implementations§

Source§

impl Endpoint

Source

pub fn new( +

Implementations§

Source§

impl Endpoint

Source

pub fn new( config: Arc<EndpointConfig>, server_config: Option<Arc<ServerConfig>>, allow_mtud: bool, @@ -10,13 +10,13 @@ connection-generated events via handle and handle_eventIPV6_DONTFRAG socket option.

Source

pub fn set_server_config(&mut self, server_config: Option<Arc<ServerConfig>>)

Replace the server configuration, affecting new incoming connections only

-
Source

pub fn handle_event( +

Source

pub fn handle_event( &mut self, ch: ConnectionHandle, event: EndpointEvent, ) -> Option<ConnectionEvent>

Process EndpointEvents emitted from related Connections

In turn, processing this event may return a ConnectionEvent for the same Connection.

-
Source

pub fn handle( +

Source

pub fn handle( &mut self, now: Instant, network_path: FourTuple, @@ -24,35 +24,35 @@ achieved via e.g. the IPV6_DONTFRAG socket option.

data: BytesMut, buf: &mut Vec<u8>, ) -> Option<DatagramEvent>

Process an incoming UDP datagram

-
Source

pub fn connect( +

Source

pub fn connect( &mut self, now: Instant, config: ClientConfig, remote: SocketAddr, server_name: &str, ) -> Result<(ConnectionHandle, Connection), ConnectError>

Initiate a connection

-
Source

pub fn accept( +

Source

pub fn accept( &mut self, incoming: Incoming, now: Instant, buf: &mut Vec<u8>, server_config: Option<Arc<ServerConfig>>, ) -> Result<(ConnectionHandle, Connection), Box<AcceptError>>

Attempt to accept this incoming connection (an error may still occur)

-
Source

pub fn refuse(&mut self, incoming: Incoming, buf: &mut Vec<u8>) -> Transmit

Reject this incoming connection attempt

-
Source

pub fn retry( +

Source

pub fn refuse(&mut self, incoming: Incoming, buf: &mut Vec<u8>) -> Transmit

Reject this incoming connection attempt

+
Source

pub fn retry( &mut self, incoming: Incoming, buf: &mut Vec<u8>, ) -> Result<Transmit, RetryError>

Respond with a retry packet, requiring the client to retry with address validation

Errors if incoming.may_retry() is false.

-
Source

pub fn ignore(&mut self, incoming: Incoming)

Ignore this incoming connection attempt, not sending any packet in response

+
Source

pub fn ignore(&mut self, incoming: Incoming)

Ignore this incoming connection attempt, not sending any packet in response

Doing this actively, rather than merely dropping the Incoming, is necessary to prevent memory leaks due to state within Endpoint tracking the incoming connection.

-
Source

pub fn config(&self) -> &EndpointConfig

Access the configuration used by this endpoint

-
Source

pub fn open_connections(&self) -> usize

Number of connections that are currently open

-
Source

pub fn incoming_buffer_bytes(&self) -> u64

Counter for the number of bytes currently used +

Source

pub fn config(&self) -> &EndpointConfig

Access the configuration used by this endpoint

+
Source

pub fn open_connections(&self) -> usize

Number of connections that are currently open

+
Source

pub fn incoming_buffer_bytes(&self) -> u64

Counter for the number of bytes currently used in the buffers for Initial and 0-RTT messages for pending incoming connections

-

Trait Implementations§

Source§

impl Debug for Endpoint

Source§

fn fmt(&self, fmt: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Endpoint

Source§

fn fmt(&self, fmt: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/451/docs/noq_proto/struct.EndpointConfig.html b/pr/451/docs/noq_proto/struct.EndpointConfig.html index 48ef8058f..9612d9eb8 100644 --- a/pr/451/docs/noq_proto/struct.EndpointConfig.html +++ b/pr/451/docs/noq_proto/struct.EndpointConfig.html @@ -1,4 +1,4 @@ -EndpointConfig in noq_proto - Rust

EndpointConfig

Struct EndpointConfig 

Source
pub struct EndpointConfig { /* private fields */ }
Expand description

Global configuration for the endpoint, affecting all connections

+EndpointConfig in noq_proto - Rust

EndpointConfig

Struct EndpointConfig 

Source
pub struct EndpointConfig { /* private fields */ }
Expand description

Global configuration for the endpoint, affecting all connections

Default values should be suitable for most internet applications.

Implementations§

Source§

impl EndpointConfig

Source

pub fn new(reset_key: Arc<dyn HmacKey>) -> Self

Create a default config with a particular reset_key

Source

pub fn cid_generator( diff --git a/pr/451/docs/noq_proto/struct.EndpointEvent.html b/pr/451/docs/noq_proto/struct.EndpointEvent.html index 88f1ee3a0..983ff3528 100644 --- a/pr/451/docs/noq_proto/struct.EndpointEvent.html +++ b/pr/451/docs/noq_proto/struct.EndpointEvent.html @@ -1,9 +1,10 @@ -EndpointEvent in noq_proto - Rust

EndpointEvent

Struct EndpointEvent 

Source
pub struct EndpointEvent(/* private fields */);
Expand description

Events sent from a Connection to an Endpoint

-

Implementations§

Source§

impl EndpointEvent

Source

pub fn drained() -> Self

Construct an event that indicating that a Connection will no longer emit events

+EndpointEvent in noq_proto - Rust

EndpointEvent

Struct EndpointEvent 

Source
pub struct EndpointEvent(/* private fields */);
Expand description

Events sent from a Connection to an Endpoint

+

Implementations§

Source§

impl EndpointEvent

Source

pub fn drained() -> Self

Construct an event that indicating that a Connection will no longer emit events

Useful for notifying an Endpoint that a Connection has been destroyed outside of the usual state machine flow, e.g. when being dropped by the user.

Source

pub fn is_drained(&self) -> bool

Determine whether this is the last event a Connection will emit

Useful for determining when connection-related event loop state can be freed.

+
Source

pub fn is_draining(&self) -> bool

Whether this is the event is the event indicating the start of the draining period.

Trait Implementations§

Source§

impl Debug for EndpointEvent

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq_proto/struct.FixedLengthConnectionIdParser.html b/pr/451/docs/noq_proto/struct.FixedLengthConnectionIdParser.html index c22ffd7ec..56de6de98 100644 --- a/pr/451/docs/noq_proto/struct.FixedLengthConnectionIdParser.html +++ b/pr/451/docs/noq_proto/struct.FixedLengthConnectionIdParser.html @@ -1,4 +1,4 @@ -FixedLengthConnectionIdParser in noq_proto - Rust

FixedLengthConnectionIdParser

Struct FixedLengthConnectionIdParser 

Source
pub struct FixedLengthConnectionIdParser { /* private fields */ }
Expand description

A ConnectionIdParser implementation that assumes the connection ID is of fixed length

+FixedLengthConnectionIdParser in noq_proto - Rust

FixedLengthConnectionIdParser

Struct FixedLengthConnectionIdParser 

Source
pub struct FixedLengthConnectionIdParser { /* private fields */ }
Expand description

A ConnectionIdParser implementation that assumes the connection ID is of fixed length

Implementations§

Source§

impl FixedLengthConnectionIdParser

Source

pub fn new(expected_len: usize) -> Self

Create a new instance of FixedLengthConnectionIdParser

Trait Implementations§

Source§

impl ConnectionIdParser for FixedLengthConnectionIdParser

Source§

fn parse(&self, buffer: &mut dyn Buf) -> Result<ConnectionId, PacketDecodeError>

Parse a connection id from given buffer

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where diff --git a/pr/451/docs/noq_proto/struct.FourTuple.html b/pr/451/docs/noq_proto/struct.FourTuple.html index 2182e3ce2..4004d84db 100644 --- a/pr/451/docs/noq_proto/struct.FourTuple.html +++ b/pr/451/docs/noq_proto/struct.FourTuple.html @@ -1,32 +1,17 @@ -FourTuple in noq_proto - Rust

FourTuple

Struct FourTuple 

Source
pub struct FourTuple { /* private fields */ }
Expand description

Identifies a network path by the combination of remote and local addresses

+FourTuple in noq_proto - Rust

FourTuple

Struct FourTuple 

Source
pub struct FourTuple { /* private fields */ }
Expand description

Identifies a network path by the combination of remote and local addresses

Including the local ensures good behavior when the host has multiple IP addresses on the same subnet and zero-length connection IDs are in use or when multipath is enabled and multiple paths exist with the same remote, but different local IP interfaces.

-

Implementations§

Source§

impl FourTuple

Source

pub fn new(remote: SocketAddr, local_ip: Option<IpAddr>) -> Self

Creates a new FourTuple.

-
Source

pub fn from_remote(remote: SocketAddr) -> Self

Creates a new FourTuple without a known local address.

-
Source

pub fn remote(&self) -> SocketAddr

Returns the remote address of the network path.

-
Source

pub fn local_ip(&self) -> Option<IpAddr>

Returns the local address of the network path.

-
Source

pub fn is_probably_same_path(&self, other: &Self) -> bool

Returns whether we think the other address probably represents the same path -as ours.

-

If we have a local IP set, then we’re exact and only match if the 4-tuples are -exactly equal. -If we don’t have a local IP set, then we only check the remote addresses for equality.

-

Note that because of this, the following calls might differ:

-
    -
  • a.is_probably_same_path(b)
  • -
  • b.is_probably_same_path(a)
  • -
-
Source

pub fn update_local_if_same_remote(&mut self, other: &Self) -> bool

Updates this tuple’s local address iff

-
    -
  • it was unset before,
  • -
  • the other tuple has the same remote, and
  • -
  • the other tuple has a local address set.
  • -
-

Returns whether this and the other remote are now fully equal.

-

Trait Implementations§

Source§

impl Clone for FourTuple

Source§

fn clone(&self) -> FourTuple

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FourTuple

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for FourTuple

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Hash for FourTuple

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where +

FourTuple implements From<SocketAddr>, which expands to Self::from_remote.

+

Implementations§

Source§

impl FourTuple

Source

pub fn new(remote: SocketAddr, local_ip: Option<IpAddr>) -> Self

Creates a new FourTuple.

+
Source

pub fn from_remote(remote: SocketAddr) -> Self

Creates a new FourTuple without a known local address.

+
Source

pub fn remote(&self) -> SocketAddr

Returns the remote address of the network path.

+
Source

pub fn local_ip(&self) -> Option<IpAddr>

Returns the local address of the network path.

+

Trait Implementations§

Source§

impl Clone for FourTuple

Source§

fn clone(&self) -> FourTuple

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FourTuple

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for FourTuple

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<SocketAddr> for FourTuple

Source§

fn from(value: SocketAddr) -> Self

Converts to this type from the input type.
Source§

impl Hash for FourTuple

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for FourTuple

Source§

fn eq(&self, other: &FourTuple) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for FourTuple

Source§

impl Eq for FourTuple

Source§

impl StructuralPartialEq for FourTuple

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for FourTuple

Source§

fn eq(&self, other: &FourTuple) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for FourTuple

Source§

impl Eq for FourTuple

Source§

impl StructuralPartialEq for FourTuple

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where @@ -34,7 +19,7 @@ and should not be overridden without very good reason.

Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, - K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

+ K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where diff --git a/pr/451/docs/noq_proto/struct.FrameStats.html b/pr/451/docs/noq_proto/struct.FrameStats.html index 2d3075205..2c2696704 100644 --- a/pr/451/docs/noq_proto/struct.FrameStats.html +++ b/pr/451/docs/noq_proto/struct.FrameStats.html @@ -1,4 +1,4 @@ -FrameStats in noq_proto - Rust

FrameStats

Struct FrameStats 

Source
#[non_exhaustive]
pub struct FrameStats {
Show 37 fields +FrameStats in noq_proto - Rust

FrameStats

Struct FrameStats 

Source
#[non_exhaustive]
pub struct FrameStats {
Show 37 fields pub acks: u64, pub path_acks: u64, pub ack_frequency: u64, @@ -37,8 +37,8 @@ pub reach_out: u64, pub remove_address: u64,
}
Expand description

Number of frames transmitted or received of each frame type.

-

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§acks: u64§path_acks: u64§ack_frequency: u64§crypto: u64§connection_close: u64§data_blocked: u64§datagram: u64§handshake_done: u8§immediate_ack: u64§max_data: u64§max_stream_data: u64§max_streams_bidi: u64§max_streams_uni: u64§new_connection_id: u64§path_new_connection_id: u64§new_token: u64§path_challenge: u64§path_response: u64§ping: u64§reset_stream: u64§retire_connection_id: u64§path_retire_connection_id: u64§stream_data_blocked: u64§streams_blocked_bidi: u64§streams_blocked_uni: u64§stop_sending: u64§stream: u64§observed_addr: u64§path_abandon: u64§path_status_available: u64§path_status_backup: u64§max_path_id: u64§paths_blocked: u64§path_cids_blocked: u64§add_address: u64§reach_out: u64§remove_address: u64

Trait Implementations§

Source§

impl Add for FrameStats

Source§

type Output = FrameStats

The resulting type after applying the + operator.
Source§

fn add(self, __rhs: Self) -> Self::Output

Performs the + operation. Read more
Source§

impl AddAssign for FrameStats

Source§

fn add_assign(&mut self, __rhs: Self)

Performs the += operation. Read more
Source§

impl Clone for FrameStats

Source§

fn clone(&self) -> FrameStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FrameStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for FrameStats

Source§

fn default() -> FrameStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for FrameStats

Source§

fn eq(&self, other: &FrameStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for FrameStats

Source§

impl Eq for FrameStats

Source§

impl StructuralPartialEq for FrameStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§acks: u64§path_acks: u64§ack_frequency: u64§crypto: u64§connection_close: u64§data_blocked: u64§datagram: u64§handshake_done: u8§immediate_ack: u64§max_data: u64§max_stream_data: u64§max_streams_bidi: u64§max_streams_uni: u64§new_connection_id: u64§path_new_connection_id: u64§new_token: u64§path_challenge: u64§path_response: u64§ping: u64§reset_stream: u64§retire_connection_id: u64§path_retire_connection_id: u64§stream_data_blocked: u64§streams_blocked_bidi: u64§streams_blocked_uni: u64§stop_sending: u64§stream: u64§observed_addr: u64§path_abandon: u64§path_status_available: u64§path_status_backup: u64§max_path_id: u64§paths_blocked: u64§path_cids_blocked: u64§add_address: u64§reach_out: u64§remove_address: u64

Trait Implementations§

Source§

impl Add for FrameStats

Source§

type Output = FrameStats

The resulting type after applying the + operator.
Source§

fn add(self, __rhs: Self) -> Self::Output

Performs the + operation. Read more
Source§

impl AddAssign for FrameStats

Source§

fn add_assign(&mut self, __rhs: Self)

Performs the += operation. Read more
Source§

impl Clone for FrameStats

Source§

fn clone(&self) -> FrameStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FrameStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for FrameStats

Source§

fn default() -> FrameStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for FrameStats

Source§

fn eq(&self, other: &FrameStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for FrameStats

Source§

impl Eq for FrameStats

Source§

impl StructuralPartialEq for FrameStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.HashedConnectionIdGenerator.html b/pr/451/docs/noq_proto/struct.HashedConnectionIdGenerator.html index cabe55e59..ae93df176 100644 --- a/pr/451/docs/noq_proto/struct.HashedConnectionIdGenerator.html +++ b/pr/451/docs/noq_proto/struct.HashedConnectionIdGenerator.html @@ -1,4 +1,4 @@ -HashedConnectionIdGenerator in noq_proto - Rust

HashedConnectionIdGenerator

Struct HashedConnectionIdGenerator 

Source
pub struct HashedConnectionIdGenerator { /* private fields */ }
Expand description

Generates 8-byte connection IDs that can be efficiently +HashedConnectionIdGenerator in noq_proto - Rust

HashedConnectionIdGenerator

Struct HashedConnectionIdGenerator 

Source
pub struct HashedConnectionIdGenerator { /* private fields */ }
Expand description

Generates 8-byte connection IDs that can be efficiently validated

This generator uses a non-cryptographic hash and can therefore still be spoofed, but nonetheless helps prevents noq from responding to non-QUIC packets at very low cost.

diff --git a/pr/451/docs/noq_proto/struct.IdleTimeout.html b/pr/451/docs/noq_proto/struct.IdleTimeout.html index ca46a047f..c0894e82c 100644 --- a/pr/451/docs/noq_proto/struct.IdleTimeout.html +++ b/pr/451/docs/noq_proto/struct.IdleTimeout.html @@ -1,4 +1,4 @@ -IdleTimeout in noq_proto - Rust

IdleTimeout

Struct IdleTimeout 

Source
pub struct IdleTimeout(/* private fields */);
Expand description

Maximum duration of inactivity to accept before timing out the connection

+IdleTimeout in noq_proto - Rust

IdleTimeout

Struct IdleTimeout 

Source
pub struct IdleTimeout(/* private fields */);
Expand description

Maximum duration of inactivity to accept before timing out the connection

This wraps an underlying VarInt, representing the duration in milliseconds. Values can be constructed by converting directly from VarInt, or using TryFrom<Duration>.

@@ -6,16 +6,16 @@ constructed by converting directly from VarInt, or using TryF let timeout = IdleTimeout::from(VarInt::from_u32(10_000)); // Try to convert a `Duration` into a `VarInt`-encoded timeout -let timeout = IdleTimeout::try_from(Duration::from_secs(10))?;

Trait Implementations§

Source§

impl Clone for IdleTimeout

Source§

fn clone(&self) -> IdleTimeout

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for IdleTimeout

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for IdleTimeout

Source§

fn default() -> IdleTimeout

Returns the “default value” for a type. Read more
Source§

impl From<VarInt> for IdleTimeout

Source§

fn from(inner: VarInt) -> Self

Converts to this type from the input type.
Source§

impl Hash for IdleTimeout

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where +let timeout = IdleTimeout::try_from(Duration::from_secs(10))?;

Trait Implementations§

Source§

impl Clone for IdleTimeout

Source§

fn clone(&self) -> IdleTimeout

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for IdleTimeout

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for IdleTimeout

Source§

fn default() -> IdleTimeout

Returns the “default value” for a type. Read more
Source§

impl From<VarInt> for IdleTimeout

Source§

fn from(inner: VarInt) -> Self

Converts to this type from the input type.
Source§

impl Hash for IdleTimeout

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for IdleTimeout

Source§

fn cmp(&self, other: &IdleTimeout) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for IdleTimeout

Source§

fn cmp(&self, other: &IdleTimeout) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for IdleTimeout

Source§

fn eq(&self, other: &IdleTimeout) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for IdleTimeout

Source§

fn partial_cmp(&self, other: &IdleTimeout) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for IdleTimeout

Source§

fn eq(&self, other: &IdleTimeout) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for IdleTimeout

Source§

fn partial_cmp(&self, other: &IdleTimeout) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl TryFrom<Duration> for IdleTimeout

Source§

type Error = VarIntBoundsExceeded

The type returned in the event of a conversion error.
Source§

fn try_from(timeout: Duration) -> Result<Self, Self::Error>

Performs the conversion.
Source§

impl Copy for IdleTimeout

Source§

impl Eq for IdleTimeout

Source§

impl StructuralPartialEq for IdleTimeout

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl TryFrom<Duration> for IdleTimeout

Source§

type Error = VarIntBoundsExceeded

The type returned in the event of a conversion error.
Source§

fn try_from(timeout: Duration) -> Result<Self, Self::Error>

Performs the conversion.
Source§

impl Copy for IdleTimeout

Source§

impl Eq for IdleTimeout

Source§

impl StructuralPartialEq for IdleTimeout

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.Incoming.html b/pr/451/docs/noq_proto/struct.Incoming.html index fe376fcd5..e3cdcd620 100644 --- a/pr/451/docs/noq_proto/struct.Incoming.html +++ b/pr/451/docs/noq_proto/struct.Incoming.html @@ -1,19 +1,19 @@ -Incoming in noq_proto - Rust

Incoming

Struct Incoming 

Source
pub struct Incoming { /* private fields */ }
Expand description

An incoming connection for which the server has not yet begun its part of the handshake.

-

Implementations§

Source§

impl Incoming

Source

pub fn local_ip(&self) -> Option<IpAddr>

The local IP address which was used when the peer established the connection

-
Source

pub fn remote_address(&self) -> SocketAddr

The peer’s UDP address

-
Source

pub fn remote_address_validated(&self) -> bool

Whether the socket address that is initiating this connection has been validated

+Incoming in noq_proto - Rust

Incoming

Struct Incoming 

Source
pub struct Incoming { /* private fields */ }
Expand description

An incoming connection for which the server has not yet begun its part of the handshake.

+

Implementations§

Source§

impl Incoming

Source

pub fn local_ip(&self) -> Option<IpAddr>

The local IP address which was used when the peer established the connection

+
Source

pub fn remote_address(&self) -> SocketAddr

The peer’s UDP address

+
Source

pub fn remote_address_validated(&self) -> bool

Whether the socket address that is initiating this connection has been validated

This means that the sender of the initial packet has proved that they can receive traffic sent to self.remote_address().

If self.remote_address_validated() is false, self.may_retry() is guaranteed to be true. The inverse is not guaranteed.

-
Source

pub fn may_retry(&self) -> bool

Whether it is legal to respond with a retry packet

+
Source

pub fn may_retry(&self) -> bool

Whether it is legal to respond with a retry packet

If self.remote_address_validated() is false, self.may_retry() is guaranteed to be true. The inverse is not guaranteed.

-
Source

pub fn orig_dst_cid(&self) -> ConnectionId

The original destination connection ID sent by the client

-
Source

pub fn decrypt(&self) -> Option<DecryptedInitial>

Decrypt the Initial packet payload

+
Source

pub fn orig_dst_cid(&self) -> ConnectionId

The original destination connection ID sent by the client

+
Source

pub fn decrypt(&self) -> Option<DecryptedInitial>

Decrypt the Initial packet payload

This clones and decrypts the packet payload (~1200 bytes). Can be used to extract information from the TLS ClientHello without completing the handshake.

-

Trait Implementations§

Source§

impl Debug for Incoming

Source§

fn fmt(&self, __derive_more_f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Debug for Incoming

Source§

fn fmt(&self, __derive_more_f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/451/docs/noq_proto/struct.IncomingAlpns.html b/pr/451/docs/noq_proto/struct.IncomingAlpns.html index 94b368ae4..6de4efade 100644 --- a/pr/451/docs/noq_proto/struct.IncomingAlpns.html +++ b/pr/451/docs/noq_proto/struct.IncomingAlpns.html @@ -1,7 +1,7 @@ -IncomingAlpns in noq_proto - Rust

IncomingAlpns

Struct IncomingAlpns 

Source
pub struct IncomingAlpns { /* private fields */ }
Expand description

Iterator over ALPN protocol names from a TLS ClientHello

+IncomingAlpns in noq_proto - Rust

IncomingAlpns

Struct IncomingAlpns 

Source
pub struct IncomingAlpns { /* private fields */ }
Expand description

Iterator over ALPN protocol names from a TLS ClientHello

Yields protocol names as [Bytes] slices. On the common fast path (single CRYPTO frame), the only allocation is the payload clone for decryption.

-

Trait Implementations§

Source§

impl Iterator for IncomingAlpns

Source§

type Item = Result<Bytes, UnexpectedEnd>

The type of the elements being iterated over.
Source§

fn next(&mut self) -> Option<Self::Item>

Advances the iterator and returns the next value. Read more
Source§

fn next_chunk<const N: usize>( +

Trait Implementations§

Source§

impl Iterator for IncomingAlpns

Source§

type Item = Result<Bytes, UnexpectedEnd>

The type of the elements being iterated over.
Source§

fn next(&mut self) -> Option<Self::Item>

Advances the iterator and returns the next value. Read more
Source§

fn next_chunk<const N: usize>( &mut self, ) -> Result<[Self::Item; N], IntoIter<Self::Item, N>>
where Self: Sized,

🔬This is a nightly-only experimental API. (iter_next_chunk)
Advances the iterator and returns an array containing the next N values. Read more
1.0.0 · Source§

fn size_hint(&self) -> (usize, Option<usize>)

Returns the bounds on the remaining length of the iterator. Read more
1.0.0 · Source§

fn count(self) -> usize
where diff --git a/pr/451/docs/noq_proto/struct.InvalidCid.html b/pr/451/docs/noq_proto/struct.InvalidCid.html index 7e2d5aa1b..7d8a2a904 100644 --- a/pr/451/docs/noq_proto/struct.InvalidCid.html +++ b/pr/451/docs/noq_proto/struct.InvalidCid.html @@ -1,4 +1,4 @@ -InvalidCid in noq_proto - Rust

InvalidCid

Struct InvalidCid 

Source
pub struct InvalidCid;
Expand description

The connection ID was not recognized by the ConnectionIdGenerator

+InvalidCid in noq_proto - Rust

InvalidCid

Struct InvalidCid 

Source
pub struct InvalidCid;
Expand description

The connection ID was not recognized by the ConnectionIdGenerator

Trait Implementations§

Source§

impl Clone for InvalidCid

Source§

fn clone(&self) -> InvalidCid

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for InvalidCid

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Copy for InvalidCid

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq_proto/struct.InvalidFrameId.html b/pr/451/docs/noq_proto/struct.InvalidFrameId.html index a234efae8..c0eab1b57 100644 --- a/pr/451/docs/noq_proto/struct.InvalidFrameId.html +++ b/pr/451/docs/noq_proto/struct.InvalidFrameId.html @@ -1,4 +1,4 @@ -InvalidFrameId in noq_proto - Rust

InvalidFrameId

Struct InvalidFrameId 

Source
pub struct InvalidFrameId(/* private fields */);
Expand description

Encounter a frame ID that was not valid.

+InvalidFrameId in noq_proto - Rust

InvalidFrameId

Struct InvalidFrameId 

Source
pub struct InvalidFrameId(/* private fields */);
Expand description

Encounter a frame ID that was not valid.

Trait Implementations§

Source§

impl Debug for InvalidFrameId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for InvalidFrameId

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for InvalidFrameId

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl PartialEq for InvalidFrameId

Source§

fn eq(&self, other: &InvalidFrameId) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Eq for InvalidFrameId

Source§

impl StructuralPartialEq for InvalidFrameId

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where diff --git a/pr/451/docs/noq_proto/struct.MtuDiscoveryConfig.html b/pr/451/docs/noq_proto/struct.MtuDiscoveryConfig.html index f5e56801a..a1d8767e1 100644 --- a/pr/451/docs/noq_proto/struct.MtuDiscoveryConfig.html +++ b/pr/451/docs/noq_proto/struct.MtuDiscoveryConfig.html @@ -1,4 +1,4 @@ -MtuDiscoveryConfig in noq_proto - Rust

MtuDiscoveryConfig

Struct MtuDiscoveryConfig 

Source
pub struct MtuDiscoveryConfig { /* private fields */ }
Expand description

Parameters governing MTU discovery.

+MtuDiscoveryConfig in noq_proto - Rust

MtuDiscoveryConfig

Struct MtuDiscoveryConfig 

Source
pub struct MtuDiscoveryConfig { /* private fields */ }
Expand description

Parameters governing MTU discovery.

§The why of MTU discovery

By design, QUIC ensures during the handshake that the network path between the client and the server is able to transmit unfragmented UDP packets with a body of 1200 bytes. In other words, @@ -42,22 +42,22 @@ timer that was set based on Implementations§

Source§

impl MtuDiscoveryConfig

Source

pub fn interval(&mut self, value: Duration) -> &mut Self

Specifies the time to wait after completing MTU discovery before starting a new MTU +

Implementations§

Source§

impl MtuDiscoveryConfig

Source

pub fn interval(&mut self, value: Duration) -> &mut Self

Specifies the time to wait after completing MTU discovery before starting a new MTU discovery run.

Defaults to 600 seconds, as recommended by RFC 8899.

-
Source

pub fn upper_bound(&mut self, value: u16) -> &mut Self

Specifies the upper bound to the max UDP payload size that MTU discovery will search for.

+
Source

pub fn upper_bound(&mut self, value: u16) -> &mut Self

Specifies the upper bound to the max UDP payload size that MTU discovery will search for.

Defaults to 1452, to stay within Ethernet’s MTU when using IPv4 and IPv6. The highest allowed value is 65527, which corresponds to the maximum permitted UDP payload on IPv6.

It is safe to use an arbitrarily high upper bound, regardless of the network path’s MTU. The only drawback is that MTU discovery might take more time to finish.

-
Source

pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self

Specifies the amount of time that MTU discovery should wait after a black hole was detected +

Source

pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self

Specifies the amount of time that MTU discovery should wait after a black hole was detected before running again. Defaults to one minute.

Black hole detection can be spuriously triggered in case of congestion, so it makes sense to try MTU discovery again after a short period of time.

-
Source

pub fn minimum_change(&mut self, value: u16) -> &mut Self

Specifies the minimum MTU change to stop the MTU discovery phase. +

Source

pub fn minimum_change(&mut self, value: u16) -> &mut Self

Specifies the minimum MTU change to stop the MTU discovery phase. Defaults to 20.

-

Trait Implementations§

Source§

impl Clone for MtuDiscoveryConfig

Source§

fn clone(&self) -> MtuDiscoveryConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MtuDiscoveryConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for MtuDiscoveryConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for MtuDiscoveryConfig

Source§

fn clone(&self) -> MtuDiscoveryConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MtuDiscoveryConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for MtuDiscoveryConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.MultipathNotNegotiated.html b/pr/451/docs/noq_proto/struct.MultipathNotNegotiated.html index dc82c3e51..f436589d7 100644 --- a/pr/451/docs/noq_proto/struct.MultipathNotNegotiated.html +++ b/pr/451/docs/noq_proto/struct.MultipathNotNegotiated.html @@ -1,5 +1,5 @@ -MultipathNotNegotiated in noq_proto - Rust

MultipathNotNegotiated

Struct MultipathNotNegotiated 

Source
pub struct MultipathNotNegotiated { /* private fields */ }
Expand description

Error when the multipath extension was not negotiated, but attempted to be used.

-

Trait Implementations§

Source§

impl Clone for MultipathNotNegotiated

Source§

fn clone(&self) -> MultipathNotNegotiated

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MultipathNotNegotiated

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for MultipathNotNegotiated

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for MultipathNotNegotiated

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl Copy for MultipathNotNegotiated

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +MultipathNotNegotiated in noq_proto - Rust

MultipathNotNegotiated

Struct MultipathNotNegotiated 

Source
pub struct MultipathNotNegotiated { /* private fields */ }
Expand description

Error when the multipath extension was not negotiated, but attempted to be used.

+

Trait Implementations§

Source§

impl Clone for MultipathNotNegotiated

Source§

fn clone(&self) -> MultipathNotNegotiated

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MultipathNotNegotiated

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for MultipathNotNegotiated

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for MultipathNotNegotiated

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl Copy for MultipathNotNegotiated

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.NoneTokenLog.html b/pr/451/docs/noq_proto/struct.NoneTokenLog.html index a10849572..98a62b9bd 100644 --- a/pr/451/docs/noq_proto/struct.NoneTokenLog.html +++ b/pr/451/docs/noq_proto/struct.NoneTokenLog.html @@ -1,4 +1,4 @@ -NoneTokenLog in noq_proto - Rust

NoneTokenLog

Struct NoneTokenLog 

Source
pub struct NoneTokenLog;
Expand description

Null implementation of TokenLog, which never accepts tokens

+NoneTokenLog in noq_proto - Rust

NoneTokenLog

Struct NoneTokenLog 

Source
pub struct NoneTokenLog;
Expand description

Null implementation of TokenLog, which never accepts tokens

Trait Implementations§

Source§

impl TokenLog for NoneTokenLog

Source§

fn check_and_insert( &self, _: u128, diff --git a/pr/451/docs/noq_proto/struct.NoneTokenStore.html b/pr/451/docs/noq_proto/struct.NoneTokenStore.html index 0b7459e4e..9339551ed 100644 --- a/pr/451/docs/noq_proto/struct.NoneTokenStore.html +++ b/pr/451/docs/noq_proto/struct.NoneTokenStore.html @@ -1,4 +1,4 @@ -NoneTokenStore in noq_proto - Rust

NoneTokenStore

Struct NoneTokenStore 

Source
pub struct NoneTokenStore;
Expand description

Null implementation of TokenStore, which does not store any tokens

+NoneTokenStore in noq_proto - Rust

NoneTokenStore

Struct NoneTokenStore 

Source
pub struct NoneTokenStore;
Expand description

Null implementation of TokenStore, which does not store any tokens

Trait Implementations§

Source§

impl TokenStore for NoneTokenStore

Source§

fn insert(&self, _: &str, _: Bytes)

Potentially store a token for later one-time use Read more
Source§

fn take(&self, _: &str) -> Option<Bytes>

Try to find and take a token that was stored with the given server name Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq_proto/struct.PartialDecode.html b/pr/451/docs/noq_proto/struct.PartialDecode.html index 7c94e1266..4b3dd5c76 100644 --- a/pr/451/docs/noq_proto/struct.PartialDecode.html +++ b/pr/451/docs/noq_proto/struct.PartialDecode.html @@ -1,4 +1,4 @@ -PartialDecode in noq_proto - Rust

PartialDecode

Struct PartialDecode 

Source
pub struct PartialDecode { /* private fields */ }
Expand description

Decodes a QUIC packet’s invariant header

+PartialDecode in noq_proto - Rust

PartialDecode

Struct PartialDecode 

Source
pub struct PartialDecode { /* private fields */ }
Expand description

Decodes a QUIC packet’s invariant header

Due to packet number encryption, it is impossible to fully decode a header (which includes a variable-length packet number) without crypto context. The crypto context (represented by the Crypto type in noq) is usually diff --git a/pr/451/docs/noq_proto/struct.PathId.html b/pr/451/docs/noq_proto/struct.PathId.html index 8a7b74c4c..b73b88114 100644 --- a/pr/451/docs/noq_proto/struct.PathId.html +++ b/pr/451/docs/noq_proto/struct.PathId.html @@ -1,20 +1,20 @@ -PathId in noq_proto - Rust

PathId

Struct PathId 

Source
pub struct PathId(/* private fields */);
Expand description

Id representing different paths when using multipath extension

-

Implementations§

Source§

impl PathId

Source

pub const MAX: Self

The maximum path ID allowed.

-
Source

pub const ZERO: Self

The 0 path id.

-
Source

pub fn saturating_add(self, rhs: impl Into<Self>) -> Self

Saturating integer addition. Computes self + rhs, saturating at the numeric bounds instead +PathId in noq_proto - Rust

PathId

Struct PathId 

Source
pub struct PathId(/* private fields */);
Expand description

Id representing different paths when using multipath extension

+

Implementations§

Source§

impl PathId

Source

pub const MAX: Self

The maximum path ID allowed.

+
Source

pub const ZERO: Self

The 0 path id.

+
Source

pub fn saturating_add(self, rhs: impl Into<Self>) -> Self

Saturating integer addition. Computes self + rhs, saturating at the numeric bounds instead of overflowing.

-
Source

pub fn saturating_sub(self, rhs: impl Into<Self>) -> Self

Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds +

Source

pub fn saturating_sub(self, rhs: impl Into<Self>) -> Self

Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds instead of overflowing.

-

Trait Implementations§

Source§

impl Clone for PathId

Source§

fn clone(&self) -> PathId

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Decodable for PathId

Source§

fn decode<B: Buf>(r: &mut B) -> Result<Self>

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Default for PathId

Source§

fn default() -> PathId

Returns the “default value” for a type. Read more
Source§

impl Display for PathId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Encodable for PathId

Source§

fn encode<B: BufMut>(&self, w: &mut B)

Append the encoding of self to the provided buffer.
Source§

impl<T: Into<u32>> From<T> for PathId

Source§

fn from(source: T) -> Self

Converts to this type from the input type.
Source§

impl Hash for PathId

Source§

fn hash<H: Hasher>(&self, state: &mut H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where +

Trait Implementations§

Source§

impl Clone for PathId

Source§

fn clone(&self) -> PathId

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Decodable for PathId

Source§

fn decode<B: Buf>(r: &mut B) -> Result<Self>

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Default for PathId

Source§

fn default() -> PathId

Returns the “default value” for a type. Read more
Source§

impl Display for PathId

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Encodable for PathId

Source§

fn encode<B: BufMut>(&self, w: &mut B)

Append the encoding of self to the provided buffer.
Source§

impl<T: Into<u32>> From<T> for PathId

Source§

fn from(source: T) -> Self

Converts to this type from the input type.
Source§

impl Hash for PathId

Source§

fn hash<H: Hasher>(&self, state: &mut H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, - Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for PathId

Source§

fn cmp(&self, other: &PathId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for PathId

Source§

fn cmp(&self, other: &PathId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where - Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for PathId

Source§

fn eq(&self, other: &PathId) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl PartialOrd for PathId

Source§

fn partial_cmp(&self, other: &PathId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the + Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl PartialEq for PathId

Source§

fn eq(&self, other: &PathId) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl PartialOrd for PathId

Source§

fn partial_cmp(&self, other: &PathId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by -the >= operator. Read more
Source§

impl Copy for PathId

Source§

impl Eq for PathId

Source§

impl IdentityHashable for PathId

Source§

impl StructuralPartialEq for PathId

Auto Trait Implementations§

§

impl Freeze for PathId

§

impl RefUnwindSafe for PathId

§

impl Send for PathId

§

impl Sync for PathId

§

impl Unpin for PathId

§

impl UnwindSafe for PathId

Blanket Implementations§

Source§

impl<T> Any for T
where +the >= operator. Read more

Source§

impl Copy for PathId

Source§

impl Eq for PathId

Source§

impl StructuralPartialEq for PathId

Auto Trait Implementations§

§

impl Freeze for PathId

§

impl RefUnwindSafe for PathId

§

impl Send for PathId

§

impl Sync for PathId

§

impl Unpin for PathId

§

impl UnwindSafe for PathId

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.PathStats.html b/pr/451/docs/noq_proto/struct.PathStats.html index d4d27a8d3..3a678d992 100644 --- a/pr/451/docs/noq_proto/struct.PathStats.html +++ b/pr/451/docs/noq_proto/struct.PathStats.html @@ -1,4 +1,4 @@ -PathStats in noq_proto - Rust

PathStats

Struct PathStats 

Source
#[non_exhaustive]
pub struct PathStats {
Show 14 fields +PathStats in noq_proto - Rust

PathStats

Struct PathStats 

Source
#[non_exhaustive]
pub struct PathStats {
Show 14 fields pub rtt: Duration, pub udp_tx: UdpStats, pub udp_rx: UdpStats, @@ -30,8 +30,8 @@

These are not included in Self::lost_packets and Self::lost_bytes.

§black_holes_detected: u64

The number of times a black hole was detected in the path.

§current_mtu: u16

Largest UDP payload size the path currently supports.

-

Trait Implementations§

Source§

impl Add<PathStats> for ConnectionStats

Source§

type Output = ConnectionStats

The resulting type after applying the + operator.
Source§

fn add(self, rhs: PathStats) -> Self::Output

Performs the + operation. Read more
Source§

impl AddAssign<PathStats> for ConnectionStats

Source§

fn add_assign(&mut self, rhs: PathStats)

Performs the += operation. Read more
Source§

impl Clone for PathStats

Source§

fn clone(&self) -> PathStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PathStats

Source§

fn default() -> PathStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStats

Source§

fn eq(&self, other: &PathStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, -and should not be overridden without very good reason.
Source§

impl Copy for PathStats

Source§

impl Eq for PathStats

Source§

impl StructuralPartialEq for PathStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Add<PathStats> for ConnectionStats

Source§

type Output = ConnectionStats

The resulting type after applying the + operator.
Source§

fn add(self, rhs: PathStats) -> Self::Output

Performs the + operation. Read more
Source§

impl AddAssign<PathStats> for ConnectionStats

Source§

fn add_assign(&mut self, rhs: PathStats)

Performs the += operation. Read more
Source§

impl Clone for PathStats

Source§

fn clone(&self) -> PathStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for PathStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PathStats

Source§

fn default() -> PathStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for PathStats

Source§

fn eq(&self, other: &PathStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +and should not be overridden without very good reason.
Source§

impl Copy for PathStats

Source§

impl Eq for PathStats

Source§

impl StructuralPartialEq for PathStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.ProtectedInitialHeader.html b/pr/451/docs/noq_proto/struct.ProtectedInitialHeader.html index 6f3019e13..cc3713b75 100644 --- a/pr/451/docs/noq_proto/struct.ProtectedInitialHeader.html +++ b/pr/451/docs/noq_proto/struct.ProtectedInitialHeader.html @@ -1,4 +1,4 @@ -ProtectedInitialHeader in noq_proto - Rust

ProtectedInitialHeader

Struct ProtectedInitialHeader 

Source
pub struct ProtectedInitialHeader {
+ProtectedInitialHeader in noq_proto - Rust

ProtectedInitialHeader

Struct ProtectedInitialHeader 

Source
pub struct ProtectedInitialHeader {
     pub dst_cid: ConnectionId,
     pub src_cid: ConnectionId,
     pub token_pos: Range<usize>,
diff --git a/pr/451/docs/noq_proto/struct.QlogConfig.html b/pr/451/docs/noq_proto/struct.QlogConfig.html
index d4f085a03..c0f238672 100644
--- a/pr/451/docs/noq_proto/struct.QlogConfig.html
+++ b/pr/451/docs/noq_proto/struct.QlogConfig.html
@@ -1,4 +1,4 @@
-QlogConfig in noq_proto - Rust

QlogConfig

Struct QlogConfig 

Source
pub struct QlogConfig { /* private fields */ }
Expand description

Configuration for qlog trace logging.

+QlogConfig in noq_proto - Rust

QlogConfig

Struct QlogConfig 

Source
pub struct QlogConfig { /* private fields */ }
Expand description

Configuration for qlog trace logging.

This struct is returned from QlogFactory::for_connection if qlog logging should be enabled for a connection. It allows to set metadata for the qlog trace.

The trace will be written to the provided writer in the [JSON-SEQ format] defined in the qlog spec.

diff --git a/pr/451/docs/noq_proto/struct.QlogFileFactory.html b/pr/451/docs/noq_proto/struct.QlogFileFactory.html index 1a77e7ba8..a70933fe8 100644 --- a/pr/451/docs/noq_proto/struct.QlogFileFactory.html +++ b/pr/451/docs/noq_proto/struct.QlogFileFactory.html @@ -1,4 +1,4 @@ -QlogFileFactory in noq_proto - Rust

QlogFileFactory

Struct QlogFileFactory 

Source
pub struct QlogFileFactory { /* private fields */ }
Expand description

Enables writing qlog traces to a directory.

+QlogFileFactory in noq_proto - Rust

QlogFileFactory

Struct QlogFileFactory 

Source
pub struct QlogFileFactory { /* private fields */ }
Expand description

Enables writing qlog traces to a directory.

Implementations§

Source§

impl QlogFileFactory

Source

pub fn new(dir: PathBuf) -> Self

Creates a new qlog factory that writes files into the specified directory.

Source

pub fn from_env() -> Self

Creates a new qlog factory that writes files into QLOGDIR, if set.

If the environment variable QLOGDIR is set, qlog traces for all connections handled diff --git a/pr/451/docs/noq_proto/struct.RandomConnectionIdGenerator.html b/pr/451/docs/noq_proto/struct.RandomConnectionIdGenerator.html index c6ead5e9f..a1caa1d95 100644 --- a/pr/451/docs/noq_proto/struct.RandomConnectionIdGenerator.html +++ b/pr/451/docs/noq_proto/struct.RandomConnectionIdGenerator.html @@ -1,4 +1,4 @@ -RandomConnectionIdGenerator in noq_proto - Rust

RandomConnectionIdGenerator

Struct RandomConnectionIdGenerator 

Source
pub struct RandomConnectionIdGenerator { /* private fields */ }
Expand description

Generates purely random connection IDs of a specified length

+RandomConnectionIdGenerator in noq_proto - Rust

RandomConnectionIdGenerator

Struct RandomConnectionIdGenerator 

Source
pub struct RandomConnectionIdGenerator { /* private fields */ }
Expand description

Generates purely random connection IDs of a specified length

Random CIDs can be smaller than those produced by HashedConnectionIdGenerator, but cannot be usefully validated.

Implementations§

Source§

impl RandomConnectionIdGenerator

Source

pub fn new(cid_len: usize) -> Self

Initialize Random CID generator with a fixed CID length

diff --git a/pr/451/docs/noq_proto/struct.RecvStream.html b/pr/451/docs/noq_proto/struct.RecvStream.html index b8f4ea9e1..55b348f78 100644 --- a/pr/451/docs/noq_proto/struct.RecvStream.html +++ b/pr/451/docs/noq_proto/struct.RecvStream.html @@ -1,5 +1,5 @@ -RecvStream in noq_proto - Rust

RecvStream

Struct RecvStream 

Source
pub struct RecvStream<'a> { /* private fields */ }
Expand description

Access to streams

-

Implementations§

Source§

impl RecvStream<'_>

Source

pub fn read(&mut self, ordered: bool) -> Result<Chunks<'_>, ReadableError>

Read from the given recv stream

+RecvStream in noq_proto - Rust

RecvStream

Struct RecvStream 

Source
pub struct RecvStream<'a> { /* private fields */ }
Expand description

Access to streams

+

Implementations§

Source§

impl RecvStream<'_>

Source

pub fn read(&mut self, ordered: bool) -> Result<Chunks<'_>, ReadableError>

Read from the given recv stream

max_length limits the maximum size of the returned Bytes value; passing usize::MAX will yield the best performance. ordered will make sure the returned chunk’s offset will have an offset exactly equal to the previously returned offset plus the previously returned @@ -12,13 +12,13 @@ improve performance when packet loss occurs and data cannot be retransmitted bef control window is filled. On any given stream, you can switch from ordered to unordered reads, but ordered reads on streams that have seen previous unordered reads will return ReadError::IllegalOrderedRead.

-
Source

pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Stop accepting data on the given receive stream

+
Source

pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Stop accepting data on the given receive stream

Discards unread data and notifies the peer to stop transmitting. Once stopped, further attempts to operate on a stream will yield ClosedStream errors.

-
Source

pub fn bytes_read(&self) -> Result<u64, ClosedStream>

Returns the number of bytes read from this stream.

+
Source

pub fn bytes_read(&self) -> Result<u64, ClosedStream>

Returns the number of bytes read from this stream.

This is the offset of the next byte to be read, i.e. the length of the contiguous prefix of the stream consumed by the application.

-
Source

pub fn received_reset(&mut self) -> Result<Option<VarInt>, ClosedStream>

Check whether this stream has been reset by the peer, returning the reset error code if so

+
Source

pub fn received_reset(&mut self) -> Result<Option<VarInt>, ClosedStream>

Check whether this stream has been reset by the peer, returning the reset error code if so

After returning Ok(Some(_)) once, stream state will be discarded and all future calls will return Err(ClosedStream).

Auto Trait Implementations§

§

impl<'a> Freeze for RecvStream<'a>

§

impl<'a> RefUnwindSafe for RecvStream<'a>

§

impl<'a> Send for RecvStream<'a>

§

impl<'a> Sync for RecvStream<'a>

§

impl<'a> Unpin for RecvStream<'a>

§

impl<'a> !UnwindSafe for RecvStream<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where diff --git a/pr/451/docs/noq_proto/struct.RetryError.html b/pr/451/docs/noq_proto/struct.RetryError.html index ab9b7fe1e..a5004ffa6 100644 --- a/pr/451/docs/noq_proto/struct.RetryError.html +++ b/pr/451/docs/noq_proto/struct.RetryError.html @@ -1,6 +1,6 @@ -RetryError in noq_proto - Rust

RetryError

Struct RetryError 

Source
pub struct RetryError(/* private fields */);
Expand description

Error for attempting to retry an Incoming which already bears a token from a previous retry

-

Implementations§

Trait Implementations§

Source§

impl Debug for RetryError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for RetryError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for RetryError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +RetryError in noq_proto - Rust

RetryError

Struct RetryError 

Source
pub struct RetryError(/* private fields */);
Expand description

Error for attempting to retry an Incoming which already bears a token from a previous retry

+

Implementations§

Trait Implementations§

Source§

impl Debug for RetryError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for RetryError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for RetryError

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

diff --git a/pr/451/docs/noq_proto/struct.RttEstimator.html b/pr/451/docs/noq_proto/struct.RttEstimator.html index d5f2287a6..6924f0cb8 100644 --- a/pr/451/docs/noq_proto/struct.RttEstimator.html +++ b/pr/451/docs/noq_proto/struct.RttEstimator.html @@ -1,10 +1,10 @@ -RttEstimator in noq_proto - Rust

RttEstimator

Struct RttEstimator 

Source
pub struct RttEstimator { /* private fields */ }
Expand description

RTT estimation for a particular network path

-

Implementations§

Source§

impl RttEstimator

Source

pub fn get(&self) -> Duration

The current best RTT estimation.

-
Source

pub fn conservative(&self) -> Duration

Conservative estimate of RTT

+RttEstimator in noq_proto - Rust

RttEstimator

Struct RttEstimator 

Source
pub struct RttEstimator { /* private fields */ }
Expand description

RTT estimation for a particular network path

+

Implementations§

Source§

impl RttEstimator

Source

pub fn get(&self) -> Duration

The current best RTT estimation.

+
Source

pub fn conservative(&self) -> Duration

Conservative estimate of RTT

Takes the maximum of smoothed and latest RTT, as recommended in 6.1.2 of the recovery spec (draft 29).

-
Source

pub fn min(&self) -> Duration

Minimum RTT registered so far for this estimator.

-

Trait Implementations§

Source§

impl Clone for RttEstimator

Source§

fn clone(&self) -> RttEstimator

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RttEstimator

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Copy for RttEstimator

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Source

pub fn min(&self) -> Duration

Minimum RTT registered so far for this estimator.

+

Trait Implementations§

Source§

impl Clone for RttEstimator

Source§

fn clone(&self) -> RttEstimator

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RttEstimator

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Copy for RttEstimator

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.SendStream.html b/pr/451/docs/noq_proto/struct.SendStream.html index a3200d250..584ba1367 100644 --- a/pr/451/docs/noq_proto/struct.SendStream.html +++ b/pr/451/docs/noq_proto/struct.SendStream.html @@ -1,7 +1,7 @@ -SendStream in noq_proto - Rust

SendStream

Struct SendStream 

Source
pub struct SendStream<'a> { /* private fields */ }
Expand description

Access to streams

-

Implementations§

Source§

impl<'a> SendStream<'a>

Source

pub fn write(&mut self, data: &[u8]) -> Result<usize, WriteError>

Send data on the given stream

+SendStream in noq_proto - Rust

SendStream

Struct SendStream 

Source
pub struct SendStream<'a> { /* private fields */ }
Expand description

Access to streams

+

Implementations§

Source§

impl<'a> SendStream<'a>

Source

pub fn write(&mut self, data: &[u8]) -> Result<usize, WriteError>

Send data on the given stream

Returns the number of bytes successfully written.

-
Source

pub fn write_chunks( +

Source

pub fn write_chunks( &mut self, data: &mut &mut [Bytes], ) -> Result<usize, WriteError>

Send data on the given stream

@@ -9,20 +9,20 @@ slice, removing all completely written chunks.

Note that this method might also write a partial chunk. In this case the chunk will be advanced and contain only non-written data after the call.

-
Source

pub fn stopped(&self) -> Result<Option<VarInt>, ClosedStream>

Check if this stream was stopped, get the reason if it was

-
Source

pub fn finish(&mut self) -> Result<(), FinishError>

Finish a send stream, signalling that no more data will be sent.

+
Source

pub fn stopped(&self) -> Result<Option<VarInt>, ClosedStream>

Check if this stream was stopped, get the reason if it was

+
Source

pub fn finish(&mut self) -> Result<(), FinishError>

Finish a send stream, signalling that no more data will be sent.

If this fails, no StreamEvent::Finished will be generated.

-
Source

pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Abandon transmitting data on a stream

+
Source

pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream>

Abandon transmitting data on a stream

§Panics
  • when applied to a receive stream
-
Source

pub fn set_priority(&mut self, priority: i32) -> Result<(), ClosedStream>

Set the priority of a stream

+
Source

pub fn set_priority(&mut self, priority: i32) -> Result<(), ClosedStream>

Set the priority of a stream

§Panics
  • when applied to a receive stream
-
Source

pub fn priority(&self) -> Result<i32, ClosedStream>

Get the priority of a stream

+
Source

pub fn priority(&self) -> Result<i32, ClosedStream>

Get the priority of a stream

§Panics
Source

pub fn stream_receive_window(&mut self, value: VarInt) -> &mut Self

Maximum number of bytes the peer may transmit without acknowledgement on any one stream before becoming blocked.

This should be set to at least the expected connection latency multiplied by the maximum desired throughput. Setting this smaller than receive_window helps ensure that a single stream doesn’t monopolize receive buffers, which may otherwise occur if the application chooses not to read from a large stream for a time while still requiring data on other streams.

-
Source

pub fn receive_window(&mut self, value: VarInt) -> &mut Self

Maximum number of bytes the peer may transmit across all streams of a connection before +

Source

pub fn receive_window(&mut self, value: VarInt) -> &mut Self

Maximum number of bytes the peer may transmit across all streams of a connection before becoming blocked.

This should be set to at least the expected connection latency multiplied by the maximum desired throughput. Larger values can be useful to allow maximum throughput within a stream while another is blocked.

-
Source

pub fn send_window(&mut self, value: u64) -> &mut Self

Maximum number of bytes to transmit to a peer without acknowledgment

+
Source

pub fn send_window(&mut self, value: u64) -> &mut Self

Maximum number of bytes to transmit to a peer without acknowledgment

Provides an upper bound on memory when communicating with peers that issue large amounts of flow control credit. Endpoints that wish to handle large numbers of connections robustly should take care to set this low enough to guarantee memory exhaustion does not occur if every connection uses the entire window.

-
Source

pub fn send_fairness(&mut self, value: bool) -> &mut Self

Whether to implement fair queuing for send streams having the same priority.

+
Source

pub fn send_fairness(&mut self, value: bool) -> &mut Self

Whether to implement fair queuing for send streams having the same priority.

When enabled, connections schedule data from outgoing streams having the same priority in a round-robin fashion. When disabled, streams are scheduled in the order they are written to.

Note that this only affects streams with the same priority. Higher priority streams always take precedence over lower priority streams.

Disabling fairness can reduce fragmentation and protocol overhead for workloads that use many small streams.

-
Source

pub fn packet_threshold(&mut self, value: u32) -> &mut Self

Maximum reordering in packet number space before FACK style loss detection considers a +

Source

pub fn packet_threshold(&mut self, value: u32) -> &mut Self

Maximum reordering in packet number space before FACK style loss detection considers a packet lost. Should not be less than 3, per RFC5681.

-
Source

pub fn time_threshold(&mut self, value: f32) -> &mut Self

Maximum reordering in time space before time based loss detection considers a packet lost, +

Source

pub fn time_threshold(&mut self, value: f32) -> &mut Self

Maximum reordering in time space before time based loss detection considers a packet lost, as a factor of RTT

-
Source

pub fn initial_rtt(&mut self, value: Duration) -> &mut Self

The RTT used before an RTT sample is taken

-
Source

pub fn initial_mtu(&mut self, value: u16) -> &mut Self

The initial value to be used as the maximum UDP payload size before running MTU discovery +

Source

pub fn initial_rtt(&mut self, value: Duration) -> &mut Self

The RTT used before an RTT sample is taken

+
Source

pub fn initial_mtu(&mut self, value: u16) -> &mut Self

The initial value to be used as the maximum UDP payload size before running MTU discovery (see TransportConfig::mtu_discovery_config).

Must be at least 1200, which is the default, and known to be safe for typical internet applications. Larger values are more efficient, but increase the risk of packet loss due to exceeding the network path’s IP MTU. If the provided value is higher than what the network path actually supports, packet loss will eventually trigger black hole detection and bring it down to TransportConfig::min_mtu.

-
Source

pub fn min_mtu(&mut self, value: u16) -> &mut Self

The maximum UDP payload size guaranteed to be supported by the network.

+
Source

pub fn min_mtu(&mut self, value: u16) -> &mut Self

The maximum UDP payload size guaranteed to be supported by the network.

Must be at least 1200, which is the default, and lower than or equal to TransportConfig::initial_mtu.

Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links @@ -71,19 +71,19 @@ catastrophic packet loss, without a possibility of repair. Prefer TransportConfig::initial_mtu together with TransportConfig::mtu_discovery_config to set a maximum UDP payload size that robustly adapts to the network.

-
Source

pub fn mtu_discovery_config( +

Source

pub fn mtu_discovery_config( &mut self, value: Option<MtuDiscoveryConfig>, ) -> &mut Self

Specifies the MTU discovery config (see MtuDiscoveryConfig for details).

Enabled by default.

-
Source

pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self

Pad UDP datagrams carrying application data to current maximum UDP payload size

+
Source

pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self

Pad UDP datagrams carrying application data to current maximum UDP payload size

Disabled by default. UDP datagrams containing loss probes are exempt from padding.

Enabling this helps mitigate traffic analysis by network observers, but it increases bandwidth usage. Without this mitigation precise plain text size of application datagrams as well as the total size of stream write bursts can be inferred by observers under certain conditions. This analysis requires either an uncongested connection or application datagrams too large to be coalesced.

-
Source

pub fn ack_frequency_config( +

Source

pub fn ack_frequency_config( &mut self, value: Option<AckFrequencyConfig>, ) -> &mut Self

Specifies the ACK frequency config (see AckFrequencyConfig for details)

@@ -92,19 +92,19 @@ frequency QUIC extension.

Defaults to None, which disables controlling the peer’s acknowledgement frequency. Even if set to None, the local side still supports the acknowledgement frequency QUIC extension and may use it in other ways.

-
Source

pub fn max_outgoing_bytes_per_second(&mut self, value: Option<u64>) -> &mut Self

Configures an outbound rate limit (in bytes per second) for each connection.

+
Source

pub fn max_outgoing_bytes_per_second(&mut self, value: Option<u64>) -> &mut Self

Configures an outbound rate limit (in bytes per second) for each connection.

Defaults to None, which disables rate limiting.

-
Source

pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self

Number of consecutive PTOs after which network is considered to be experiencing persistent congestion.

-
Source

pub fn keep_alive_interval(&mut self, value: Option<Duration>) -> &mut Self

Period of inactivity before sending a keep-alive packet

+
Source

pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self

Number of consecutive PTOs after which network is considered to be experiencing persistent congestion.

+
Source

pub fn keep_alive_interval(&mut self, value: Option<Duration>) -> &mut Self

Period of inactivity before sending a keep-alive packet

Keep-alive packets prevent an inactive but otherwise healthy connection from timing out.

None to disable, which is the default. Only one side of any given connection needs keep-alive enabled for the connection to be preserved. Must be set lower than the idle_timeout of both peers to be effective.

-
Source

pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self

Maximum quantity of out-of-order crypto layer data to buffer

-
Source

pub fn allow_spin(&mut self, value: bool) -> &mut Self

Whether the implementation is permitted to set the spin bit on this connection

+
Source

pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self

Maximum quantity of out-of-order crypto layer data to buffer

+
Source

pub fn allow_spin(&mut self, value: bool) -> &mut Self

Whether the implementation is permitted to set the spin bit on this connection

This allows passive observers to easily judge the round trip time of a connection, which can be useful for network administration but sacrifices a small amount of privacy.

-
Source

pub fn datagram_receive_buffer_size( +

Source

pub fn datagram_receive_buffer_size( &mut self, value: Option<usize>, ) -> &mut Self

Maximum number of incoming application datagram bytes to buffer, or None to disable @@ -112,12 +112,12 @@ incoming datagrams

The peer is forbidden to send single datagrams larger than this size. If the aggregate size of all datagrams that have been received from the peer but not consumed by the application exceeds this value, old datagrams are dropped until it is no longer exceeded.

-
Source

pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self

Maximum number of outgoing application datagram bytes to buffer

+
Source

pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self

Maximum number of outgoing application datagram bytes to buffer

While datagrams are sent ASAP, it is possible for an application to generate data faster than the link, or even the underlying hardware, can transmit them. This limits the amount of memory that may be consumed in that case. When the send buffer is full and a new datagram is sent, older datagrams are dropped until sufficient space is available.

-
Source

pub fn congestion_controller_factory( +

Source

pub fn congestion_controller_factory( &mut self, factory: Arc<dyn ControllerFactory + Send + Sync + 'static>, ) -> &mut Self

How to construct new congestion::Controllers

@@ -125,7 +125,7 @@ sent, older datagrams are dropped until sufficient space is available.

e.g. a congestion::NewRenoConfig.

§Example
let mut config = TransportConfig::default();
-config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));
Source

pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self

Whether to use “Generic Segmentation Offload” to accelerate transmits, when supported by the +config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default()));

Source

pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self

Whether to use “Generic Segmentation Offload” to accelerate transmits, when supported by the environment

Defaults to true.

GSO dramatically reduces CPU consumption when sending large numbers of packets with the same @@ -133,10 +133,10 @@ headers, such as when transmitting bulk data on a connection. However, it is not by all network interface drivers or packet inspection tools. noq-udp will attempt to disable GSO automatically when unavailable, but this can lead to spurious packet loss at startup, temporarily degrading performance.

-
Source

pub fn send_observed_address_reports(&mut self, enabled: bool) -> &mut Self

Whether to send observed address reports to peers.

+
Source

pub fn send_observed_address_reports(&mut self, enabled: bool) -> &mut Self

Whether to send observed address reports to peers.

This will aid peers in inferring their reachable address, which in most NATd networks will not be easily available to them.

-
Source

pub fn receive_observed_address_reports(&mut self, enabled: bool) -> &mut Self

Whether to receive observed address reports from other peers.

+
Source

pub fn receive_observed_address_reports(&mut self, enabled: bool) -> &mut Self

Whether to receive observed address reports from other peers.

Peers with the address discovery extension enabled that are willing to provide observed address reports will do so if this transport parameter is set. In general, observed address reports cannot be trusted. This, however, can aid the current endpoint in inferring its @@ -179,23 +179,33 @@ the Nat Traversal Extension for QUIC, see https://www.ietf.org/archive/id/draft-seemann-quic-nat-traversal-02.html

This implementation expects the multipath extension to be enabled as well. if not yet enabled via Self::max_concurrent_multipath_paths, then that setting is set to 8.

-
Source

pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self

Configures qlog capturing by setting a QlogFactory.

+
Source

pub fn server_handshake_migration(&mut self, allow_migration: bool) -> &mut Self

Sets whether the server is allowed to migrate once during the handshake.

+

Enabling this is not RFC9000 compliant.

+

Defaults to false.

+

Enabling this allows the server to migrate once during the handshake: it can send a +response from a different address than the client’s initial packet was sent to. Once +an authenticated Handshake packet is received the server can no longer migrate +during the handshake (or after the handshake if not other extension enables this).

+

This can be used to duplicate the client’s initial packet to multiple addresses for +the server and accept the fastest response. The server will discard all but the +first such initial, considering any remaining as duplicates.

+
Source

pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self

Configures qlog capturing by setting a QlogFactory.

This assigns a QlogFactory that produces qlog capture configurations for individual connections.

-
Source

pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self

Configures qlog capturing through the QLOGDIR environment variable.

+
Source

pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self

Configures qlog capturing through the QLOGDIR environment variable.

This uses QlogFileFactory::from_env to create a factory to write qlog traces into the directory set through the QLOGDIR environment variable.

If QLOGDIR is not set, no traces will be written. If QLOGDIR is set to a path that does not exist, it will be created.

The files will be prefixed with prefix.

-
Source

pub fn qlog_from_path( +

Source

pub fn qlog_from_path( &mut self, path: impl AsRef<Path>, prefix: &str, ) -> &mut Self

Configures qlog capturing into a directory.

This uses QlogFileFactory to create a factory to write qlog traces into the specified directory. The files will be prefixed with prefix.

-

Trait Implementations§

Source§

impl Clone for TransportConfig

Source§

fn clone(&self) -> TransportConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for TransportConfig

Source§

fn fmt(&self, fmt: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for TransportConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where +

Trait Implementations§

Source§

impl Clone for TransportConfig

Source§

fn clone(&self) -> TransportConfig

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for TransportConfig

Source§

fn fmt(&self, fmt: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for TransportConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where diff --git a/pr/451/docs/noq_proto/struct.TransportError.html b/pr/451/docs/noq_proto/struct.TransportError.html index 79bb2ba3c..25ec547c4 100644 --- a/pr/451/docs/noq_proto/struct.TransportError.html +++ b/pr/451/docs/noq_proto/struct.TransportError.html @@ -1,4 +1,4 @@ -TransportError in noq_proto - Rust

TransportError

Struct TransportError 

Source
#[non_exhaustive]
pub struct TransportError { +TransportError in noq_proto - Rust

TransportError

Struct TransportError 

Source
#[non_exhaustive]
pub struct TransportError { pub code: Code, pub frame: MaybeFrame, pub reason: String, @@ -11,7 +11,7 @@
§reason: String

Human-readable explanation of the reason

§crypto: Option<Arc<dyn Error + Send + Sync>>

An underlying crypto (e.g. TLS) layer error

Implementations§

Source§

impl Error

Source

pub fn new(code: Code, reason: String) -> Self

Construct an error with a code and a reason

-

Trait Implementations§

Source§

impl Clone for Error

Source§

fn clone(&self) -> Error

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for Error

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Error> for ConnectionClose

Source§

fn from(x: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for TransportError

Source§

fn from(e: Error) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Error

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, +

Trait Implementations§

Source§

impl Clone for Error

Source§

fn clone(&self) -> Error

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Error

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for Error

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Error> for ConnectionClose

Source§

fn from(x: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for ConnectionError

Source§

fn from(source: TransportError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for TransportError

Source§

fn from(e: Error) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Error

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Eq for Error

Auto Trait Implementations§

§

impl Freeze for Error

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where diff --git a/pr/451/docs/noq_proto/struct.TransportErrorCode.html b/pr/451/docs/noq_proto/struct.TransportErrorCode.html index d6b2a9bbc..2ea3ef344 100644 --- a/pr/451/docs/noq_proto/struct.TransportErrorCode.html +++ b/pr/451/docs/noq_proto/struct.TransportErrorCode.html @@ -1,4 +1,4 @@ -TransportErrorCode in noq_proto - Rust

TransportErrorCode

Struct TransportErrorCode 

Source
pub struct TransportErrorCode(/* private fields */);
Expand description

Transport-level error code

+TransportErrorCode in noq_proto - Rust

TransportErrorCode

Struct TransportErrorCode 

Source
pub struct TransportErrorCode(/* private fields */);
Expand description

Transport-level error code

Implementations§

Source§

impl Code

Source

pub fn crypto(code: u8) -> Self

Create QUIC error code from TLS alert code

Source§

impl Code

Source

pub const NO_ERROR: Self

the connection is being closed abruptly in the absence of any error

Source

pub const INTERNAL_ERROR: Self

the endpoint encountered an internal error and cannot continue with the connection

diff --git a/pr/451/docs/noq_proto/struct.UdpStats.html b/pr/451/docs/noq_proto/struct.UdpStats.html index 9b596b615..7b55abf75 100644 --- a/pr/451/docs/noq_proto/struct.UdpStats.html +++ b/pr/451/docs/noq_proto/struct.UdpStats.html @@ -1,4 +1,4 @@ -UdpStats in noq_proto - Rust

UdpStats

Struct UdpStats 

Source
#[non_exhaustive]
pub struct UdpStats { +UdpStats in noq_proto - Rust

UdpStats

Struct UdpStats 

Source
#[non_exhaustive]
pub struct UdpStats { pub datagrams: u64, pub bytes: u64, pub ios: u64, @@ -7,8 +7,8 @@ on a connection.

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§datagrams: u64

The number of UDP datagrams observed.

§bytes: u64

The total amount of bytes which have been transferred inside UDP datagrams.

-
§ios: u64

The number of I/O operations executed.

-

Can be less than datagrams when GSO, GRO, and/or batched system calls are in use.

+
§ios: u64
👎Deprecated since 1.0.2: IO counting can’t be meaningfully measured from this crate. See https://github.com/n0-computer/noq/issues/727

The number of I/O operations executed.

+

This can’t be measured from this crate and will always be 0

Trait Implementations§

Source§

impl Add for UdpStats

Source§

type Output = UdpStats

The resulting type after applying the + operator.
Source§

fn add(self, __rhs: Self) -> Self::Output

Performs the + operation. Read more
Source§

impl AddAssign for UdpStats

Source§

fn add_assign(&mut self, __rhs: Self)

Performs the += operation. Read more
Source§

impl Clone for UdpStats

Source§

fn clone(&self) -> UdpStats

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for UdpStats

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for UdpStats

Source§

fn default() -> UdpStats

Returns the “default value” for a type. Read more
Source§

impl PartialEq for UdpStats

Source§

fn eq(&self, other: &UdpStats) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for UdpStats

Source§

impl Eq for UdpStats

Source§

impl StructuralPartialEq for UdpStats

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where diff --git a/pr/451/docs/noq_proto/struct.ValidationTokenConfig.html b/pr/451/docs/noq_proto/struct.ValidationTokenConfig.html index 82ff44e81..9c86f3b32 100644 --- a/pr/451/docs/noq_proto/struct.ValidationTokenConfig.html +++ b/pr/451/docs/noq_proto/struct.ValidationTokenConfig.html @@ -1,4 +1,4 @@ -ValidationTokenConfig in noq_proto - Rust

ValidationTokenConfig

Struct ValidationTokenConfig 

Source
pub struct ValidationTokenConfig { /* private fields */ }
Expand description

Configuration for sending and handling validation tokens in incoming connections

+ValidationTokenConfig in noq_proto - Rust

ValidationTokenConfig

Struct ValidationTokenConfig 

Source
pub struct ValidationTokenConfig { /* private fields */ }
Expand description

Configuration for sending and handling validation tokens in incoming connections

Default values should be suitable for most internet applications.

§QUIC Tokens

The QUIC protocol defines a concept of “address validation”. Essentially, one side of a diff --git a/pr/451/docs/noq_proto/struct.VarInt.html b/pr/451/docs/noq_proto/struct.VarInt.html index 8204132a7..672d17770 100644 --- a/pr/451/docs/noq_proto/struct.VarInt.html +++ b/pr/451/docs/noq_proto/struct.VarInt.html @@ -1,4 +1,4 @@ -VarInt in noq_proto - Rust

VarInt

Struct VarInt 

Source
pub struct VarInt(/* private fields */);
Expand description

An integer less than 2^62

+VarInt in noq_proto - Rust

VarInt

Struct VarInt 

Source
pub struct VarInt(/* private fields */);
Expand description

An integer less than 2^62

Values of this type are suitable for encoding as QUIC variable-length integer.

Implementations§

Source§

impl VarInt

Source

pub const MAX: Self

The largest representable value

Source

pub const MAX_SIZE: usize = 8usize

The largest encoded value length

@@ -15,7 +15,7 @@ unstructured data. Read more
§

fn try_size_hint( depth: usize, ) -> Result<(usize, Option<usize>), MaxRecursionReached>

Get a size hint for how many bytes out of an Unstructured this type -needs to construct itself. Read more
Source§

impl Clone for VarInt

Source§

fn clone(&self) -> VarInt

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for VarInt

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Decodable for VarInt

Source§

fn decode<B: Buf>(r: &mut B) -> Result<Self>

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Default for VarInt

Source§

fn default() -> VarInt

Returns the “default value” for a type. Read more
Source§

impl Display for VarInt

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Encodable for VarInt

Source§

fn encode<B: BufMut>(&self, w: &mut B)

Append the encoding of self to the provided buffer.
Source§

impl From<Code> for VarInt

Source§

fn from(value: Code) -> Self

Converts to this type from the input type.
Source§

impl From<StreamId> for VarInt

Source§

fn from(x: StreamId) -> Self

Converts to this type from the input type.
Source§

impl From<VarInt> for Code

Source§

fn from(value: VarInt) -> Self

Converts to this type from the input type.
Source§

impl From<VarInt> for IdleTimeout

Source§

fn from(inner: VarInt) -> Self

Converts to this type from the input type.
Source§

impl From<VarInt> for StreamId

Source§

fn from(v: VarInt) -> Self

Converts to this type from the input type.
Source§

impl From<VarInt> for u64

Source§

fn from(x: VarInt) -> Self

Converts to this type from the input type.
Source§

impl From<u16> for VarInt

Source§

fn from(x: u16) -> Self

Converts to this type from the input type.
Source§

impl From<u32> for VarInt

Source§

fn from(x: u32) -> Self

Converts to this type from the input type.
Source§

impl From<u8> for VarInt

Source§

fn from(x: u8) -> Self

Converts to this type from the input type.
Source§

impl Hash for VarInt

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where +needs to construct itself. Read more

Source§

impl Clone for VarInt

Source§

fn clone(&self) -> VarInt

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for VarInt

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Decodable for VarInt

Source§

fn decode<B: Buf>(r: &mut B) -> Result<Self>

Decode a Self from the provided buffer, if the buffer is large enough
Source§

impl Default for VarInt

Source§

fn default() -> VarInt

Returns the “default value” for a type. Read more
Source§

impl Display for VarInt

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Encodable for VarInt

Source§

fn encode<B: BufMut>(&self, w: &mut B)

Append the encoding of self to the provided buffer.
Source§

impl From<Code> for VarInt

Source§

fn from(value: Code) -> Self

Converts to this type from the input type.
Source§

impl From<StreamId> for VarInt

Source§

fn from(x: StreamId) -> Self

Converts to this type from the input type.
Source§

impl From<VarInt> for Code

Source§

fn from(value: VarInt) -> Self

Converts to this type from the input type.
Source§

impl From<VarInt> for IdleTimeout

Source§

fn from(inner: VarInt) -> Self

Converts to this type from the input type.
Source§

impl From<VarInt> for StreamId

Source§

fn from(v: VarInt) -> Self

Converts to this type from the input type.
Source§

impl From<VarInt> for u64

Source§

fn from(x: VarInt) -> Self

Converts to this type from the input type.
Source§

impl From<u16> for VarInt

Source§

fn from(x: u16) -> Self

Converts to this type from the input type.
Source§

impl From<u32> for VarInt

Source§

fn from(x: u32) -> Self

Converts to this type from the input type.
Source§

impl From<u8> for VarInt

Source§

fn from(x: u8) -> Self

Converts to this type from the input type.
Source§

impl Hash for VarInt

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for VarInt

Source§

fn cmp(&self, other: &VarInt) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where diff --git a/pr/451/docs/noq_proto/struct.VarIntBoundsExceeded.html b/pr/451/docs/noq_proto/struct.VarIntBoundsExceeded.html index bbf796052..70a675175 100644 --- a/pr/451/docs/noq_proto/struct.VarIntBoundsExceeded.html +++ b/pr/451/docs/noq_proto/struct.VarIntBoundsExceeded.html @@ -1,4 +1,4 @@ -VarIntBoundsExceeded in noq_proto - Rust

VarIntBoundsExceeded

Struct VarIntBoundsExceeded 

Source
pub struct VarIntBoundsExceeded;
Expand description

Error returned when constructing a VarInt from a value >= 2^62

+VarIntBoundsExceeded in noq_proto - Rust

VarIntBoundsExceeded

Struct VarIntBoundsExceeded 

Source
pub struct VarIntBoundsExceeded;
Expand description

Error returned when constructing a VarInt from a value >= 2^62

Trait Implementations§

Source§

impl Clone for VarIntBoundsExceeded

Source§

fn clone(&self) -> VarIntBoundsExceeded

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for VarIntBoundsExceeded

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for VarIntBoundsExceeded

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for VarIntBoundsExceeded

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<VarIntBoundsExceeded> for ConfigError

Source§

fn from(_: VarIntBoundsExceeded) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for VarIntBoundsExceeded

Source§

fn eq(&self, other: &VarIntBoundsExceeded) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for VarIntBoundsExceeded

Source§

impl Eq for VarIntBoundsExceeded

Source§

impl StructuralPartialEq for VarIntBoundsExceeded

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where diff --git a/pr/451/docs/noq_proto/trait.ConnectionIdGenerator.html b/pr/451/docs/noq_proto/trait.ConnectionIdGenerator.html index 4bda392db..f036fa751 100644 --- a/pr/451/docs/noq_proto/trait.ConnectionIdGenerator.html +++ b/pr/451/docs/noq_proto/trait.ConnectionIdGenerator.html @@ -1,4 +1,4 @@ -ConnectionIdGenerator in noq_proto - Rust

ConnectionIdGenerator

Trait ConnectionIdGenerator 

Source
pub trait ConnectionIdGenerator: Send + Sync {
+ConnectionIdGenerator in noq_proto - Rust

ConnectionIdGenerator

Trait ConnectionIdGenerator 

Source
pub trait ConnectionIdGenerator: Send + Sync {
     // Required methods
     fn generate_cid(&mut self) -> ConnectionId;
     fn cid_len(&self) -> usize;
diff --git a/pr/451/docs/noq_proto/trait.ConnectionIdParser.html b/pr/451/docs/noq_proto/trait.ConnectionIdParser.html
index e9d81941a..b625e00fc 100644
--- a/pr/451/docs/noq_proto/trait.ConnectionIdParser.html
+++ b/pr/451/docs/noq_proto/trait.ConnectionIdParser.html
@@ -1,4 +1,4 @@
-ConnectionIdParser in noq_proto - Rust

ConnectionIdParser

Trait ConnectionIdParser 

Source
pub trait ConnectionIdParser {
+ConnectionIdParser in noq_proto - Rust

ConnectionIdParser

Trait ConnectionIdParser 

Source
pub trait ConnectionIdParser {
     // Required method
     fn parse(
         &self,
diff --git a/pr/451/docs/noq_proto/trait.NetworkChangeHint.html b/pr/451/docs/noq_proto/trait.NetworkChangeHint.html
index eed69fcd3..dd4a39869 100644
--- a/pr/451/docs/noq_proto/trait.NetworkChangeHint.html
+++ b/pr/451/docs/noq_proto/trait.NetworkChangeHint.html
@@ -1,4 +1,4 @@
-NetworkChangeHint in noq_proto - Rust

NetworkChangeHint

Trait NetworkChangeHint 

Source
pub trait NetworkChangeHint: Debug + 'static {
+NetworkChangeHint in noq_proto - Rust

NetworkChangeHint

Trait NetworkChangeHint 

Source
pub trait NetworkChangeHint: Debug + 'static {
     // Required method
     fn is_path_recoverable(
         &self,
@@ -6,7 +6,7 @@
         network_path: FourTuple,
     ) -> bool;
 }
Expand description

Hints when the caller identifies a network change.

-

Required Methods§

Source

fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool

Inform the connection if a path may recover after a network change.

+

Required Methods§

Source

fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool

Inform the connection if a path may recover after a network change.

After network changes, paths may not be recoverable. In this case, waiting for the path to become idle may take longer than what is desirable. If Self::is_path_recoverable returns false, a multipath-enabled, client-side connection will establish a new path to diff --git a/pr/451/docs/noq_proto/trait.QlogFactory.html b/pr/451/docs/noq_proto/trait.QlogFactory.html index 9aada7d50..38d0c61d5 100644 --- a/pr/451/docs/noq_proto/trait.QlogFactory.html +++ b/pr/451/docs/noq_proto/trait.QlogFactory.html @@ -1,4 +1,4 @@ -QlogFactory in noq_proto - Rust

QlogFactory

Trait QlogFactory 

Source
pub trait QlogFactory:
+QlogFactory in noq_proto - Rust

QlogFactory

Trait QlogFactory 

Source
pub trait QlogFactory:
     Send
     + Sync
     + 'static {
diff --git a/pr/451/docs/noq_proto/trait.TimeSource.html b/pr/451/docs/noq_proto/trait.TimeSource.html
index bdf9b3c8e..c3b83a20a 100644
--- a/pr/451/docs/noq_proto/trait.TimeSource.html
+++ b/pr/451/docs/noq_proto/trait.TimeSource.html
@@ -1,4 +1,4 @@
-TimeSource in noq_proto - Rust

TimeSource

Trait TimeSource 

Source
pub trait TimeSource: Send + Sync {
+TimeSource in noq_proto - Rust

TimeSource

Trait TimeSource 

Source
pub trait TimeSource: Send + Sync {
     // Required method
     fn now(&self) -> SystemTime;
 }
Expand description

Object to get current SystemTime

diff --git a/pr/451/docs/noq_proto/trait.TokenLog.html b/pr/451/docs/noq_proto/trait.TokenLog.html index 223cbfa33..2fdd9fa30 100644 --- a/pr/451/docs/noq_proto/trait.TokenLog.html +++ b/pr/451/docs/noq_proto/trait.TokenLog.html @@ -1,4 +1,4 @@ -TokenLog in noq_proto - Rust

TokenLog

Trait TokenLog 

Source
pub trait TokenLog: Send + Sync {
+TokenLog in noq_proto - Rust

TokenLog

Trait TokenLog 

Source
pub trait TokenLog: Send + Sync {
     // Required method
     fn check_and_insert(
         &self,
diff --git a/pr/451/docs/noq_proto/trait.TokenStore.html b/pr/451/docs/noq_proto/trait.TokenStore.html
index adb21f7e5..775677dfc 100644
--- a/pr/451/docs/noq_proto/trait.TokenStore.html
+++ b/pr/451/docs/noq_proto/trait.TokenStore.html
@@ -1,4 +1,4 @@
-TokenStore in noq_proto - Rust

TokenStore

Trait TokenStore 

Source
pub trait TokenStore: Send + Sync {
+TokenStore in noq_proto - Rust

TokenStore

Trait TokenStore 

Source
pub trait TokenStore: Send + Sync {
     // Required methods
     fn insert(&self, server_name: &str, token: Bytes);
     fn take(&self, server_name: &str) -> Option<Bytes>;
diff --git a/pr/451/docs/noq_proto/transport_parameters/enum.Error.html b/pr/451/docs/noq_proto/transport_parameters/enum.Error.html
index 7237a41b5..9894baf03 100644
--- a/pr/451/docs/noq_proto/transport_parameters/enum.Error.html
+++ b/pr/451/docs/noq_proto/transport_parameters/enum.Error.html
@@ -1,4 +1,4 @@
-Error in noq_proto::transport_parameters - Rust

Error

Enum Error 

Source
pub enum Error {
+Error in noq_proto::transport_parameters - Rust

Error

Enum Error 

Source
pub enum Error {
     IllegalValue,
     Malformed,
 }
Expand description

Errors encountered while decoding TransportParameters

diff --git a/pr/451/docs/noq_proto/transport_parameters/index.html b/pr/451/docs/noq_proto/transport_parameters/index.html index cc1b10a32..7675ae73c 100644 --- a/pr/451/docs/noq_proto/transport_parameters/index.html +++ b/pr/451/docs/noq_proto/transport_parameters/index.html @@ -1,4 +1,4 @@ -noq_proto::transport_parameters - Rust

Module transport_parameters

Module transport_parameters 

Source
Expand description

QUIC connection transport parameters

+noq_proto::transport_parameters - Rust

Module transport_parameters

Module transport_parameters 

Source
Expand description

QUIC connection transport parameters

The TransportParameters type is used to represent the transport parameters negotiated by peers while establishing a QUIC connection. This process happens as part of the establishment of the TLS session. As such, the types diff --git a/pr/451/docs/noq_proto/transport_parameters/struct.TransportParameters.html b/pr/451/docs/noq_proto/transport_parameters/struct.TransportParameters.html index 35de07fb4..135b488c1 100644 --- a/pr/451/docs/noq_proto/transport_parameters/struct.TransportParameters.html +++ b/pr/451/docs/noq_proto/transport_parameters/struct.TransportParameters.html @@ -1,4 +1,4 @@ -TransportParameters in noq_proto::transport_parameters - Rust

TransportParameters

Struct TransportParameters 

Source
pub struct TransportParameters {
+TransportParameters in noq_proto::transport_parameters - Rust

TransportParameters

Struct TransportParameters 

Source
pub struct TransportParameters {
     pub max_remote_nat_traversal_addresses: Option<NonZeroU8>,
     /* private fields */
 }
Expand description

Transport parameters used to negotiate connection-level preferences between peers

diff --git a/pr/451/docs/noq_udp/all.html b/pr/451/docs/noq_udp/all.html index e2c3b0d59..5f3869ce3 100644 --- a/pr/451/docs/noq_udp/all.html +++ b/pr/451/docs/noq_udp/all.html @@ -1 +1 @@ -List of all items in this crate

All

\ No newline at end of file +List of all items in this crate

All

\ No newline at end of file diff --git a/pr/451/docs/noq_udp/constant.BATCH_SIZE.html b/pr/451/docs/noq_udp/constant.BATCH_SIZE.html index e00fd2532..392fcddd2 100644 --- a/pr/451/docs/noq_udp/constant.BATCH_SIZE.html +++ b/pr/451/docs/noq_udp/constant.BATCH_SIZE.html @@ -1,2 +1,2 @@ -BATCH_SIZE in noq_udp - Rust

BATCH_SIZE

Constant BATCH_SIZE 

Source
pub const BATCH_SIZE: usize = imp::BATCH_SIZE; // 32usize
Expand description

Number of UDP packets to send/receive at a time

+BATCH_SIZE in noq_udp - Rust

BATCH_SIZE

Constant BATCH_SIZE 

Source
pub const BATCH_SIZE: usize = imp::BATCH_SIZE; // 32usize
Expand description

Number of UDP packets to send/receive at a time

\ No newline at end of file diff --git a/pr/451/docs/noq_udp/enum.EcnCodepoint.html b/pr/451/docs/noq_udp/enum.EcnCodepoint.html index a590db43f..ac8adfa7c 100644 --- a/pr/451/docs/noq_udp/enum.EcnCodepoint.html +++ b/pr/451/docs/noq_udp/enum.EcnCodepoint.html @@ -1,4 +1,4 @@ -EcnCodepoint in noq_udp - Rust

EcnCodepoint

Enum EcnCodepoint 

Source
#[repr(u8)]
pub enum EcnCodepoint { +EcnCodepoint in noq_udp - Rust

EcnCodepoint

Enum EcnCodepoint 

Source
#[repr(u8)]
pub enum EcnCodepoint { Ect0 = 2, Ect1 = 1, Ce = 3, diff --git a/pr/451/docs/noq_udp/index.html b/pr/451/docs/noq_udp/index.html index f6ff6cefa..585d3be43 100644 --- a/pr/451/docs/noq_udp/index.html +++ b/pr/451/docs/noq_udp/index.html @@ -1,4 +1,4 @@ -noq_udp - Rust

Crate noq_udp

Crate noq_udp 

Source
Expand description

Uniform interface to send and receive UDP packets with advanced features useful for QUIC

+noq_udp - Rust

Crate noq_udp

Crate noq_udp 

Source
Expand description

Uniform interface to send and receive UDP packets with advanced features useful for QUIC

This crate exposes kernel UDP stack features available on most modern systems which are required for an efficient and conformant QUIC implementation. As of this writing, these are not available in std or major async runtimes, and their niche character and complexity are a barrier to adding diff --git a/pr/451/docs/noq_udp/struct.RecvMeta.html b/pr/451/docs/noq_udp/struct.RecvMeta.html index fa27d5986..4c6a03e64 100644 --- a/pr/451/docs/noq_udp/struct.RecvMeta.html +++ b/pr/451/docs/noq_udp/struct.RecvMeta.html @@ -1,4 +1,4 @@ -RecvMeta in noq_udp - Rust

RecvMeta

Struct RecvMeta 

Source
#[non_exhaustive]
pub struct RecvMeta { +RecvMeta in noq_udp - Rust

RecvMeta

Struct RecvMeta 

Source
#[non_exhaustive]
pub struct RecvMeta { pub addr: SocketAddr, pub len: usize, pub stride: usize, diff --git a/pr/451/docs/noq_udp/struct.Transmit.html b/pr/451/docs/noq_udp/struct.Transmit.html index 730bcf09a..46148e04d 100644 --- a/pr/451/docs/noq_udp/struct.Transmit.html +++ b/pr/451/docs/noq_udp/struct.Transmit.html @@ -1,4 +1,4 @@ -Transmit in noq_udp - Rust

Transmit

Struct Transmit 

Source
pub struct Transmit<'a> {
+Transmit in noq_udp - Rust

Transmit

Struct Transmit 

Source
pub struct Transmit<'a> {
     pub destination: SocketAddr,
     pub ecn: Option<EcnCodepoint>,
     pub contents: &'a [u8],
diff --git a/pr/451/docs/noq_udp/struct.UdpSockRef.html b/pr/451/docs/noq_udp/struct.UdpSockRef.html
index 80c6f6e94..d1f8b6206 100644
--- a/pr/451/docs/noq_udp/struct.UdpSockRef.html
+++ b/pr/451/docs/noq_udp/struct.UdpSockRef.html
@@ -1,4 +1,4 @@
-UdpSockRef in noq_udp - Rust

UdpSockRef

Struct UdpSockRef 

Source
pub struct UdpSockRef<'a>(/* private fields */);
Expand description

A borrowed UDP socket

+UdpSockRef in noq_udp - Rust

UdpSockRef

Struct UdpSockRef 

Source
pub struct UdpSockRef<'a>(/* private fields */);
Expand description

A borrowed UDP socket

On Unix, constructible via From<T: AsFd>. On Windows, constructible via From<T: AsSocket>.

Trait Implementations§

Source§

impl<'s, S> From<&'s S> for UdpSockRef<'s>
where S: AsFd,

Available on Unix only.
Source§

fn from(socket: &'s S) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl<'a> Freeze for UdpSockRef<'a>

§

impl<'a> RefUnwindSafe for UdpSockRef<'a>

§

impl<'a> Send for UdpSockRef<'a>

§

impl<'a> Sync for UdpSockRef<'a>

§

impl<'a> Unpin for UdpSockRef<'a>

§

impl<'a> UnwindSafe for UdpSockRef<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where diff --git a/pr/451/docs/noq_udp/struct.UdpSocketState.html b/pr/451/docs/noq_udp/struct.UdpSocketState.html index 5a45e8fb2..7260f13fb 100644 --- a/pr/451/docs/noq_udp/struct.UdpSocketState.html +++ b/pr/451/docs/noq_udp/struct.UdpSocketState.html @@ -1,4 +1,4 @@ -UdpSocketState in noq_udp - Rust

UdpSocketState

Struct UdpSocketState 

Source
pub struct UdpSocketState { /* private fields */ }
Expand description

Tokio-compatible UDP socket with some useful specializations.

+UdpSocketState in noq_udp - Rust

UdpSocketState

Struct UdpSocketState 

Source
pub struct UdpSocketState { /* private fields */ }
Expand description

Tokio-compatible UDP socket with some useful specializations.

Unlike a standard tokio UDP socket, this allows ECN bits to be read and written on some platforms.

Implementations§

Source§

impl UdpSocketState

Source

pub fn new(sock: UdpSockRef<'_>) -> Result<Self>

Source

pub fn send( diff --git a/pr/451/docs/perf/client/fn.run.html b/pr/451/docs/perf/client/fn.run.html index d78e6dd08..24a4eb43a 100644 --- a/pr/451/docs/perf/client/fn.run.html +++ b/pr/451/docs/perf/client/fn.run.html @@ -1 +1 @@ -run in perf::client - Rust

run

Function run 

Source
pub async fn run(opt: Opt) -> Result<()>
\ No newline at end of file +run in perf::client - Rust

run

Function run 

Source
pub async fn run(opt: Opt) -> Result<()>
\ No newline at end of file diff --git a/pr/451/docs/perf/fn.parse_byte_size.html b/pr/451/docs/perf/fn.parse_byte_size.html index 6d4afc897..5c1fc50bd 100644 --- a/pr/451/docs/perf/fn.parse_byte_size.html +++ b/pr/451/docs/perf/fn.parse_byte_size.html @@ -1 +1 @@ -parse_byte_size in perf - Rust

parse_byte_size

Function parse_byte_size 

Source
pub fn parse_byte_size(s: &str) -> Result<u64, ParseIntError>
\ No newline at end of file +parse_byte_size in perf - Rust

parse_byte_size

Function parse_byte_size 

Source
pub fn parse_byte_size(s: &str) -> Result<u64, ParseIntError>
\ No newline at end of file diff --git a/pr/451/docs/perf/server/fn.run.html b/pr/451/docs/perf/server/fn.run.html index e1e250aae..eee2b309f 100644 --- a/pr/451/docs/perf/server/fn.run.html +++ b/pr/451/docs/perf/server/fn.run.html @@ -1 +1 @@ -run in perf::server - Rust

run

Function run 

Source
pub async fn run(opt: Opt) -> Result<()>
\ No newline at end of file +run in perf::server - Rust

run

Function run 

Source
pub async fn run(opt: Opt) -> Result<()>
\ No newline at end of file diff --git a/pr/451/docs/perf/struct.CommonOpt.html b/pr/451/docs/perf/struct.CommonOpt.html index 12f255781..e37ca54a5 100644 --- a/pr/451/docs/perf/struct.CommonOpt.html +++ b/pr/451/docs/perf/struct.CommonOpt.html @@ -40,7 +40,7 @@ becoming blocked.

§max_udp_payload_size: u16

Max UDP payload size in bytes

§qlog_dir: Option<PathBuf>

qlog output directory

Alternatively you can set the QLOGDIR environment variable.

-

Implementations§

Trait Implementations§

Source§

impl Args for CommonOpt

Source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
Source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self via +

Implementations§

Trait Implementations§

Source§

impl Args for CommonOpt

Source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
Source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self via [FromArgMatches::from_arg_matches_mut] Read more
Source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate self via [FromArgMatches::update_from_arg_matches_mut] Read more
Source§

impl CommandFactory for CommonOpt

Source§

fn command<'b>() -> Command

Build a [Command] that can instantiate Self. Read more
Source§

fn command_for_update<'b>() -> Command

Build a [Command] that can update self. Read more
Source§

impl FromArgMatches for CommonOpt

Source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
Source§

fn from_arg_matches_mut( __clap_arg_matches: &mut ArgMatches, diff --git a/pr/451/docs/search.index/000adb4f555c.js b/pr/451/docs/search.index/000adb4f555c.js new file mode 100644 index 000000000..c9fd43c43 --- /dev/null +++ b/pr/451/docs/search.index/000adb4f555c.js @@ -0,0 +1 @@ +rn_("AQAA9K4QAAABAAEAAQDzAgFz9UYJAAABAAEAAQCKBgEGAPQDEgAAAQABAAEAAQEAOzAAAAEAAAYAAQDCFAYA8wYBZfRvFAAAAQA0AAEAEwYDsEAUmwABsGAThwCJYXJ183sQAAB5AgEA84ACYWUBAwD0thEAAAEAAQABAAECAPTIDwAAAQBRBAEAAYICoBAAABNdsEAS+gABYXMBgAKgAAAADBewcBCMAAFyc+cHAACHoHAAABICkQVBOjAAAAEAAAAAAAwAEAAAAOcB6AHpAeoBeAfhB+IHTwlVDAcQCBAJEBwR") \ No newline at end of file diff --git a/pr/451/docs/search.index/000ba6d7fd92.js b/pr/451/docs/search.index/000ba6d7fd92.js new file mode 100644 index 000000000..3eb2b3070 --- /dev/null +++ b/pr/451/docs/search.index/000ba6d7fd92.js @@ -0,0 +1 @@ +rn_("AQECsCAM8QABsAAMaQABZXPyYwcAAAEAAQUA8x4TAAABAAEA8wQBcvTkDwAAAQDvAAEAE4ACoLAAABReYXPzgAJiYw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/00142191e584.js b/pr/451/docs/search.index/00142191e584.js new file mode 100644 index 000000000..ddad680f2 --- /dev/null +++ b/pr/451/docs/search.index/00142191e584.js @@ -0,0 +1 @@ +rn_("AQIA9AoRAAABAJIBAQABAAD08g8AAAEA9QAHASOCArBQE1UAAWVzAQIA9CUTAAABAJIAAQABAwD0WxMAAAEAmAABAPOFAmJ1AQIA9HURAAABAHwBAQDzgAJpbPMAA2J0dTowAAABAAAAAAAPABAAAAByB/sH/Af9B/4HGxDhEK8TMRR+FH8UgxSQFJEUtxS7FAEMAPMUFAAAAQABAAGAArAACRkAAaAgAAANA2RuAQYA9CMTAAABAJIAAQDzAwFi9AgRAAABAJIBAQCXBgAAh7BAD/AAAaBwAAASp7CQE1MAATgJAzowAAABAAAAAAAkABAAAADBBcIFwwXEBTQHNQczDPgM+Qz6DP4M/wwADegP6Q88ED0QQRBCEF8QYBBhEGIQYxBkEGUQqBCtENkQ2hCPE7MT2hN0FIAUoxS0FA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/005f416926ad.js b/pr/451/docs/search.index/005f416926ad.js deleted file mode 100644 index b563f9018..000000000 --- a/pr/451/docs/search.index/005f416926ad.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9SQRAAABAAEAAQABACOBArCQE+oAAWF0Q4ADsRAUbAABsIATrAABY290EwACsAAUQQABbnL2CQIAAAEAdgIBAKkCSww=") \ No newline at end of file diff --git a/pr/451/docs/search.index/00c4f658f46f.js b/pr/451/docs/search.index/00c4f658f46f.js deleted file mode 100644 index 0f001500d..000000000 --- a/pr/451/docs/search.index/00c4f658f46f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsIATzgABdAEHCQAAAQkA87ESAAABAAEA84ACY2cBgwKgkAAAFBugkAAAFBwyMwGAAqBQAAAUR6CAAAATZmNl8wACc3TyNAcAAAEAAQABoLAAABOxYvM7EwAALgEBACOAAqAAAAATOmRyI4ACoFAAABQNY2XzgAJpeQEGAPUkEQAAAQABAAEAAQABAQGwQBRLAAFl9Q8UAAABAAEAAQABAPcAAICFoBAAAAv+KWAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/00ee768feb28.js b/pr/451/docs/search.index/00ee768feb28.js new file mode 100644 index 000000000..b0a390133 --- /dev/null +++ b/pr/451/docs/search.index/00ee768feb28.js @@ -0,0 +1 @@ +rn_("AYQCoCAAABEnoDAAABGNaXQBAQA7MAAAAQAAjQACAHMHAAB1CYwA8wACZWz1twQAAMACzQlVAgEAAQIAOzAAAAEAAO4AAgAMDAAAGA3tAMcAAACFsLATQwABsBATyQABsDANFgABFDACOzAAAAEAABoBCwDgAQYAowUAAM0F7QBCBwcAQxACAJwRDQDLEQIApBIBACsTAQB+EwEAFBQCAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0125cbd5bba3.js b/pr/451/docs/search.index/0125cbd5bba3.js deleted file mode 100644 index 834381469..000000000 --- a/pr/451/docs/search.index/0125cbd5bba3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsFAT+wABoAAAAAj9Y3QBAQD0dxMAAAEAAQABAAEDAPTcDAAA1QagAAEAAYACoBAAAAxloFAAABKGY2QBAgDz8QgAAEsDAQDzAAFt854EAADFAAEAAYECoBAAABMqoHAAABNyYWPzAAFy804HAABBCiAAZw8AgIiwMA+4ANGgMAAAFHspMCM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/014f131aa095.js b/pr/451/docs/search.index/014f131aa095.js new file mode 100644 index 000000000..b567c3b10 --- /dev/null +++ b/pr/451/docs/search.index/014f131aa095.js @@ -0,0 +1 @@ +rn_("AQYA9L4TAAABAAEAAQABDwD0qBQAAAEAAQABAPOHAmhtAQAA9A4TAAABAAEAAQDzBAFk9LESAAABAAEAAQAjh4SwoBRWAAGwQBMMAAGwcBP/AAEVgAABAAGgQAAAEJdlOzAAAAEAAAcAAgBsBwUA+QcBABOAArBQEE0AAWRyAQkA9eATAAABAAEAeAABALcCAICGsCASvAABVFDsruw9EUkB") \ No newline at end of file diff --git a/pr/451/docs/search.index/023324239cca.js b/pr/451/docs/search.index/023324239cca.js new file mode 100644 index 000000000..f5b4e02ac --- /dev/null +++ b/pr/451/docs/search.index/023324239cca.js @@ -0,0 +1 @@ +rn_("AQABsIAUPAABcPI4FAAAAQBDBQOgIAAAEWCgQAAAErVkZXPz+A8AAFADAQABAgA7MAAAAQAAEwABACUQEwATgYSgUAAAERCgEAAADCagIAAADPAcQAAzgAOwUBOEAAFhZm4=") \ No newline at end of file diff --git a/pr/451/docs/search.index/027e427e88a7.js b/pr/451/docs/search.index/027e427e88a7.js new file mode 100644 index 000000000..24fc65d69 --- /dev/null +++ b/pr/451/docs/search.index/027e427e88a7.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAI0AAgD3EAAACBKMAAEAAbBwE8wAAWXy4g8AAAEA8wABc/RNEAAAAQBNBAEAM4ADoGAAABGqZGlv") \ No newline at end of file diff --git a/pr/451/docs/search.index/02cee3b0c9d6.js b/pr/451/docs/search.index/02cee3b0c9d6.js new file mode 100644 index 000000000..d6583727a --- /dev/null +++ b/pr/451/docs/search.index/02cee3b0c9d6.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAkAEAAAAN8P4A/WEJIR8BLxEvETeBSuFK8UAYACsOAT6wABsDAUAwABZHDDgISxQBTAAAGgkAAAE68CgAk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/02dd940163a0.js b/pr/451/docs/search.index/02dd940163a0.js deleted file mode 100644 index 04ef38f41..000000000 --- a/pr/451/docs/search.index/02dd940163a0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgBoIAAABScZgF7EwAAE4ACoOAAABReYnABAwGwABQWAFBz9tAPAAABAMIAKwABAP4CAYACsEASoAABoBAAABD9cHIBgQOgAAAAEW6wAA+3ASigEAAAD69lc3XzgANhb3IBAgD1wRAAAAEAAQBdAgEAAQQAOzAAAAEAAAkAAQAzEQkAAQAA9KMRAAABAAEAAQABgAKgkAAAE9awUBKIAAFid/OAAmR0o4CEsFAT8gABsJATFwABQCADVwMAAIagcAAAE82gQAAAFEMwQQw7MAAAAQAAQgAhALsEAQB+BQoAcQcAABkJAQAfCQAAGwwBAD4MAgDTDAEA4AwAAIUOAACgDwQAtw8AANoPAQAfEAAAnBAAAKoQAwDNEAEA3xAAAAURAQByEQAAxhEAAM8RAQDUEQAA0xIAAA0TAQDBEwEAyBMAAPITAQAUFAEALBQAAGcUAQCZFAEApBQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0302cc930c05.js b/pr/451/docs/search.index/0302cc930c05.js deleted file mode 100644 index 58a2551b4..000000000 --- a/pr/451/docs/search.index/0302cc930c05.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYECsDAQJAABsBATnwABc3QBAwDzsRIAAAEAAQAjgAKwABGpAAFoaQEAADswAAABAAATAAEAeAcTACOAAqAwAAAMymFl84ACcnTzAAJhbDswAAABAAAoAAMAmQATAKsBAAALEBMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/03352c8d2282.js b/pr/451/docs/search.index/03352c8d2282.js new file mode 100644 index 000000000..d59d97ef2 --- /dev/null +++ b/pr/451/docs/search.index/03352c8d2282.js @@ -0,0 +1 @@ +rn_("AQEA8/kPAAABAAEAAYmEoIAAABS3oEAAABRhoGAAABSDoJAAABS7AoAKE4EDoKAAABPxsNAUWgABY2l1M4ADsMAUuQABYXR1") \ No newline at end of file diff --git a/pr/451/docs/search.index/03aae57c1bdc.js b/pr/451/docs/search.index/03aae57c1bdc.js deleted file mode 100644 index 180d12205..000000000 --- a/pr/451/docs/search.index/03aae57c1bdc.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQAzBQOwYBNqAIphcnXzYRAAAHcCAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/040989688a54.js b/pr/451/docs/search.index/040989688a54.js deleted file mode 100644 index f55da29dc..000000000 --- a/pr/451/docs/search.index/040989688a54.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9KETAAABAAEAAQABDwD0jBQAAAEAAQABAAEAAbDwFJYAAWs7MAAAAQAACAADAP0TAAACFAYALRQAAAECAPRbEAAAAQCyAAEA9wAAAIWxABR/AAGJMADyvhIAAAEAE4ACsBAMQQABZW8BBQA7MAAAAQAADQABAIERDQABAgDz7g8AAAEAAQABAgD06g8AAAEAAQABAAEDADswAAABAAAIAAEAnxAIAAGAAqAAAAAMFLAwFEsAAXFyAYACoEAAABD1sMAUQQABZXMBAQA7MAAAAQAAEwABAAsQEwAjgAKwkBOpAAFpbXc2AACKoFAAABQMsHAT6gABoCAAAAzdnFUROzAAAAEAAAsABABKBwAAzxADAJ4SAQAPFAQAAQIBsDARXAABcAGFEgAAs4CEoCAAABHGAigE") \ No newline at end of file diff --git a/pr/451/docs/search.index/04d8cce59752.js b/pr/451/docs/search.index/04d8cce59752.js new file mode 100644 index 000000000..0ef8456ba --- /dev/null +++ b/pr/451/docs/search.index/04d8cce59752.js @@ -0,0 +1 @@ +rn_("AQAA9CkTAAABAFoBAQDzBAFzOzAAAAEAAA0AAgCmEwcAnRQFAEcAAAWGoIAAABOGsQAUrAABsEAS4wABsIAUvAABsFATKwABFQAW9JYOAAABAAEAAQBDAISgIAAACSug0AAAE5KgMAAACQsVAATzAwIAAAEAcAU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/0527185c0cd7.js b/pr/451/docs/search.index/0527185c0cd7.js deleted file mode 100644 index 535bd4b78..000000000 --- a/pr/451/docs/search.index/0527185c0cd7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoFAAABEqoFAAABJ5YXUBgwKxABSiAAGw0BSSAAFrbQELADswAAABAAAGAAEAAhQGACOAAqCAAAAULWVvIwMDoGAAABPAsEAUnQABZW5wAREMAAAjgAKgQAAAELttcAELAPPkEwAAAQB3APcAAACFoQAAABReBIEDOzAAAAEAADsABACuBQUAQwcAAJQHMwBqEQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/05284f2042e8.js b/pr/451/docs/search.index/05284f2042e8.js new file mode 100644 index 000000000..b54d19436 --- /dev/null +++ b/pr/451/docs/search.index/05284f2042e8.js @@ -0,0 +1 @@ +rn_("AQMA88ASAAABAAEAI4ECsSAUvAABY2sBBAGgQAAAEfll8wUNAAAUBAEAAQAAOzAAAAEAAAUAAgDzEAMAjBMBAHOAhKAwAAAUOggwAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0536332d465c.js b/pr/451/docs/search.index/0536332d465c.js new file mode 100644 index 000000000..fb689ce1e --- /dev/null +++ b/pr/451/docs/search.index/0536332d465c.js @@ -0,0 +1 @@ +rn_("AQQBoFAAABRecwGgEgAAAYICsEASxwABsEASyQABNDYBCwDzyBMAAGoAAQABBgD0tRQAAAEACgABAPMCAXIBERQAAPMDAWU7MAAAAQAABQACAH8QAwAwEQEA84QDYWVpY4ADoFAAABSYY2V0") \ No newline at end of file diff --git a/pr/451/docs/search.index/058e2bfdda25.js b/pr/451/docs/search.index/058e2bfdda25.js new file mode 100644 index 000000000..d12c3a1de --- /dev/null +++ b/pr/451/docs/search.index/058e2bfdda25.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAUAAgB/DAMAGhMBAAECAPN7EQAAAQABAAEGAPSoFAAAAQABAAEA8wYBbfSsEgAAAQABAAEA84ACbnbzgAJhaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/05ba80c533eb.js b/pr/451/docs/search.index/05ba80c533eb.js new file mode 100644 index 000000000..f95eb14c0 --- /dev/null +++ b/pr/451/docs/search.index/05ba80c533eb.js @@ -0,0 +1 @@ +rn_("AQMBsDATXgABY/OGEQAAAQABAAEDAYATgAAQ+HM7MAAAAQAABQABAGATBQAjggKgkAAAFGJpcwcBAIDFsFATJwABoKAAABPLsHATkAABoNAAABRugAEYCPOAAmRy") \ No newline at end of file diff --git a/pr/451/docs/search.index/05daabf53ac2.js b/pr/451/docs/search.index/05daabf53ac2.js deleted file mode 100644 index ed95e45d1..000000000 --- a/pr/451/docs/search.index/05daabf53ac2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsGAT8gABZQEsFAAAAQYA9BwTAAABAFEBAQABBAD0NhMAAAEAEQEBAPMAA2VyczowAAABAAAAAAAJABAAAAAWCRcJGAkqEWkRfBJ9EooS6hPrEw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/05ff51e69bf3.js b/pr/451/docs/search.index/05ff51e69bf3.js deleted file mode 100644 index 8d58588c1..000000000 --- a/pr/451/docs/search.index/05ff51e69bf3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOjAAAAEAAAAAAAoAEAAAAM4Pzw+8EL0QSBHZEdoR9hL3EpAUkRQBBADzyQ8AAN8AAQDzAAFx9/cMAAABADgDAQDMAGEAAQDzAAJleTowAAABAAAAAAAOABAAAAD8CEEMQgxdEHIQcxAHEQgRCRHfE+AT8BPxE2sUcBQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/0620fd046b92.js b/pr/451/docs/search.index/0620fd046b92.js new file mode 100644 index 000000000..8c385345b --- /dev/null +++ b/pr/451/docs/search.index/0620fd046b92.js @@ -0,0 +1 @@ +rn_("AQkBoIAAABS4ZgGYEwAAAQIA9dwQAAABAAEAXwIBAPMAAml1OjAAAAEAAAAAAAcAEAAAALYQ7xLeE98TDhQPFC8UMBQBCAD1QhQAAAEAAQABAAEA8wIBZvWaEAAAAQABAAEAAQATgAKwoBSOAAFydPMCAmFz88oQAAABAAEAAQQBsEAUZgABZfUqFAAAAQABAAEAAQABAgA7MAAAAQAACwACANQTBQCSFAUA8wMBbTswAAABAAANAAQA0AwAAG0RAADpEgUAThQFAPOBAmNl84ACYWU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/06738305d393.js b/pr/451/docs/search.index/06738305d393.js new file mode 100644 index 000000000..0409fdfb2 --- /dev/null +++ b/pr/451/docs/search.index/06738305d393.js @@ -0,0 +1 @@ +rn_("AQIA9F4MAAABACIIAQAjgAKwABAjAAFlaQGJArAgFHEAAaAwAAAUe2lwEwADoCAAABMtoIAAABNCZWl19RAMAAA6AAEAAQABAAGGArCQFJkAAaAAAAASuWJkAYACsDATIQABoDAAABD4cnbXAAAAhaAwAAAQbKAwAAATeRABEzowAAABAAAAAAAKABAAAAD7CPwI/QgtCS4JHwyHEI4QSBFJEf0SY4ADsBAI7wABZG10") \ No newline at end of file diff --git a/pr/451/docs/search.index/06f31413a772.js b/pr/451/docs/search.index/06f31413a772.js new file mode 100644 index 000000000..b7794ba79 --- /dev/null +++ b/pr/451/docs/search.index/06f31413a772.js @@ -0,0 +1 @@ +rn_("AQEAOzAAAAEAAAYAAQBkEQYAAQAA9x8JAAABAF0HAQBzAAEAFQEBggKgIAAAESegMAAAEY1pdPOAA2FncwEBAaBAAAAUOmXyaBQAAAEAI4ACsFAT7wABZnPzgAJpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/070f6c0257e1.js b/pr/451/docs/search.index/070f6c0257e1.js deleted file mode 100644 index 49306476a..000000000 --- a/pr/451/docs/search.index/070f6c0257e1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwCwEQIAYRMBAPgTAgABBADzsxEAAAEAAQAjAAKgYAAAEuFhcvPUEAAAAQDIAQGAAqEQAAAUh7CAFH0AAWl1NwQAgoexUBSkAAGgAAAAELSwcBPBAAGhMAAAFJgKQBsTgAKwEBLtAAFpcg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0777c1b794b3.js b/pr/451/docs/search.index/0777c1b794b3.js deleted file mode 100644 index 94c875648..000000000 --- a/pr/451/docs/search.index/0777c1b794b3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAEoAAAABB/sCARMQABoFAAABHdoEAAABQgZGxzdjswAAABAAATAAYApAUAACAMAADVDAAA8Q8NAFURAAAbFAEAAQIA9LwRAAABAAEAAQAThAKwIBHAAAFhc/OAAmFl") \ No newline at end of file diff --git a/pr/451/docs/search.index/07aa85ef4d97.js b/pr/451/docs/search.index/07aa85ef4d97.js new file mode 100644 index 000000000..88e45aab4 --- /dev/null +++ b/pr/451/docs/search.index/07aa85ef4d97.js @@ -0,0 +1 @@ +rn_("AQUA9OgPAAABAPAAAQCDAISgQAAADPuwcBNxAAGwIAx4AAEQBQI7MAAAAQAACQADALsEAwB/BQIAzhECAAGAAqAgAAARJbAgFDwAAWR0AYQCsLAUjgABsAAT3gABYXPTgISgsAAAFHNAKAQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/082363d032b8.js b/pr/451/docs/search.index/082363d032b8.js deleted file mode 100644 index 92fb27686..000000000 --- a/pr/451/docs/search.index/082363d032b8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9M4TAAABAFYAAQAjgAKhEAAAFItjchOAAqAgAAAI/mh5AQEBoGAAABR8c/LAEQAAAQAjgAKwIAljAAFldQGAArAgEAkAAaCQAAAULGFlAQcBsEAUSwABZfUPFAAAAQABAAEAAQABAAD0DRMAAAEAWQEBAPMEAXM7MAAAAQAADQACAIkTBwCBFAUARwAABIaggAAAE2mxABSQAAGwQBLHAAGwgBSgAAGwUBMPAAEVABb0gQ4AAAEAAQABAIMAhLBAFJYAAbAwEv4AAbAwE50AAcAAAzowAAABAAAAAAAJABAAAACxBPYQ9xCZEZoRpxGoEewT7RNfFAEDAbBAFGEAAXTyvhMAAAEAI4QCoEAAABPuYnMTgAKwwBSUAAF1eQGAAqAAAAAJHrAgEOgAAWdwAQEA9LwRAAABAAEAAQDzgAJkbLfoAACMoNAAABN1oFAAABFVoJAAABORsFAS7QABoDAAAAxZt5gHOzAAAAEAAGUAEwD/AQEABQIBAF8EAwC7BQAAtwYBAFUHAQBoBwAA+ggBAAcJAAARCQAAQwkAAHIMQgDLDAAA1g8BAK4QAQDLEAEAEBEBAJARBACgEgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/08410bada6e3.js b/pr/451/docs/search.index/08410bada6e3.js new file mode 100644 index 000000000..bb89e3d92 --- /dev/null +++ b/pr/451/docs/search.index/08410bada6e3.js @@ -0,0 +1 @@ +rn_("AQQA9HQUAAAMACMAEQABAgGwQBK+AAFlAcsPAAABgAKgkAAAFDagkAAAFDcyM5cCAADGoCAAAAx0oGAAABFEsGANCAABAWQAAzswAAABAAAKAAMAGQcFAEAMAAB6EwMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/0867d8c9f05d.js b/pr/451/docs/search.index/0867d8c9f05d.js new file mode 100644 index 000000000..99d831a68 --- /dev/null +++ b/pr/451/docs/search.index/0867d8c9f05d.js @@ -0,0 +1 @@ +rn_("AYYCoAAAABNXoAAAABNYZHIBgAKgcAAAE+qgMAAAERhmdAELAaBAAAAUOmXyaBQAAAEAI4ECsFAQ7wABY3YBAAGwgBQ8AAFw8jgUAAABAEMGA6AgAAARYKBAAAAStWRlc/P4DwAAUAMBAPMABGlyc3Y7MAAAAQAADwAFAPQGBQDcEgUACRMBAIMTAABzFAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/08f0fa1cddd0.js b/pr/451/docs/search.index/08f0fa1cddd0.js new file mode 100644 index 000000000..29675be49 --- /dev/null +++ b/pr/451/docs/search.index/08f0fa1cddd0.js @@ -0,0 +1 @@ +rn_("AYADoIAAABLWsFAS/AA8sHAULwABYWNzAQABoIAAABS4ZvL6CAAAngoBBwD1ZhMAAAEAAQABAAEAI4ACoFAAABFjYW/zAAJmczswAAABAAAIAAQATQcAAH4HBQAEDQAAmxEAAPOAAmR5") \ No newline at end of file diff --git a/pr/451/docs/search.index/090c885d9b86.js b/pr/451/docs/search.index/090c885d9b86.js deleted file mode 100644 index 02dec59dd..000000000 --- a/pr/451/docs/search.index/090c885d9b86.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9FkUAAAMACIAEQABAgGwQBKiAAFlAbMPAAABgAKgkAAAFBugkAAAFBwyM5cCAADGoCAAAAxioGAAABEpsGAM9QABAWQAAzswAAABAAAKAAMADQcFAC4MAABdEwMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/09d1502998bb.js b/pr/451/docs/search.index/09d1502998bb.js deleted file mode 100644 index 0aaa02067..000000000 --- a/pr/451/docs/search.index/09d1502998bb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUBsEASpwABZfLLEgAAAQABAAA7MAAAAQAAEQAEAP0BAQCfEAgAmxEDAKQSAgABCgGwQBRLAAFl9Q8UAAABAAEAAQABAEcGAACHsCAQ+QI6sMAUlgABoCAAAAbvsDAHTAABGIkKOjAAAAEAAAAAAAwAEAAAAEgHSQcyCQsMDAwSDBMMZgxnDLUMtgz6EvsS") \ No newline at end of file diff --git a/pr/451/docs/search.index/0a98c9fc8112.js b/pr/451/docs/search.index/0a98c9fc8112.js deleted file mode 100644 index 8c172d512..000000000 --- a/pr/451/docs/search.index/0a98c9fc8112.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9O8QAAABAJEBAQAjgQKgYAAAEyViaQEAAPTaDwAAAQDyAAcBI4ICsFATOAABZXMBiQKwIBRWAAGgMAAAFGBpcAECAPQJEwAAAQCRAAEAAQMA9D4TAAABAJgAAQDzhQJidQECAPRaEQAAAQB7AQEA84ACaWy3AQAAhaAgAAATERIBDDowAAABAAAAAAAUABAAAABmB+8H8AfxB/IH/ws4DDkMOgw7DAEQxhCSExYUYxRkFGYUcxR0FJsUnxQBBQDz+BMAAAEAAQBDhgOwkBR9AAGgAAAAEp1iZHcBgAKwAAkMAAGwIAzwA2JkbgEDAPPYDwAAAQAEAQECAPYIDAAAAQCIAwEA4QMBAAEAAPQqCQAAAQABAAEAAQAAOzAAAAEAAAYAAgBxEwAA2RMFACOBArBAFFEAAWVz84ADZXh5AQAA9CYJAAABAAEAAQBDgAOgQAAAFHuwIBMFAAFlcngHGgAAyaCQAAATU7BQEAIAAbDAE50AAaAQAAARVqAwAAAT77BADNMAAQBHoAs6MAAAAQAAAAAACQAQAAAAZgVnBdkM2gzuDNQPLBAtEPwS/RIBBgD0BxMAAAEAkQABAPMDAWL07RAAAAEAkQEBALcMAACJoHAAABKLsJATNgABsGAQJAABoDAAABNcOIkTOjAAAAEAAAAAAC8AEAAAALcFuAW5BboFKAcpB+4I7wjwCCAJIQkNDCEM5QzmDOcM6wzsDO0M0A/RDyIQIxAnECgQRRBGEEcQSBBJEEoQSxBtEHQQjhCTEL4QvxAtES4R4RJyE5YTvRNZFGUUhxSYFA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0a99e2bd71de.js b/pr/451/docs/search.index/0a99e2bd71de.js new file mode 100644 index 000000000..5cdee67e1 --- /dev/null +++ b/pr/451/docs/search.index/0a99e2bd71de.js @@ -0,0 +1 @@ +rn_("AQMBoEAAABQ6ZfJoFAAAAQABAAD0DhMAAAEAAQABAPMEAWT0sRIAAAEAAQABAKcAAAKFsKAUVgABsEATDAABsHAT/wABFYgAASgUAAABAAGwgBQ8AAFw8jgUAAABAEMAA6AgAAARYKBAAAAStWRlc/P4DwAAUAMBAPOAAmVp") \ No newline at end of file diff --git a/pr/451/docs/search.index/0ab5109df87a.js b/pr/451/docs/search.index/0ab5109df87a.js deleted file mode 100644 index 38fec4e4d..000000000 --- a/pr/451/docs/search.index/0ab5109df87a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAAUAAQDAEgUAAQQBoFAAABQMaPKeEgAAAQABAAKwQBOdAAGgoAAAFCxlcjowAAABAAAAAAAHABAAAAC3BrgGVQdWB9YP1w/LEMwQ84ADZ2lz") \ No newline at end of file diff --git a/pr/451/docs/search.index/0add2bce1a52.js b/pr/451/docs/search.index/0add2bce1a52.js deleted file mode 100644 index a3abca6f6..000000000 --- a/pr/451/docs/search.index/0add2bce1a52.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoCAAAA+3oDAAABO9YXLzAAF0AXEHAAABBAGgQAAAFEpl8mwUAAABAAEFADswAAABAAALAAUAPxAFALQSAQDkEwEAPRQAAFwUAADjAISgMAAAD7WFAAI7MAAAAQAACwAFAJwFAgCxBgQARQwBAHARAADGEgAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/0bdf527b5d3f.js b/pr/451/docs/search.index/0bdf527b5d3f.js deleted file mode 100644 index c3312182d..000000000 --- a/pr/451/docs/search.index/0bdf527b5d3f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICoFAAABLGoQAAABR8Y3QjAAKwQA+5AAFldTswAAABAAA3ABYAuQYBAMIMAQDjDAEAvg8BAD8QBQB1EAUA8hABAFARAQDhEQIAtBIDAOYSBAAXEwEAHhMBAC0TAQCzEwEAxhMBAOQTAQDoEwEADhQAADkUAQA9FAEAXBQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0c258f5e1776.js b/pr/451/docs/search.index/0c258f5e1776.js deleted file mode 100644 index 1aabec6b9..000000000 --- a/pr/451/docs/search.index/0c258f5e1776.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwCwEQIAYRMBAPgTAgABBADzsxEAAAEAAQAjAAKgYAAAEuFhcvPUEAAAAQDIAQGAAqEQAAAUh7CAFH0AAWl1NwQAgYexUBSkAAGgAAAAELSwcBPBAAGhMAAAFJgKQBsjgAKgMAAAEn5udg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0c57b4f8c6b4.js b/pr/451/docs/search.index/0c57b4f8c6b4.js deleted file mode 100644 index 1c751afaf..000000000 --- a/pr/451/docs/search.index/0c57b4f8c6b4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9F0TAAABAAEAAQABBADzqA8AAAEAAQABgAKgUAAAELugYAAAESppcgGAAqAgAAAMYqBgAAARKWVoAQEA814MAAABAAEAE4ECoBAAAAxhYWMBAwA7MAAAAQAABQACAFoMAwA9EQEAAQYA9PYSAAABAJkBAQATAAKwYBL4AAFmbfLPDAAAAQAjgAKgwAAAE8hucqcPAACIoEAAAA+lsEAPpgABNCAXOzAAAAEAABkAAwAHAgAAYwQOAN8LCQABgAOwcBN8AAGwMBMmAAGwgBO1AAFjcnMBAAGwIBN8AAFvOjAAAAEAAAAAAAkAEAAAAMMRxBHeEt8SzhPPEyUUJhSgFKEUAYACsCAJFAABsDATMQABcnT3BAAAyKAgAAAHaKBgAAAP1bCAEzEAAUAGkgn2EwIAAHQC2AQBAIIKAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/0d6fbddc62d7.js b/pr/451/docs/search.index/0d6fbddc62d7.js deleted file mode 100644 index 29056ac93..000000000 --- a/pr/451/docs/search.index/0d6fbddc62d7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoGAAABPvoKAAABPhYWkBCwD0fhMAAAEAAQABAAEEAPSPEAAAAQABAAEAI4ACsEAQ6wABbHLzAANjZHMB4BAAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0e276ddba088.js b/pr/451/docs/search.index/0e276ddba088.js new file mode 100644 index 000000000..fdbea5899 --- /dev/null +++ b/pr/451/docs/search.index/0e276ddba088.js @@ -0,0 +1 @@ +rn_("AQwA9MITAAABAAEAAQABBgDzchEAAAEAAQABggKwYBNIAAGw0BRoAAFpchOAArAQDOkAAWVpFwMAg4ixMBSJAAGgYAAAEXGgUAAAEQWwkBMYAAGwQBEBAAG0EAfzAAFh9D0JAAABAAgIAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/0e2c4243bbb8.js b/pr/451/docs/search.index/0e2c4243bbb8.js new file mode 100644 index 000000000..64d321b06 --- /dev/null +++ b/pr/451/docs/search.index/0e2c4243bbb8.js @@ -0,0 +1 @@ +rn_("AQAA9D0JAAABAAgIAQABCQGggAAAFLhmAZgTAAABAgD13BAAAAEAAQBfAgEA8wACaXU6MAAAAQAAAAAABwAQAAAAthDvEt4T3xMOFA8ULxQwFAEIAPVCFAAAAQABAAEAAQDzAgFm9ZoQAAABAAEAAQABABOAArCgFI4AAXJ08wACYXPzyhAAAAEAAQDzgAJ0dg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/0ed1801265ff.js b/pr/451/docs/search.index/0ed1801265ff.js new file mode 100644 index 000000000..d7b381034 --- /dev/null +++ b/pr/451/docs/search.index/0ed1801265ff.js @@ -0,0 +1 @@ +rn_("AQABoEAAABCtdPfzDAAARgPwAgEAuwCfAAEA8wABczswAAABAAAcAAgAtAQCACkMAwDwDwEAtxABAPgQAQDoEQEAphMHAJ0UBQAjgAKxEBSkAAFhdA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/103a5d03a0ae.js b/pr/451/docs/search.index/103a5d03a0ae.js new file mode 100644 index 000000000..0f4ce5587 --- /dev/null +++ b/pr/451/docs/search.index/103a5d03a0ae.js @@ -0,0 +1 @@ +rn_("AQUA9AUUAAABAE4AAQDzAwFj8gcTAAABACOAArBwFLwAAWhvAYICsAAS4wABsAAR6gABZXNDAAOgoAAAFFmgIAAAE5JlaXTy9hIAAAEAAQIAOzAAAAEAAAYAAQBkEQYAAQIBoEAAABQ6ZfJoFAAAAQDzgAJpbwEIAPT8EwAAAQCLAAUAEwEDsJAUYwABsFATQQFmZ2xwOzAAAAEAABIACQBpEAIA7REBAC4TAQBREwEAiBMBAO0TAQAlFAEANBQBAHUUAQABgAKwQBK4ARegIAAAEShhaQEDAPPXEgAAAQABACOBArAwFAwAAWNpAQAA9CkTAAABAFoBAQDzAwFzOzAAAAEAAA8AAwDoEQEAphMHAJ0UBQABAwD15hEAAAEAJQEBAGEB84ACcnYBAAA7MAAAAQAABQACALYSAQAOEwMA8wIBZDswAAABAAAJAAMA4hEDALESAwDlEgEAI4ACoJAAABRYaW8TgAKw8BSsAAFsbwEFAPR0FAAADAAjABEAI4ACoGAAABOGZ3MjgAKwkBRWAAFibPfHAACNsGAUjgABsHAT/wABsGATPwABsFATKwABtckXOjAAAAEAAAAAACIAEAAAALUFAQkvCV4MXwzfDOAM4QyWDpcOmA6ZDpsOnA78D/0P/g//DwAQ0BDREAwRLxGOEY8RkBGREeARNRNOE08TcBMoFIEUghQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/105593ddf996.js b/pr/451/docs/search.index/105593ddf996.js new file mode 100644 index 000000000..419958851 --- /dev/null +++ b/pr/451/docs/search.index/105593ddf996.js @@ -0,0 +1 @@ +rn_("AQEA9T8RAAABAAEAAQABACOBArCQFAcAAWF0Q4ADsRAUiQABsIATyQABY290EwACsAAUXAABbnL2DQIAAAEAewIBAKwCXAw=") \ No newline at end of file diff --git a/pr/451/docs/search.index/10ae14a3503e.js b/pr/451/docs/search.index/10ae14a3503e.js deleted file mode 100644 index 1f7e121fe..000000000 --- a/pr/451/docs/search.index/10ae14a3503e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOjAAAAEAAAAAAA4AEAAAAPoM+wz6EN4R3xHgEZkSohKjEqcSqBIAEwETOBM5EwEJAPNgEQAAAQABAAEKAPOxEgAAAQABAAEGAPUkEQAAAQABAAEAAQDzgAJkbnMAhLEgFCMAAQARA/ILEwAAAQABDQD2hRMAAAEArABYABgAAQABgAKwoBOdAAGgEAAADCRjdQEBAPSvEwAAAQBLAAEA8wABZfJjCQAAAQD3AACAxbAwEJ0AAQACqAg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/11349ef54c1b.js b/pr/451/docs/search.index/11349ef54c1b.js new file mode 100644 index 000000000..c2f04a25d --- /dev/null +++ b/pr/451/docs/search.index/11349ef54c1b.js @@ -0,0 +1 @@ +rn_("AQACoDAAABEcoJAAABOTMGj1AhIAAJkBAQABAAEAE4ACoDAAAA/MZHIjAISwEAj3A0CwIAwDAQygMAAAD/QSABj1nAUAAC0AAQABAAEAAQIA9LUUAAABAAoAAQABAgDzbxAAAAEAAQDzgAJpcgEBArBAEWEAAaBQAAATeGVz85UOAADiAgEAAQsA9qITAAABAKoAWQAYAAEA84ACbHTTgISgkAAAE1ABRAQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/116465ed6584.js b/pr/451/docs/search.index/116465ed6584.js deleted file mode 100644 index 2a7a8321b..000000000 --- a/pr/451/docs/search.index/116465ed6584.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9HcTAAABAAEAAQDzBQF0OzAAAAEAAAgAAgD0CwAAJgwHAAEFAPOTEwAAAQABADcAAICFsDAQMwABoKAAABOxoDAAABPuEEFI") \ No newline at end of file diff --git a/pr/451/docs/search.index/11a0ed9e6082.js b/pr/451/docs/search.index/11a0ed9e6082.js new file mode 100644 index 000000000..1c950b176 --- /dev/null +++ b/pr/451/docs/search.index/11a0ed9e6082.js @@ -0,0 +1 @@ +rn_("AQUA9UMQAAABAAEAXwIBAAEEAaAAAAAQ4XP1/gwAAAEAAQBBAwEAAQIA9asRAAABAAEAAQABAAGAArBgExYAAaAgAAARAG95AQABoMAAABQ7afRvEwAACAAcABsAAQUA9KgUAAABAAEAAQATgAKwcBSBAAFlaQECAPT+EAAAAQCaAAEAAQIBoEAAABNGcjswAAABAAAHAAIA/BABAJMRBQDzgAJhb2cDAACGsIATRQBasKATvAABDDAGOzAAAAEAAAgAAwAcDAAAxhEDAKwSAwDzggJlaUcBAACFsHATzAABoIAAABS4oEAAAA/3sEAE9PoIAADoBgEAtQMBgQKgIAAAEPqwUBK8AAFhbwEEAaAAAAAT5XMB+RAAAGMEA7BQEWsAAWNlc/YCCQAAAQABACwAAQBxCQEAADswAAABAAAkAAgAQgcHAEMQAgCcEQ0AyxECAKQSAQArEwEAfhMBABQUAgABBQDzHhMAAAEAAQABAQA7MAAAAQAABQACAP8LAwD2EQEAUwADsDAUVgABZXBy9OQPAAABAO8AAQABAAOwQBQcAAGhQAAAFI2gUAAAEexiY2YBCAcAAOOAhLFAFLUAAQIwEAEBhKCwAAAT2rBAELcAAbDAFAkAAaBgAAARkhQgBPbvBgAAYAkBADoBLgEXAQEDADswAAABAAAFAAEA3BIFAAEAAaCgAAAUR3L07g8AAAEA9wABAPOBAmdzAQcA9WYTAAABAAEAAQABAAGAAqBAAAARY7BQEUoAAWNp84ACYW8BAAGgIAAAEYNy8zQMAACBA3ABE4ACgAVyAB0ncngBAwKwQBMhAAGgMAAAE3lld/KOEAAAuwATgwKgcAAAE7NnbwECAPUAEwAAAQDyAH4AAQATgAKwMBMyAUlpcAEGAPOwEwAAAQABAPMFAmJzAUAQAAABAgD0HhAAAAEAAQABACOAAqBgAAASoGJj918BAM2wYBDfAAGgkAAAEqHATnIVOjAAAAEAAAAAABgAEAAAABoCGwKEBIUEhgQSB00Hfgd/B4AHgQeCB4MH+gt0DAQNOxBNEE4QGxGbEZsUnBSwFLEU") \ No newline at end of file diff --git a/pr/451/docs/search.index/11b42c6b0cf2.js b/pr/451/docs/search.index/11b42c6b0cf2.js new file mode 100644 index 000000000..740326da5 --- /dev/null +++ b/pr/451/docs/search.index/11b42c6b0cf2.js @@ -0,0 +1 @@ +rn_("AQEA9DsHAAABAAEAAQABAQGgQAAAEYlz9TUMAAAqBUoBAQABABOCAqAgAAANDGVyI4ADoEAAAAw0sDAJJgABZW90AQAA9DcHAAABAAEAAQABAgD0pBMAAAEADwABABOAArBAFFwAAWJ284ACb3gBCgD14BMAAAEAAQB4AAEAAYECoCAAABD6sFASvAABYW8BBAGgAAAAE+VzAfkQAABjAwOwUBFrAAFjZXP2AgkAAAEAAQAsAAEAcQkBAQA7MAAAAQAABQABANcMBQAjgAKwMBNBAWZydPMAhgHAB/LxAQAA/AU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/120ac1721451.js b/pr/451/docs/search.index/120ac1721451.js deleted file mode 100644 index bfe023233..000000000 --- a/pr/451/docs/search.index/120ac1721451.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsQAUogABsNAUkgABa20BCwA7MAAAAQAABgABAAIUBgAjgAKggAAAFC1lbyMEA6BgAAATwLBAFJ0AAWVucAERDAAAAQsA8+QTAAABAHcAdwAAAIWgYAAAEnmhAAAAFF4EgQM7MAAAAQAAOwAEAK4FBQBDBwAAlAczAGoRAABDgAOwgBFpASGgAAAAAhRka3Q=") \ No newline at end of file diff --git a/pr/451/docs/search.index/121d0e5bbae7.js b/pr/451/docs/search.index/121d0e5bbae7.js new file mode 100644 index 000000000..72a50666c --- /dev/null +++ b/pr/451/docs/search.index/121d0e5bbae7.js @@ -0,0 +1 @@ +rn_("AYACsRAUmwABsBAMUwABZW8BBQA7MAAAAQAADQABAJwRDQABAgDzBxAAAAEAAQABAgD0AxAAAAEAAQABAAEDADswAAABAAAIAAEAuRAIAIcHAACHsKATxgABsNAUXAABoFAAABQnjBQROzAAAAEAAAYAAwBWBwAA6hADALoSAQDzgAJsdBOAA6AgAAAMzrCgFEAAAWFkbg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1227057cb7dc.js b/pr/451/docs/search.index/1227057cb7dc.js new file mode 100644 index 000000000..57a34a0a1 --- /dev/null +++ b/pr/451/docs/search.index/1227057cb7dc.js @@ -0,0 +1 @@ +rn_("AQEAOzAAAAEAAAcAAQBTCQcAAQIA90wQAADbALkCAQABAHgAAQATgAKwMBDpAKRpdPMAAXPyjhQAAAEAM4ADsRAT/wABZG5w") \ No newline at end of file diff --git a/pr/451/docs/search.index/12ff91055174.js b/pr/451/docs/search.index/12ff91055174.js deleted file mode 100644 index a54e219a0..000000000 --- a/pr/451/docs/search.index/12ff91055174.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYADsAATyQABsAATywABsHAURAABNDZhAQQA9JkUAAABAAoAAQAjgQKgQAAAEUNtcAECADswAAABAAAOAAQAXgcBAPUMAQAzEQkAcREAAAEBADswAAABAAAIAAMAuhIAAC8TAACmFAYAAQEAOzAAAAEAAAoAAwBXEQIAoRMDAIwUAwAjgAKgMAAAED5kZ9cDAADGoIAAABKUQEaACDswAAABAAAYAAkA3Q8BAPsQAQAvEQEAPxEDALYRBQClEwMA9RMAAFQUAQCIFAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/135b5f2dd6d8.js b/pr/451/docs/search.index/135b5f2dd6d8.js new file mode 100644 index 000000000..b2d60dff6 --- /dev/null +++ b/pr/451/docs/search.index/135b5f2dd6d8.js @@ -0,0 +1 @@ +rn_("AQYA9BITAAABAJkBAQATAgKwYBMUAAFmbfLiDAAAAQAjggKwQBNDAAFlbAEFAPQFFAAAAQBOAAEA8wMBY/IHEwAAAQABgQKgAAAACSygMAAAD9NzdBOAAqAQAAAHTm5404CEsHAUvAABkEAB") \ No newline at end of file diff --git a/pr/451/docs/search.index/1363513dee0c.js b/pr/451/docs/search.index/1363513dee0c.js deleted file mode 100644 index d3875e06d..000000000 --- a/pr/451/docs/search.index/1363513dee0c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCoCAAABEMoDAAABFyaXQBAQA7MAAAAQAAjAACAGcHAABnCYsAYwADsAACFgABZGVsOjAAAAEAAAAAAAcAEAAAAK4EewV8BX0FawcpEXwTfRM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/139d49b47758.js b/pr/451/docs/search.index/139d49b47758.js deleted file mode 100644 index 7149e6204..000000000 --- a/pr/451/docs/search.index/139d49b47758.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA80wQAAABAAEAAYACoLAAABQMsDATVgABbnRDAAOgIAAAEQCwUBHXAAFlaGk7MAAAAQAABgADAP0LAADoEQMAtRMBAPOAAmRy") \ No newline at end of file diff --git a/pr/451/docs/search.index/1440b866445a.js b/pr/451/docs/search.index/1440b866445a.js new file mode 100644 index 000000000..f72ecf30a --- /dev/null +++ b/pr/451/docs/search.index/1440b866445a.js @@ -0,0 +1 @@ +rn_("AQAA9iARAAABAJQDAQAKAAEAAQAAOzAAAAEAAAYAAgCOEwAA9hMFACOIArBAFGwAAWVzAQ0BsEAUZgABZfUqFAAAAQABAAEAAQABBQA7MAAAAQAACAADAPwRAgACEwQAKRQAANcBAACFBbqAxTPrFIQC88sMAAABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/1461c0534145.js b/pr/451/docs/search.index/1461c0534145.js new file mode 100644 index 000000000..af435e1e5 --- /dev/null +++ b/pr/451/docs/search.index/1461c0534145.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAABYACQBZEAUAaxEBAPwRAgDQEgEAAhMEAAEUAQApFAAAWBQAAHcUAAATgAKw8BS8AAFmZyOAArBQE7wAAW1uAQUA8wEUAAABAHUA8wQBYwGLEQAAAQEBsHAUXAABcDswAAABAAAJAAIA1QcHANIQAQABAgA7MAAAAQAACwACANQTBQCSFAUA8wEBbTswAAABAAANAAQA0AwAAG0RAADpEgUAThQFABMAAqAwAAAM1GVz8gASAAABAAGCArBwFDgAAaBAAAARGGlz9wEAgIawkBQaAAGQSQg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1573b4768191.js b/pr/451/docs/search.index/1573b4768191.js new file mode 100644 index 000000000..02d471795 --- /dev/null +++ b/pr/451/docs/search.index/1573b4768191.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAI0AAgBzBwAAdQmMAAGEArBwFDgAAaBAAAARGGlzAQcA8/EQAAABABUBI4ACoLAAABLWYXQBAAOwMAkhAAGwMBOZAAGgIAAAB3RhZG8BFwIAADcDAICGoMAAABQwsAAUAwABAJgV") \ No newline at end of file diff --git a/pr/451/docs/search.index/16096c00710e.js b/pr/451/docs/search.index/16096c00710e.js new file mode 100644 index 000000000..0034e5343 --- /dev/null +++ b/pr/451/docs/search.index/16096c00710e.js @@ -0,0 +1 @@ +rn_("AQAA9HUQAAABALMAAQABhAKwABF+AAGwABGAAAE0NvMCAWHydQwAAAEAAQEA9sUSAAABAHMAAQBRAQEA84ACZW/zgAJoaw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/162fe2003634.js b/pr/451/docs/search.index/162fe2003634.js deleted file mode 100644 index cb0e8a47d..000000000 --- a/pr/451/docs/search.index/162fe2003634.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9FsHAABuCN8AAQABBQD0LhQAAAEAAQABAAEFAPQeEwAAAQCUAAEA8wICY3X1PBAAAAEAxQABAAEAYwAEoDAAAAwkoHAAABHGZW5zdvNaEAAAngAiAg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/16850befe1eb.js b/pr/451/docs/search.index/16850befe1eb.js deleted file mode 100644 index 351619b2e..000000000 --- a/pr/451/docs/search.index/16850befe1eb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9A0TAAABAFkBAQDzBAFzOzAAAAEAAA0AAgCJEwcAgRQFAEcAAASGoIAAABNpsQAUkAABsEASxwABsIAUoAABsFATDwABFQAW9IEOAAABAAEAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/169efb3fe210.js b/pr/451/docs/search.index/169efb3fe210.js deleted file mode 100644 index ae4c7e0c3..000000000 --- a/pr/451/docs/search.index/169efb3fe210.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBoDAAABBdYQFEDAAAAYECoCAAABDfsFASoAABYW8BAwGgAAAAE8hzAd4QAAAjgAKgAAAAB+ZydmMAA7BQEVAAAWNlczswAAABAAAIAAQA9QgCACMJAQBVEAIAhhIAAAGAArDQFJQAAaCQAAATkWJoJwEAgIWwUBEQAAGw8BOpAAGwIA+TAAFEIQTzgAJudA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/169f211e0328.js b/pr/451/docs/search.index/169f211e0328.js new file mode 100644 index 000000000..7a9623896 --- /dev/null +++ b/pr/451/docs/search.index/169f211e0328.js @@ -0,0 +1 @@ +rn_("AYACsHAUmQABoAAAAASmZmcBAQA7MAAAAQAABwABAEIMBwABAQGgcAAAEqBhARIHAAAjgAKhAAAAFKNkbnOAhKBgAAASnwBBSA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/16a23e4d7f90.js b/pr/451/docs/search.index/16a23e4d7f90.js new file mode 100644 index 000000000..db5a58d22 --- /dev/null +++ b/pr/451/docs/search.index/16a23e4d7f90.js @@ -0,0 +1 @@ +rn_("AQIBoKAAABNvZPLyBgAAAQABgQKgMAAAFHOgUAAAE/5jcwEEA7AgEUwAAaBQAAAR+KBAAAAUO2xzdvI2FAAAAQDzAANhZW87MAAAAQAACAADAIsEAQAEDAAAEA0FAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/16ed99e26abc.js b/pr/451/docs/search.index/16ed99e26abc.js new file mode 100644 index 000000000..0951c2e62 --- /dev/null +++ b/pr/451/docs/search.index/16ed99e26abc.js @@ -0,0 +1 @@ +rn_("AYACsCATRQBaoFAAABEybXNDgAOw4BQHAAEwEgALOzNkZ3IBAwDz1xIAAAEAAQAjgAKwMBQMAAFjaTOAA7AADQMDaWFpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1727786f31fa.js b/pr/451/docs/search.index/1727786f31fa.js new file mode 100644 index 000000000..8ede5a34d --- /dev/null +++ b/pr/451/docs/search.index/1727786f31fa.js @@ -0,0 +1 @@ +rn_("AQEA9fQHAAABAAEAAQABAAEAAqAwAAARHKCQAAATkzBo9QISAACZAQEAAQABABOAAqAwAAAPzGRyIwKEsBAI9wNAsCAMAwEMoDAAAA/0EgAY9ZwFAAAtAAEAAQABADOAhLCgEtAAAbBAD9YAARBJAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/178c38e077ae.js b/pr/451/docs/search.index/178c38e077ae.js new file mode 100644 index 000000000..e84586a50 --- /dev/null +++ b/pr/451/docs/search.index/178c38e077ae.js @@ -0,0 +1 @@ +rn_("AQAA9CkTAAABAFoBAQDzBAFzOzAAAAEAAA0AAgCmEwcAnRQFAEcAAASGoIAAABOGsQAUrAABsEAS4wABsIAUvAABsFATKwABFQAW9JYOAAABAAEAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/178ca3667736.js b/pr/451/docs/search.index/178ca3667736.js new file mode 100644 index 000000000..f52e61afa --- /dev/null +++ b/pr/451/docs/search.index/178ca3667736.js @@ -0,0 +1 @@ +rn_("AQABsGAUDgABZQFHFAAAAQYA9DkTAAABAFEBAQABBAD0UxMAAAEADwEBAPMAA2VyczowAAABAAAAAAAJABAAAAAjCSQJJQlFEYQRmBKZEqYSBxQIFA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/17ac3cf6f003.js b/pr/451/docs/search.index/17ac3cf6f003.js deleted file mode 100644 index 05164e3b5..000000000 --- a/pr/451/docs/search.index/17ac3cf6f003.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9C8HAAABAAEAAQABAQGgQAAAEW5z9SMMAAAhBUkBAQABABOCAqAgAAAM+WVyI4ADoEAAAAwisDAJGQABZW90AQAA9CsHAAABAAEAAQABAgD0hxMAAAEADwABABOAArBAFEEAAWJ284ACb3gBCgD1wxMAAAEAAQB6AAEAAYECoCAAABDfsFASoAABYW8BBAGgAAAAE8hzAd4QAABjAwOwUBFQAAFjZXP29QgAAAEAAQAsAAEAYgkBAQA7MAAAAQAABQABAMQMBQAjgAKwMBMkAWdydPMAhgHAB/LtAQAA9AU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/17b7a87bec39.js b/pr/451/docs/search.index/17b7a87bec39.js deleted file mode 100644 index 70b88d1dc..000000000 --- a/pr/451/docs/search.index/17b7a87bec39.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQDKoRAAABRfgAjyABrNoEAAABD6sGAUTwABoBAAAAxxsFAT5gABsDARXAABoAAAAAwvsSAUcQABsEAQ+AIiQE6wCTowAAABAAAAAAAUABAAAAAICQkJHAkdCfcL+Av5C/oLAAwFDMoMhRKxErISsxJtE24TgxOEE/YT9xM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/182999809797.js b/pr/451/docs/search.index/182999809797.js new file mode 100644 index 000000000..d7cde7931 --- /dev/null +++ b/pr/451/docs/search.index/182999809797.js @@ -0,0 +1 @@ +rn_("AQMBoAAAABNNc/TODwAA+AMBAAQAAQECsGAUPgABsEAUfAABZHQ7MAAAAQAADQAGAP4SAwBrEwEA2xMBAPITAQBxFAEAmRQBAAEBA7BwEgAAAbCAEtoAAYATgAAQ+G1uczswAAABAAALAAIA8QgFAGATBQCjgISwsBPJAAGxcBS8AAEIQIIBgAKwMBSwAAGxABQ+AAFjbgEBADswAAABAAAGAAEAwhQGAPMGAWX0bxQAAAEANAABABMBA7BAFJsAAbBgE4cAiWFydfN7EAAAeQIBAHcBAACFsDATMgFJEYEIOzAAAAEAAAYAAgCOEwAA9hMFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1834499c14e5.js b/pr/451/docs/search.index/1834499c14e5.js new file mode 100644 index 000000000..71da409f6 --- /dev/null +++ b/pr/451/docs/search.index/1834499c14e5.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAgAEAAAAAkJdxAiESMRJBH8E/0TiBSNFAECAPSyEAAAAQABAAEAE4ACoNAAABRzY3NTgAOgcAAAFG5laW+DgISwIBOgAAGgIAAAD+uwAAdSAAEKCAQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/18aeef727410.js b/pr/451/docs/search.index/18aeef727410.js new file mode 100644 index 000000000..e58733d80 --- /dev/null +++ b/pr/451/docs/search.index/18aeef727410.js @@ -0,0 +1 @@ +rn_("AQMA9gENAADrAloAAQDRAgEAAQQAOzAAAAEAAAoABACPEAUA0hIBADMTAQBZFAAAAYYCoAAAABNXoAAAABNYZHLzAgF2AYMTAADzgAJlbycCAADIoDAAAA/roHAAABKisRAUgQABoIAAABQosNATyQABoLAAABQoQGUICTswAAABAAAJAAMAvgUCAB8HBABaDAEAI4ACY0TMVIdIZHIBAAA6MAAAAQAAAAAACgAQAAAAUQlSCSIQcRNyE6IToxNNFKYUvhS/FBOAAqAwAAAQpWx084ACYWU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/190a93e1a007.js b/pr/451/docs/search.index/190a93e1a007.js deleted file mode 100644 index c53373144..000000000 --- a/pr/451/docs/search.index/190a93e1a007.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9DAJAAABAPoHAQABCQGggAAAFJxmAXsTAAABAgD1wRAAAAEAAQBdAgEA8wACaXU6MAAAAQAAAAAABwAQAAAAnBDTEsETwhPyE/MTFBQVFAEIAPUnFAAAAQABAAEAAQDzAgFm9YAQAAABAAEAAQABABOAArCgFHEAAXJ08wACYXPzsBAAAAEAAQDzgAJ0dg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/19705dd8c3fb.js b/pr/451/docs/search.index/19705dd8c3fb.js deleted file mode 100644 index 85c4db13b..000000000 --- a/pr/451/docs/search.index/19705dd8c3fb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoAAAABMwc/S2DwAA8wMBAAQAAQECsGAUIwABsEAUYQABZHQ7MAAAAQAADQAGAOISAwBOEwEAvhMBANUTAQBWFAEAfRQBAAEBA7BwEeUAAbCAEr4AAYATYwAQ+m1uczswAAABAAALAAIA5AgFAEMTBQCjgISwsBOsAAGxcBSgAAEIQIIBgAKwMBSUAAGxABQjAAFjbmcBAACFYI9wTDUWsDATFgFKEYEIOzAAAAEAAAYAAgBxEwAA2RMFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/19b75bdc92f3.js b/pr/451/docs/search.index/19b75bdc92f3.js new file mode 100644 index 000000000..9320e9851 --- /dev/null +++ b/pr/451/docs/search.index/19b75bdc92f3.js @@ -0,0 +1 @@ +rn_("AQsA9KQTAAABAA8AAQABBAGwQBNZAAFlAaMSAADzAAF0AWEUAAABDQDzeBQAADYAAQABEADzphQAABgAAQDzgQNrbXMBAAA7MAAAAQAABgACAI4TAAD2EwUAI4YCsEAUbAABZXMBBAD0xBAAAAEAAQABAPOAA2NydDOAA7BgEOQAAWFscg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/19c9dbfd629e.js b/pr/451/docs/search.index/19c9dbfd629e.js new file mode 100644 index 000000000..c9a2be50e --- /dev/null +++ b/pr/451/docs/search.index/19c9dbfd629e.js @@ -0,0 +1 @@ +rn_("AQDKoRAAABR6gAj/ABrdoEAAABEVsGAUagABoBAAAAyDsFAUAwABsDARdwABoAAAAAxBsSAUjgABsEAREwIjQE6wCTowAAABAAAAAAAUABAAAAAVCRYJKQkqCQgMCQwKDAsMEQwWDN0MoRLNEs4SzxKKE4sToBOhExIUExQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1a45b3f127a1.js b/pr/451/docs/search.index/1a45b3f127a1.js new file mode 100644 index 000000000..b5814e508 --- /dev/null +++ b/pr/451/docs/search.index/1a45b3f127a1.js @@ -0,0 +1 @@ +rn_("AQYAOzAAAAEAAAUAAQBgEwUAg4YEsFATJwABsHATkAABoNAAABRuYm5vdhOAArAQEvgAAXByAQABoDAAABHvdAGvEwAA8wIBc/KoEAAACwMTgAKgYAAAEJlldQECAPYKDQAAAQA/AwEALgEBAAGAArAAEuMAAYAOmjUAAWVzAQEA81cHAAAtDAEAY4ADsIAUAwABYm5084ADYW9yARQAOzAAAAEAAAUAAQCdFAUAAQAAOzAAAAEAAAYAAgCOEwAA9hMFACOIArBAFGwAAWVzAQQBsDATXgABY/OGEQAAAQABAAEFADswAAABAAAIAAMA/BECAAITBAApFAAA8wIDY2Vw88sMAAABAAEA84ACb3UBAAGwYBQOAAFlAUcUAADzAgFz9iMJAAABAAEAIAg/ACIBAQgA88ASAAABAAEAQ4ADoBAAABOSoOAAABQZZG9zAQIBoFAAABGFcgEmEQAAAYACoEAAABCloHAAABRZYWUBAAA7MAAAAQAACwACAPQGBQDcEgUA84ACdHYjAAKwEAfuAAFucvYYCQAAWAATBwEAtwABAPMAhzCRDDowAAABAAAAAAAIABAAAADJBMoEAxEEEfYS9xKAE4ETeRQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1a4760f711b7.js b/pr/451/docs/search.index/1a4760f711b7.js new file mode 100644 index 000000000..18520b819 --- /dev/null +++ b/pr/451/docs/search.index/1a4760f711b7.js @@ -0,0 +1 @@ +rn_("AYACsRAUmwABsBAMUwABZW8BBQA7MAAAAQAADQABAJwRDQABAgDzBxAAAAEAAQABAgD0AxAAAAEAAQABAAEDADswAAABAAAIAAEAuRAIAIcHAACHsKATxgABsNAUXAABoFAAABQnjBQROzAAAAEAAAYAAwBWBwAA6hADALoSAQDzgQJsdPMAAWk7MAAAAQAAjgADAJwEAAD3EAAACBKMAAEFAPN+FAAAAQA8AAEAAaBAAAAQ+2XzBQkAAAEAoQkTgAKgMAAAD95kcvMAAWXz7AYAAAEAAQDzgAJodAEBADswAAABAACNAAIA9xAAAAgSjAAjgAOwABAYAx+gUAAAECJnaXQBCAD0/BMAAAEAiwAFABMAhLDwFIAANLCQFGMAAbBQE0EBZkCJADswAAABAAASAAkAaRACAO0RAQAuEwEAURMBAIgTAQDtEwEAJRQBADQUAQB1FAEA1wEAAIawgBG0AAGgQAAACSgIMCY6MAAAAQAAAAAADQAQAAAAkQCSAGAHagkDDA8NChELEZ0SnhIlEyYTuBO5Ew==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1a65a2901852.js b/pr/451/docs/search.index/1a65a2901852.js new file mode 100644 index 000000000..62d20d0e6 --- /dev/null +++ b/pr/451/docs/search.index/1a65a2901852.js @@ -0,0 +1 @@ +rn_("AQIBoDAAABB3YQFWDAAAAYECoCAAABD6sFASvAABYW8BAwGgAAAAE+VzAfkQAAAjgAKgAAAAB/JydmMAA7BQEWsAAWNlczswAAABAAAIAAQAAgkCADAJAQBvEAIAohIAAAGAArDQFLAAAaCQAAATrmJoJwEAgIWwUBErAAGw8BPGAAGwIA+rAAFEIQTzgAJudA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1b5ac0a0d3eb.js b/pr/451/docs/search.index/1b5ac0a0d3eb.js new file mode 100644 index 000000000..6651ab407 --- /dev/null +++ b/pr/451/docs/search.index/1b5ac0a0d3eb.js @@ -0,0 +1 @@ +rn_("AQABsKAS0gABZTswAAABAAAJAAIA4wcHABQTAQABAAGgIAAADOttAYQFAAABgAKwoBLQAAGwQA/WAAFsbwEAAPQOEwAAAQABAAEA8wQBZPSxEgAAAQABAAEAIwKEsKAUVgABsEATDAABsHAT/wABFYAAASgUAAABAAGwgBQ8AAFw8jgUAAABAEMAA6AgAAARYKBAAAAStWRlc/P4DwAAUAMBAPOBAmVp1wIAAIawIBKWAAGw0BRAAAEXAQL0owQAAAEAAQDHAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1bb2d6d431c6.js b/pr/451/docs/search.index/1bb2d6d431c6.js new file mode 100644 index 000000000..7863e0a31 --- /dev/null +++ b/pr/451/docs/search.index/1bb2d6d431c6.js @@ -0,0 +1 @@ +rn_("AQAA9AoRAAABAJIBAQAjgQKgYAAAE0JiaQEAAPTyDwAAAQD1AAcBI4ICsFATVQABZXMBiQKwIBRxAAGgMAAAFHtpcAECAPQlEwAAAQCSAAEAAQMA9FsTAAABAJgAAQDzhQJidQECAPR1EQAAAQB8AQEA84ACaWy3AQAAhaAgAAATLRIBDDowAAABAAAAAAAUABAAAAByB/sH/Af9B/4HEAxKDEsMTAxNDBsQ4RCvEzEUfhR/FIMUkBSRFLcUuxQBBQDzFBQAAAEAAQBDhgOwkBSZAAGgAAAAErliZHcBgAKwAAkZAAGwIA0DA2lkbgEDAPPwDwAAAQAHAQECAPYaDAAAAQCOAwEA5gMBAAEAAPQ3CQAAAQABAAEAAQAAOzAAAAEAAAYAAgCOEwAA9hMFACOBArBAFGwAAWVz84ADZXh5AQAA9DMJAAABAAEAAQAjgAKwIBMhAAFyeAcNAADIoJAAABNwsFAQHAABsMATugABoBAAABFxsEAM5gABAEeACzowAAABAAAAAAAJABAAAABwBXEF7AztDAEN7A9GEEcQGBMZEwEGAPQjEwAAAQCSAAEA8wMBYvQIEQAAAQCSAQEAtwwAAImgcAAAEqewkBNTAAGwYBA+AAGgMAAAE3k4iRM6MAAAAQAAAAAALwAQAAAAwQXCBcMFxAU0BzUH+wj8CP0ILQkuCR8MMwz4DPkM+gz+DP8MAA3oD+kPPBA9EEEQQhBfEGAQYRBiEGMQZBBlEIcQjhCoEK0Q2RDaEEgRSRH9Eo8TsxPaE3QUgBSjFLQU") \ No newline at end of file diff --git a/pr/451/docs/search.index/1bcbe3047453.js b/pr/451/docs/search.index/1bcbe3047453.js new file mode 100644 index 000000000..bc62dab49 --- /dev/null +++ b/pr/451/docs/search.index/1bcbe3047453.js @@ -0,0 +1 @@ +rn_("AQADsCARTAABoFAAABH4oEAAABQ7bHN28jYUAAABAAECAPTXEQAAAQABAAEAE4QCsCAR2wABYXPzgAJhZROAAlLd8URqtnJzAQEAOzAAAAEAAAYAAQDCFAYA8wYBZfRvFAAAAQA0AAEAEwcDsEAUmwABsGAThwCJYXJ183sQAAB5AgEAI4ACoBAAAAwXZXIBAAA7MAAAAQAAIgAOAMQEAQCIBQoAJgkBAC0MAQDmDAEAmg4AALgPBADPDwAA8g8BAOgQAQD6EAAAjREAAOoRAQDvEQAAAQEAOjAAAAEAAAAAAAwAEAAAAAkJUwxUDHcQjBCNECIRIxEkEfwT/ROIFI0UAYECsAARfgABsAARgAABNDYBBQD0BRQAAAEATgABAPMEAWPyBxMAAAEAAQMBoEAAABQ6ZfJoFAAAAQABBQDz1xIAAAEAAQABBADzDBMAAAEAYQEBAAD0DhMAAAEAAQABAPMEAWT0sRIAAAEAAQABAOcSAAGJsKAUVgABoDAAABEosGAUjgABsHAT/wABtcgEOjAAAAEAAAAAAAwAEAAAAAEJLwleDF8M/g//DwAQ4BFOE08TKBSBFIIUEYECErAgEdsAAWFzAQIA880SAAABAAEA9wAAgMWgEAAAEKWAADgIAQUA9MsSAAABAKkAAQABCAD0ihMAAAEAhwABADMBA7BAE1kAAWFiZfX0DAAAAQBFA2kC1QABAAA7MAAAAQAABgACAC0MAQC4DwQA84ICc3UBBgD0hRAAAAEAtwABAPMAAmJ09XoFAAABAOYOWAABAAEPAPQlFAAAAQBPAAEAAQQA9IMQAAABALcAAQAjgAKxABSLAAFvcGOBA7CAE6AAAWNzdAELADswAAABAAAGAAEAHhQGAAEMAPSIEwAAAQCrAAEAI4ACoHAAABRIZ3fzgAJlbwENAPN4FAAANgABAAEQAPOmFAAAGAABAAEBAbBgEsUAAXL3tgUAAAEAOgIwBAEA9wMBAAEDAbAADPMDRnP0KQwAAAEAAQABAAEDAPQnDAAAAQA5BQEAAYACoEAAABPdsDAS+AABYXPzgANucnYBCQD08BEAAAEAPwEBAAEHAPQREQAAAQCwAAEA84ACaGkBAQKwIAzxAAGwAAxpAAFlc/JjBwAAAQABBQDzHhMAAAEAAQDzBQFy9OQPAAABAO8AAQDzgAJiY9eeAQCOoCAAAAdloFAAABGKoOAAABQRsEAUuQABsNAUAwABl/ULOzAAAAEAABwADgAGAgIAZQQBABMJAAAjDAAAZwwBAGsMAADXDAUAlQ4AAHkQAQDNEAEAdxEBAMQRAQBWFAEAahQBAAGAAqCwAAAUc6AwAAARJWxuEwACsIAUPAABZXD0hxMAAIkAKAABAAEDAbBAFGYAAWX1KhQAAAEAAQABAAEAAQYA9L4TAAABAAEAAQABDwD0qBQAAAEAAQABAAECADswAAABAAAFAAEAkhQFAPMNAW07MAAAAQAABQABAE4UBQABBAD1tRIAAA4AAQCRAAEAAQACoBAAABFgsPAUsgABZWs7MAAAAQAACgAEABkUAAAeFAYASBQAAF8UAQABAgD0shAAAAEAAQABAAECAPR1EAAAAQCzAAEA98sAAIygUAAAERSxABSbAAGgoAAAE1C9tAM7MAAAAQAAJgANAPsLAwD4DwAAIxABADMRBwCJEQAA2hIBAOMSAQDnEgEASBMBAGYTBABCFAQASRQDALgUAAABAgA7MAAAAQAACwACANQTBQCSFAUA8wEBbTswAAABAAANAAQA0AwAAG0RAADpEgUAThQFABMBAqAwAAAM1GVz8gASAAABAAGJArAgFHEAAaAwAAAUe2lwEwADoCAAABMtoIAAABNCZWl19RAMAAA6AAEAAQABAAGGArCQFJkAAaAAAAASuWJkAYACsDATIQABoDAAABD4cnbXAAADhaAwAAAQbKAwAAATeRABEzowAAABAAAAAAAJABAAAAD7CPwI/QgtCS4JhxCOEEgRSRH9EgEGADowAAABAAAAAAAHABAAAADYD9kPphCnECMTJBO2E7cTAQcA9JsTAAABAAEAAQB3AAAAhqBwAAATs6BAAAATRqBwAAARJkNABTswAAABAAAQAAcAHAIAAFsHAQBkCQEA/BABAAgRAQCTEQUAmxIBAHd3AICLsLAUhgABoGAAABEmtUEP") \ No newline at end of file diff --git a/pr/451/docs/search.index/1be2131faf3c.js b/pr/451/docs/search.index/1be2131faf3c.js new file mode 100644 index 000000000..508d4e7b6 --- /dev/null +++ b/pr/451/docs/search.index/1be2131faf3c.js @@ -0,0 +1 @@ +rn_("AQAA9LEPAAABAAEAAQDzAgFzATIRAAABAAKwgBSQAAGgYAAAFLdkc/L3CAAAQAOTgISwwBPMAAGgEAAAEd0AwUA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1be4871c7cfc.js b/pr/451/docs/search.index/1be4871c7cfc.js new file mode 100644 index 000000000..51cb13252 --- /dev/null +++ b/pr/451/docs/search.index/1be4871c7cfc.js @@ -0,0 +1 @@ +rn_("AQQAOjAAAAEAAAAAAAcAEAAAANgP2Q+mEKcQIxMkE7YTtxMBBAD02g8AAAEAAQABAAEAiKEQAAAUeoAI/wAa3aBAAAARFbBgFGoAAaAQAAAMg7BQFAMAAbAwEXcAAbEgFI4AATnBAjowAAABAAAAAAARABAAAAAVCRYJKQkqCQgMCQwKDAsMEQwWDN0MoRKKE4sToBOhExIUExTzgANlaW8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1c28e9635eec.js b/pr/451/docs/search.index/1c28e9635eec.js deleted file mode 100644 index 64f4ac0e2..000000000 --- a/pr/451/docs/search.index/1c28e9635eec.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYAOzAAAAEAAAkAAQAzEQkAAQIAOzAAAAEAABMAAQALEBMAE4GEoFAAABD1oBAAAAwUoCAAAAzdHEAAE4ACsFATZwABYW4zgAOwMA+wAAFpc3Q=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1c36524cf913.js b/pr/451/docs/search.index/1c36524cf913.js deleted file mode 100644 index 9b685d998..000000000 --- a/pr/451/docs/search.index/1c36524cf913.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABN1sDARzwABaXTy2hIAAAEAAQMAOzAAAAEAAAYAAQBJEQYAAQgA9N8TAAABAIsABQATAQOwkBRIAAGwUBMkAWdnbHA7MAAAAQAAEgAJAE8QAgDSEQEAEhMBADQTAQBrEwEA0BMBAAoUAQAZFAEAWhQBAAGAArAwEcsAAbAwEc0AAXJ2AQQBsAASmgABZDswAAABAAAFAAIAxxEDAMkSAQC3BQABh7BQEpwBFrBgEyIAAZQJAzowAAABAAAAAAALABAAAADMDM0MzgyGDocO8RBzEXQRdRF2ERkTUxMjgAKgEAAADMtubwEDAPNMEAAAAQABACOAAqAAAAAL/WRyYwGEoFAAABFwoDAAABCKFAEQ8owHAAABABOAArBgEr4AAWNnE4ADoLAAABP9oEAAAA6FZW9z") \ No newline at end of file diff --git a/pr/451/docs/search.index/1c43b144cda4.js b/pr/451/docs/search.index/1c43b144cda4.js deleted file mode 100644 index d7c72fd39..000000000 --- a/pr/451/docs/search.index/1c43b144cda4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsAATyQABsAATywABNDYBBgD0mRQAAAEACgABAPMCAnJ2890PAAABABcE8wIBZTswAAABAAAFAAIAZRADABURAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1cbd4fd00132.js b/pr/451/docs/search.index/1cbd4fd00132.js deleted file mode 100644 index d15bae0da..000000000 --- a/pr/451/docs/search.index/1cbd4fd00132.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9egHAAABAAEAAQABAAEAAqAwAAARAaCQAAATdjBo9ecRAACXAQEAAQABABOAAqAwAAAPtGRyIwKEsBAI6gM7sCAL8gEKoDAAAA/cEgAY9ZIFAAAtAAEAAQABADOAhLCgErQAAbBAD74AARBJAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1cd53c18f140.js b/pr/451/docs/search.index/1cd53c18f140.js deleted file mode 100644 index 7da1ec9cb..000000000 --- a/pr/451/docs/search.index/1cd53c18f140.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9F0TAAABAAEAAQATAAKwgBMxAAFzdvRfCQAAAQCCCgEAE4ACsDAQ8gABbG4BgAKgAAAAEHugAAAAEHw0NvMDAXYB7wwAABMAAqBAAAARbmFz9yMMAAC8A2UBSQEBAAEAtAEBAgD0CRMAAAEAkQABAAEDAPQ+EwAAAQCYAAEA8wACYnU6MAAAAQAAAAAAEwAQAAAAPgc/B0AHwA/BD2sQbBCMEI0QIhEjEdUR1hEHEwgTFBMVEyUTmROaE/OAAmR0E4ACoCAAAAz5ZXIBBwGgQAAAFB9l8k0UAAABAAEAAPVRCQAAAQABAAEAAQAjgAKgoAAAFHtmcEcAAACFsDAPlwABoFAAABBesHAQ6wABsUAUpgABgSkA8mwFAAABAPOAAmx0lwEAgIaxMBQhAAGxEBRPAAGgsAAAE0ARRQg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1d05d97e46ea.js b/pr/451/docs/search.index/1d05d97e46ea.js deleted file mode 100644 index f67c21cf5..000000000 --- a/pr/451/docs/search.index/1d05d97e46ea.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9JQQAAABAAEAAQABAAA6MAAAAQAAAAAACgAQAAAAzg/PD7wQvRBIEdkR2hH2EvcSkBSRFPMBAXny8BMAAAEAAQADoKAAABQ+oCAAABN1sDARzwABZWl08toSAAABAAEDADswAAABAAAGAAEASREGAAEIAPTfEwAAAQCLAAUAEwEDsJAUSAABsFATJAFnZ2xwOzAAAAEAABIACQBPEAIA0hEBABITAQA0EwEAaxMBANATAQAKFAEAGRQBAFoUAQABgAKwMBHLAAGwMBHNAAFydgECAbAAEpoAAWQ7MAAAAQAABQACAMcRAwDJEgEAI4ECoJAAABQ9aW8BBwD0WRQAAAwAIgARAHcWAAGJsFAT8AABsFASnAEWsGATIgABtQkDOjAAAAEAAAAAAA4AEAAAAMwMzQzODIYOhw61ELYQ8RAUEXMRdBF1EXYRGRNTE0OAA6AgAAAMvqAQAAAMy21ubwEDAPRMEAAAAQABABkBAYACoLAAABQMsDATVgABbnRDAAOgIAAAEQCwUBHXAAFlaGk7MAAAAQAABgADAP0LAADoEQMAtRMBAPOAAmRyhwsAAAqAAfQAEAGAAfcAEAGgUAAAEXCgUAAAFEOgMAAAEH+gMAAAEIowMWNlaW9zdHV39YwHAAABACEI1gJdAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1d276fafa4c6.js b/pr/451/docs/search.index/1d276fafa4c6.js new file mode 100644 index 000000000..7c73b557f --- /dev/null +++ b/pr/451/docs/search.index/1d276fafa4c6.js @@ -0,0 +1 @@ +rn_("AQADoKAAABRZoCAAABOSsDAR6gABZWl08vYSAAABAAEDADswAAABAAAGAAEAZBEGAAEIAPT8EwAAAQCLAAUAEwEDsJAUYwABsFATQQFmZ2xwOzAAAAEAABIACQBpEAIA7REBAC4TAQBREwEAiBMBAO0TAQAlFAEANBQBAHUUAQABgAKwMBHmAAGwMBHoAAFydgECAbAAErYAAWQ7MAAAAQAABQACAOIRAwDlEgEAI4ECoJAAABRYaW8BBwD0dBQAAAwAIwARAHcLAAGIsFASuAEXsGATPwABlQkDOjAAAAEAAAAAAA4AEAAAAN8M4AzhDJsOnA7QENEQDBEvEY4RjxGQEZERNRNwEyOAAqAQAAAM3m5v") \ No newline at end of file diff --git a/pr/451/docs/search.index/1d98360e7dff.js b/pr/451/docs/search.index/1d98360e7dff.js deleted file mode 100644 index 6ad9fe823..000000000 --- a/pr/451/docs/search.index/1d98360e7dff.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9ZARAAABAAEAAQABAAGAArBgEvoAAaAgAAAQ5W95AQABoMAAABQgafRSEwAACAAcABsAAQMA9HcTAAABAAEAAQABBgD0jBQAAAEAAQABAAECAPTjEAAAAQCaAAEAAQIBoEAAABMpcjswAAABAAAHAAIA4RABAHgRBQDzgAJhb2cHAACHsIATKABasKATnwABDDAHOzAAAAEAAA4ABACMBAUACgwAAKsRAwCQEgMApwAAgIWg8AAAE7KwUBA1AAGgkAAAEW+UgQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1db6f0fddb6f.js b/pr/451/docs/search.index/1db6f0fddb6f.js new file mode 100644 index 000000000..973e52051 --- /dev/null +++ b/pr/451/docs/search.index/1db6f0fddb6f.js @@ -0,0 +1 @@ +rn_("AQMBsAAM8wNGc/QpDAAAAQABAAEAAQIA9CcMAAABADkFAQAjAAKgQAAADO9lcvK4BAAAAQABgAKgQAAAE92wMBL4AAFhcwECAaBAAAAUZWXyiRQAAAEA84AEbG5ydg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1dbb8572c4de.js b/pr/451/docs/search.index/1dbb8572c4de.js new file mode 100644 index 000000000..2fe0f305d --- /dev/null +++ b/pr/451/docs/search.index/1dbb8572c4de.js @@ -0,0 +1 @@ +rn_("AQcA9JQTAAABAAEAAQDzBQF0OzAAAAEAAAgAAgAFDAAAOAwHAAEFAPOwEwAAAQABADcAAICFsDAQTQABoKAAABPOoDAAABQLEEFI") \ No newline at end of file diff --git a/pr/451/docs/search.index/1ddac7964cac.js b/pr/451/docs/search.index/1ddac7964cac.js deleted file mode 100644 index 1ea25fb9f..000000000 --- a/pr/451/docs/search.index/1ddac7964cac.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBsFAPvgABY/SaBAAAAQABAMYAAQABoIAAABScZgF7EwAA8wYBc/LxDAAAjwQzAAOgYAAAFHxjaXPywBEAAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/1e2e1d96899e.js b/pr/451/docs/search.index/1e2e1d96899e.js deleted file mode 100644 index 90d7390bf..000000000 --- a/pr/451/docs/search.index/1e2e1d96899e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHASfAABsCAUlgABaW8BAAGw8BSWAAFrOzAAAAEAAAgAAwD9EwAAAhQGAC0UAADzBQFk8r4SAAABAAECAPPBEAAAAQABAPMAAWk6MAAAAQAAAAAABwAQAAAAnBDTEsETwhPyE/MTFBQVFCODArCwFHEAAWFzAYECoKAAABMqoEAAABBab3bzgIQBUAE=") \ No newline at end of file diff --git a/pr/451/docs/search.index/1e619d7f9016.js b/pr/451/docs/search.index/1e619d7f9016.js new file mode 100644 index 000000000..ed1139149 --- /dev/null +++ b/pr/451/docs/search.index/1e619d7f9016.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAgAEAAAAAkJdxAiESMRJBH8E/0TiBSNFAEBAqBAAAAUbqAwAAARKGVo9wEJAAAuAC8DAQCBBaECAQABAgD01xEAAAEAAQABABOBArAgEdsAAWFz84ACbm8BAgD0shAAAAEAAQABABMAAqDQAAAUc2Nz8iMQAAABAPOAA2Vpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1e76f3f2f3ae.js b/pr/451/docs/search.index/1e76f3f2f3ae.js new file mode 100644 index 000000000..7f6370cda --- /dev/null +++ b/pr/451/docs/search.index/1e76f3f2f3ae.js @@ -0,0 +1 @@ +rn_("AQsBoEAAABQ6ZfJoFAAAAQABBQD0AxIAAAEAAQABAAGDArAAEO8AAbBQE34AAWR3E4ACoKAAABPyZXYBAgD0ChEAAAEAkgEBAAEAAPTyDwAAAQD1AAcBI4ICsFATVQABZXMBAgD0JRMAAAEAkgABAAEDAPRbEwAAAQCYAAEA84UCYnUBAgD0dREAAAEAfAEBAPOAAmls8wADYnR1OjAAAAEAAAAAAA8AEAAAAHIH+wf8B/0H/gcbEOEQrxMxFH4UfxSDFJAUkRS3FLsUAQwA8xQUAAABAAEAAYACsAAJGQABoCAAAA0DZG4BBgD0IxMAAAEAkgABAPMDAWL0CBEAAAEAkgEBAJcGAACHsEAP8AABoHAAABKnsJATUwABOAkDOjAAAAEAAAAAACQAEAAAAMEFwgXDBcQFNAc1BzMM+Az5DPoM/gz/DAAN6A/pDzwQPRBBEEIQXxBgEGEQYhBjEGQQZRCoEK0Q2RDaEI8TsxPaE3QUgBSjFLQUAQMA9DkTAAABAFEBAQDzgAJjbXcBAIDFsYAUxAABQIEECA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1ec93e54aaec.js b/pr/451/docs/search.index/1ec93e54aaec.js deleted file mode 100644 index abbaa876a..000000000 --- a/pr/451/docs/search.index/1ec93e54aaec.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEAUYQABdDswAAABAAANAAYA4hIDAE4TAQC+EwEA1RMBAFYUAQB9FAEAE4ACsDATFgFKaXABBQDzkxMAAAEAAQATgAKgMAAAE+5leVMCA7BwE/sAAWJlcwEmEAAAEwACoCAAAAWaZm31gQAAANgGAQDUAQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/1edda76fba5c.js b/pr/451/docs/search.index/1edda76fba5c.js new file mode 100644 index 000000000..87259f6ce --- /dev/null +++ b/pr/451/docs/search.index/1edda76fba5c.js @@ -0,0 +1 @@ +rn_("AQACoCAAABOSsDAR6gABaXTy9hIAAAEAAQMAOzAAAAEAAAYAAQBkEQYAAQgA9PwTAAABAIsABQATAQOwkBRjAAGwUBNBAWZnbHA7MAAAAQAAEgAJAGkQAgDtEQEALhMBAFETAQCIEwEA7RMBACUUAQA0FAEAdRQBAAGAArAwEeYAAbAwEegAAXJ2AQQBsAAStgABZDswAAABAAAFAAIA4hEDAOUSAQC3BQABh7BQErgBF7BgEz8AAZQJAzowAAABAAAAAAALABAAAADfDOAM4QybDpwODBGOEY8RkBGRETUTcBMjgAKgEAAADN5ubwEDAPNmEAAAAQABACOAAqAAAAAMDmRyYwGEoFAAABGLoDAAABCkFAEQ8pgHAAABABOAA7BgEtoAAbAAD6sAAWNnchOAA6CwAAAUGaBAAAAOmmVvcw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1f0b55d3dbc2.js b/pr/451/docs/search.index/1f0b55d3dbc2.js deleted file mode 100644 index c2c8ca01c..000000000 --- a/pr/451/docs/search.index/1f0b55d3dbc2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9AkTAAABAJEAAQABAwD0PhMAAAEAmAABAPMFAmJ1OjAAAAEAAAAAAAcAEAAAAGsQbBAiESMR1RHWERQTFRMBAgD0WhEAAAEAewEBAAEFAPNgEQAAAQABAAEEAPSvEgAAAQCoAAEA84CEEQkA") \ No newline at end of file diff --git a/pr/451/docs/search.index/1f242d58f36b.js b/pr/451/docs/search.index/1f242d58f36b.js deleted file mode 100644 index de79ced3a..000000000 --- a/pr/451/docs/search.index/1f242d58f36b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoDAAABEBoJAAABN2MGj15xEAAJcBAQABAAEAE4ACoDAAAA+0ZHIjAISwEAjqAzuwIAvyAQqgMAAAD9wSABj1kgUAAC0AAQABAAEAAQIA9JkUAAABAAoAAQABAgDzVRAAAAEAAQDzgAJpcgEBArBAEUYAAaBQAAATW2Vz9IAOAADcAgEAkgIBCwD2hRMAAAEArABYABgAAQDzgAJsdNOAhKCQAAATMwFEBA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1f54ccf79fde.js b/pr/451/docs/search.index/1f54ccf79fde.js deleted file mode 100644 index f8889d6a0..000000000 --- a/pr/451/docs/search.index/1f54ccf79fde.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9JURAAABAAEAAQABAQDzsxEAAAEAAQATgAIhgW3VhgNlcwEKAPSlEwAAAQABAAEAAYACsCAUIwABoGAAABNyb3YBgAKwMBKpAAGgAAAAEypjZPOAhQUQBg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1f9a8e3654ea.js b/pr/451/docs/search.index/1f9a8e3654ea.js deleted file mode 100644 index dc966596a..000000000 --- a/pr/451/docs/search.index/1f9a8e3654ea.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBsIATUAABdfI3CQAAAQAjgAKwEAk1AAEzNgEGAPPWEAAAAQAVAROAArAAD54AAW91AQIA80UHAADuAQEAAQMA87ESAAABAAEAI4ACsAARqQABaGkBAAA7MAAAAQAAEwABAHgHEwAjgAKgMAAADMphZfOAAnJ0E4ACsDATnwABYWwBAAGggAAAFJxm8u0IAACOCgEHAPVJEwAAAQABAAEAAQAjgAKgUAAAEUhhb/MCAmZzOzAAAAEAAAgABABBBwAAcgcFAPEMAACAEQAAE4ACoEAAAAv8YW8BAwGwQBRhAAF08r4TAAABACOFAqBAAAAT7mJzIwADsEAQ6AABoFAAABFVYWJm9/oIAAABAEgAawcBAGEAAQATgQKgUAAAEeRndAGAAqBAAAAR0aAgAAATzWFpAQIA9DAJAAABAPoHAQABAgDzPgwAAAEAAQATgAOwEAkuAAGwMBQAAAFpdXkBAgA7MAAAAQAACwACALcTBQB1FAUA8wgBbTswAAABAAAMAAMAUhEAAM0SBQAzFAUAAYECoIAAABK6oHAAABQUYXPzgAJucrfbAQCNoUAAABRwoCAAAAkyoFAAAA/pN5UPOzAAAAEAADECBQD6AQAAIAIvAf0GAABpBwAA8wn+AA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/1fee3e0237a8.js b/pr/451/docs/search.index/1fee3e0237a8.js new file mode 100644 index 000000000..436d39989 --- /dev/null +++ b/pr/451/docs/search.index/1fee3e0237a8.js @@ -0,0 +1 @@ +rn_("AQAA9HoTAAABAAEAAQATAgKwgBNOAAFzdvRtCQAAAQCRCgEA8wABYwFlDAAAAQICoLAAABRYoLAAABRZY3Pz0BAAAAEAXgABgQKwUBHyAAGgwAAAFCdoafOCAmVp84ACZW8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/20173a2c01e8.js b/pr/451/docs/search.index/20173a2c01e8.js new file mode 100644 index 000000000..779c6c959 --- /dev/null +++ b/pr/451/docs/search.index/20173a2c01e8.js @@ -0,0 +1 @@ +rn_("AQQA884RAAABAAEAAYICsEASxwABsEASyQABNDYBCwDzyBMAAGoAAQABBgD0tRQAAAEACgABAPMCAXIBERQAAPMCAWU7MAAAAQAABQACAH8QAwAwEQEAE4ACoFAAABL9ZHTzAIQRAQH37xAAAAEAbgAdAAEAAQA8AQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/20f76c7fa42e.js b/pr/451/docs/search.index/20f76c7fa42e.js new file mode 100644 index 000000000..174b2ad11 --- /dev/null +++ b/pr/451/docs/search.index/20f76c7fa42e.js @@ -0,0 +1 @@ +rn_("AQAA9DsHAAABAAEAAQABhQKwcBQXAAGwMBNrAAFlcxMAA7BAD9QAAaBgAAARjGJlczswAAABAAASAAQAqwUBAMYGCgBREAEAqRADAAECAPO1BQAARwoBAAEDAPXMDwAAoQABAAQAAQABCgGgQAAAFGVl8okUAAABACMABKAwAAARHKCQAAATk7BgEroAATBjaGn1AhIAAJkBAQABAAEA84ACZHLnBAAAh7AQCPcDQLAgDAMBDKAwAAAP9BJBODswAAABAAASAAYAjAACAJwFAADJBQMAmgcFAN0HAwANDAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/2160978e1be3.js b/pr/451/docs/search.index/2160978e1be3.js deleted file mode 100644 index 0b7a63e19..000000000 --- a/pr/451/docs/search.index/2160978e1be3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQECsCAM3gABsAAMVwABZXPyVwcAAAEAAQUA8wITAAABAAEA8wQBcvTMDwAAAQDsAAEAE4ACoLAAABRDYXPzgAJiYw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/21816dd58603.js b/pr/451/docs/search.index/21816dd58603.js deleted file mode 100644 index 3a75737c1..000000000 --- a/pr/451/docs/search.index/21816dd58603.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYADsAATyQABsAATywABsHAURAABNDZhAQQA9JkUAAABAAoAAQAjgQKgQAAAEUNtcAEBADswAAABAAAIAAMAuhIAAC8TAACmFAYAxwEAAMWgQAAAED6ggAAAEpRABoAIOzAAAAEAABYACADdDwEA+xABAD8RAwC2EQUApRMDAPUTAABUFAEAiBQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/21a316596b65.js b/pr/451/docs/search.index/21a316596b65.js deleted file mode 100644 index 2ce56784d..000000000 --- a/pr/451/docs/search.index/21a316596b65.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsQAUogABsNAUkgABa20BCwDz5BMAAAEAdwAzAYSgYAAAEnmhAAAAFF4EgAM7MAAAAQAANAACAEMHAACUBzMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/21a6ad5e522a.js b/pr/451/docs/search.index/21a6ad5e522a.js deleted file mode 100644 index 16af45473..000000000 --- a/pr/451/docs/search.index/21a6ad5e522a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoDAAAAy7aQFcEwAAAQABsDARLwABbvJMBwAAAQABAQDzyAcAAM0HAQATAAKgUAAAEI5lc/VSBAAAAQABAAEAAQABhAKwoBP+AAGwoBQAAAFjcvMBAXT00REAAOoAAQABAAEDAbBgFE8AAWY7MAAAAQAABwADAPcLAwBtEwEA9hMBAPOAAmFl84CEACgF") \ No newline at end of file diff --git a/pr/451/docs/search.index/21f47a3ec65b.js b/pr/451/docs/search.index/21f47a3ec65b.js deleted file mode 100644 index 7cca81298..000000000 --- a/pr/451/docs/search.index/21f47a3ec65b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9ZARAAABAAEAAQABAAGAArBgEvoAAaAgAAAQ5W95AQABoMAAABQgafRSEwAACAAcABsAAQYA9IwUAAABAAEAAQABAgD04xAAAAEAmgABAAECAaBAAAATKXI7MAAAAQAABwACAOEQAQB4EQUA84ACYW9nAwAAhrCAEygAWrCgE58AAQwwBjswAAABAAAIAAMACgwAAKsRAwCQEgMA84ACZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/21fc35579d6a.js b/pr/451/docs/search.index/21fc35579d6a.js new file mode 100644 index 000000000..e748810d3 --- /dev/null +++ b/pr/451/docs/search.index/21fc35579d6a.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAUAAQCSFAUA8w0BbTswAAABAAAFAAEAThQFACMDA7BAEsMAAbAAE4cAiWVmczswAAABAAAYAAYAMxEHAOcSAQBmEwQAQhQEAEkUAwC4FAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/21fc60b1602f.js b/pr/451/docs/search.index/21fc60b1602f.js new file mode 100644 index 000000000..89b4046d0 --- /dev/null +++ b/pr/451/docs/search.index/21fc60b1602f.js @@ -0,0 +1 @@ +rn_("AQMBoAAAABPlcwH5EAAAAQMA9IgQAAABAAEAAQATgAKwUBCMAAFhaQGAArBwFLAAAbBwFDwAAWVyAYACoLAAABRzoDAAABElbG6XAgAAhqAgAAASn6AgAAAI+LAwDgYAARCRBTowAAABAAAAAAARABAAAACeBNYE1wTYBNkEaQmxD7IPsw+0D00ThxMLFBAUHBQdFDgUORQBAgA6MAAAAQAAAAAACgAQAAAAUQlSCSIQcRNyE6IToxNNFKYUvhS/FAEAADswAAABAAAGAAIAjhMAAPYTBQABAgGwMBNeAAFjOjAAAAEAAAAAABMAEAAAAPsMzA9mEGcQaBBtEG4QchBzEA8RghGGEYcRiBH/EVMTVBM6FGMUZBQBAwDzyBMAAGoAAQATgQKwQBFuAAFhZROAAqCwAAAUemFlAQEBsEAUZgABZfUqFAAAAQABAAEAAQATgAKgEAAADA9hb4cPAADIsDAJCQdusEAUbAABoIAAABKaQEyICfPyBwAAJQQ/AAGAAqCAAAATeKCQAAATUGFrAQMA88ASAAABAAEAI4CEsSAUvAABoAAAAAksoDAAAA/TBAQGAQgA9cQUAAABAAEAAQABABOKArBAFJsAAWFyk4CEoMAAABNMoQAAABQZAUAGAQMBoEAAABH5ZfMFDQAAFAQBAAEFAPS+EwAAAQABAAEAo4CEoIAAABM4sAATcwABEQAGAQYA9KgUAAABAAEAAQDzCAFt9KwSAAABAAEAAQABAgA7MAAAAQAABQABAJIUBQDzDQFtOzAAAAEAAAUAAQBOFAUAAQQA9w0NAAABAKcFDgABAJEAAQABAwGAE4AAEPhzOzAAAAEAAAUAAQBgEwUAI4ICoJAAABRiaXMBBQGgUAAAFCdo8roSAAABABOAArAwE3MAAW50AYACoIAAABOTsEAR8gABYWUBgAKgEAAAERugAAAAEWBucgEJAaBAAAAUZWXyiRQAAAEAI4ACoJAAABPLaWzHIwAACqAwAAARHLBQEycAAbDwFLIAAbBwE5AAAaDQAAAUbjBiY2VoaWtub3Y7MAAAAQAAHgALAA4MAADzEAMAAhIEAIwTAQCbEwMA0hMBABkUAAAeFAYAPBQBAEgUAABfFAEAAQICoLAAABRYoLAAABRZY3Pz0BAAAAEAXgABAQKwUBHyAAGgwAAAFCdoafLSEwAAAQDzgAJlaQEBAbCQE4wAAXP0shAAAAEAAQABAAEDAaAAAAAQ7nMBVRAAAPOAA2hpdAEBAaBQAAATC2MBzwwAAAEFAbCAFF8AAXY7MAAAAQAABwACANERAwDCEwMAAQEA9HUQAAABALMAAQDjgISwcBH2AAEOCAD3tQcA0LCQEpgAAaBQAAARFKCgAAATr6AQAAAHTkA/vSk7MAAAAQAAPQAYAMYEAADIBAAAmQUCABUHAwD7CwMA0gwBAMAPAgDODwAA+A8AAAEQAQAjEAEAMhEIAIkRAADaEgEA4xIBAOcSAQBIEwEAZhMEALwTAQDGEwEAyxMAAEIUBABJFAMAuBQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/221b8a1321dc.js b/pr/451/docs/search.index/221b8a1321dc.js deleted file mode 100644 index c862eecb1..000000000 --- a/pr/451/docs/search.index/221b8a1321dc.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEAUYQABdDswAAABAAANAAYA4hIDAE4TAQC+EwEA1RMBAFYUAQB9FAEAE4ACsDATFgFKaXABBQDzkxMAAAEAAQATgAKgMAAAE+5leVMAA7BwE/sAAWJlcwEmEAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/2239713765f5.js b/pr/451/docs/search.index/2239713765f5.js new file mode 100644 index 000000000..1fcbe605e --- /dev/null +++ b/pr/451/docs/search.index/2239713765f5.js @@ -0,0 +1 @@ +rn_("AQEBoEAAABRlZfKJFAAAAQABgAOgMAAAEqGw4BRAAAGwkBSLAAFhY22HAQCAhbAQES0AAbAQEUwAAaCgAAAUczEJAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/228447d4f222.js b/pr/451/docs/search.index/228447d4f222.js new file mode 100644 index 000000000..06e0dce17 --- /dev/null +++ b/pr/451/docs/search.index/228447d4f222.js @@ -0,0 +1 @@ +rn_("AQAA9HoTAAABAAEAAQDzBgFz8v8TAAABAAEBAbBQEvgAAWX3EwkAAFQDAQARBAEA3AMBAAEDAPS+EQAAAQABAAEAAQMA9LoRAAABAAEAAQDzgwJsc/OAAmN0U4ADoBAAAAzKYW9y") \ No newline at end of file diff --git a/pr/451/docs/search.index/22b02790a247.js b/pr/451/docs/search.index/22b02790a247.js deleted file mode 100644 index 19f7ae705..000000000 --- a/pr/451/docs/search.index/22b02790a247.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcAOzAAAAEAAIwAAgDcEAAA7RGLAAEDADswAAABAACLAAEA9A2LAAECADswAAABAAAHAAEAMAwHAPMAA2JpdDswAAABAACMAAIAmAQAANIEiwA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/22d00b1c59e3.js b/pr/451/docs/search.index/22d00b1c59e3.js new file mode 100644 index 000000000..991c379a4 --- /dev/null +++ b/pr/451/docs/search.index/22d00b1c59e3.js @@ -0,0 +1 @@ +rn_("AQIBsEAUfAABdDswAAABAAANAAYA/hIDAGsTAQDbEwEA8hMBAHEUAQCZFAEAE4ACsDATMgFJaXABBQDzsBMAAAEAAQATgAKgMAAAFAtleVMAA7BwFBcAAWJlcwFAEAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/235afbc24e7c.js b/pr/451/docs/search.index/235afbc24e7c.js new file mode 100644 index 000000000..1854553bd --- /dev/null +++ b/pr/451/docs/search.index/235afbc24e7c.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAAkABABcCQEAnxADAOIQAQDvEwEAAQIBsEAUfAABdDswAAABAAANAAYA/hIDAGsTAQDbEwEA8hMBAHEUAQCZFAEAE4ACsDATMgFJaXABiAKwABPmAAGwABPoAAE0NhOBAqAwAAARhWRzAYACoCAAAAx3sGAUFwABbngBBQDzsBMAAAEAAQATgAKgMAAAFAtlefMAhBIAA/JAEAAA5gDzgAJydA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/235b929c3e77.js b/pr/451/docs/search.index/235b929c3e77.js deleted file mode 100644 index 467a2045e..000000000 --- a/pr/451/docs/search.index/235b929c3e77.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAgAEAAAAPwIXRAHEQgRCRHfE+ATaxRwFAECAPSYEAAAAQABAAEAE4ACoNAAABRYY3NTgAOgcAAAFFNlaW+DgISwIBODAAGgIAAAD9OwAAdGAAEKCAQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/238fff737ae7.js b/pr/451/docs/search.index/238fff737ae7.js deleted file mode 100644 index 179227459..000000000 --- a/pr/451/docs/search.index/238fff737ae7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBsDATQQABY/NrEQAAAQABAAEDAYATYwAQ+nM7MAAAAQAABQABAEMTBQAjggKgkAAAFEdpcwcBAIDFsFATCwABoKAAABOusHATcwABoNAAABRTgAEYCPOAAmRy") \ No newline at end of file diff --git a/pr/451/docs/search.index/23a40eca4762.js b/pr/451/docs/search.index/23a40eca4762.js new file mode 100644 index 000000000..916463b93 --- /dev/null +++ b/pr/451/docs/search.index/23a40eca4762.js @@ -0,0 +1 @@ +rn_("AQYAOzAAAAEAAAkAAQBOEQkAAQIAOzAAAAEAABMAAQAlEBMAE4GEoFAAABEQoBAAAAwmoCAAAAzwHEAAE4ACsFAThAABYW4zgAOwMA/IAAFpc3Q=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2424bb274ca9.js b/pr/451/docs/search.index/2424bb274ca9.js new file mode 100644 index 000000000..8d7706aaf --- /dev/null +++ b/pr/451/docs/search.index/2424bb274ca9.js @@ -0,0 +1 @@ +rn_("AYICsCARRgABsDARxAABZXABBQD05g8AAAEA8AABAFOAA7CQEfQAAWFpcgGBArCgFBoAAbCgFBwAAWNyxwAAAIWwUAz2AAGgIAAACW+gIAAADB88AAE6MAAAAQAAAAAACgAQAAAAtQG2AXsE+gYfCSAJfRB+EPEQ8hAHEg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/243924e3924e.js b/pr/451/docs/search.index/243924e3924e.js deleted file mode 100644 index f27b68899..000000000 --- a/pr/451/docs/search.index/243924e3924e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9FsQAAABALIAAQABhAKwABFjAAGwABFlAAE0NvMCAWHyYwwAAAEAAQEA9qkSAAABAHIAAQBRAQEA84ACZW/zgAJoaw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/243cde139a13.js b/pr/451/docs/search.index/243cde139a13.js new file mode 100644 index 000000000..65c9b20f6 --- /dev/null +++ b/pr/451/docs/search.index/243cde139a13.js @@ -0,0 +1 @@ +rn_("AYECoCAAABSYsEAP/AABYnABBQD0ShMAAAEAmAABAPMDAWMBsBIAAAEMAbAAFIQAAXM7MAAAAQAABQABAJ0UBQDzgAJudgGAArAwDFcAAaAwAAAMWWlvAQMBoAAAABRicwH4EQAAAQQA9BIMAAABAAEAAQDXAwCAhrAABXQAAZFIAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/245720643b05.js b/pr/451/docs/search.index/245720643b05.js new file mode 100644 index 000000000..dd6c3b3e2 --- /dev/null +++ b/pr/451/docs/search.index/245720643b05.js @@ -0,0 +1 @@ +rn_("AQoA9KQTAAABAA8AAQABgwKgIAAAEEygMAAAEOlpdAEAAPQOEwAAAQABAAEA8wQBZPSxEgAAAQABAAEAI4aEsKAUVgABsEATDAABsHAT/wABFYAAAQACsDAQPgABsBATvAABc3TzlwUAAIEGAQABgAKwwBSGAAGgAAAAB1B0efOAhRHBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/247d5e509be4.js b/pr/451/docs/search.index/247d5e509be4.js deleted file mode 100644 index 8c75d2636..000000000 --- a/pr/451/docs/search.index/247d5e509be4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9AkTAAABAJEAAQABAgD04xAAAAEAmgABAAEDAPQ+EwAAAQCYAAEAAQIBoEAAABMpcjswAAABAAAHAAIA4RABAHgRBQDzAIQDQAg6MAAAAQAAAAAAFQAQAAAAPgc/B0AHwA/BD2sQbBCMEI0QIhEjEdUR1hEHEwgTFBMVEyUTUBNRE5kTmhM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/24b98fc4a43b.js b/pr/451/docs/search.index/24b98fc4a43b.js deleted file mode 100644 index c5e3f5edb..000000000 --- a/pr/451/docs/search.index/24b98fc4a43b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACoAAAABB7oAAAABB8NDYBgAOwABPJAAGwABPLAAGwcBREAAE0NmEBBAD0mRQAAAEACgABACOBAqBAAAARQ21wAQEAOzAAAAEAAAgAAwC6EgAALxMAAKYUBgDHAQACxaBAAAAQPqCAAAASlEAGgAg7MAAAAQAAFgAIAN0PAQD7EAEAPxEDALYRBQClEwMA9RMAAFQUAQCIFAEA8wACZXY6MAAAAQAAAAAAFQAQAAAADwIQAhECpgXvDJcPmA+1D9MPZRBmEGcQaBAVERYRqxKsEq0SrhKrExcUGBQBgQKwcBE/AAGwcBFBAAE0NiOAArAQB1UAAW5wAQMCsEATBQABoDAAABNcZXf0bRAAAAcAuQABABOBAqBwAAATlmdvAQQAOzAAAAEAAAYAAgDzCwAA/QwFAGOAA7BQD90AAWRzdFcBAACFoBAAABA+sSAUVAABESEBOzAAAAEAAAoAAwB9AAAAkwAFALwRAwA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/24fd72fc1f6c.js b/pr/451/docs/search.index/24fd72fc1f6c.js deleted file mode 100644 index 7255a9f81..000000000 --- a/pr/451/docs/search.index/24fd72fc1f6c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9vcMAAABADgDAQAtAQEAAQoA87gSAAABAKwAAQEA9xIJAAABAFAHAQByAAEAFQEjgAKwYBPSAAFpbwECAPS1DAAAAQBEBgEAAQUA9OgTAAABAFAAAQD3CAAAiLCQE58AAaAgAAAMt7CQEd8AAQTcAzowAAABAAAAAAAIABAAAACLBQMJBAlRDFIM5hDnEOsS7BI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/254b3dc1eadd.js b/pr/451/docs/search.index/254b3dc1eadd.js new file mode 100644 index 000000000..e6a846551 --- /dev/null +++ b/pr/451/docs/search.index/254b3dc1eadd.js @@ -0,0 +1 @@ +rn_("AQACoCAAABOSsDAR6gABaXTy9hIAAAEAAQMAOzAAAAEAAAYAAQBkEQYAAQgA9PwTAAABAIsABQATAQOwkBRjAAGwUBNBAWZnbHA7MAAAAQAAEgAJAGkQAgDtEQEALhMBAFETAQCIEwEA7RMBACUUAQA0FAEAdRQBAAGAArAwEeYAAbAwEegAAXJ2AQQBsAAStgABZDswAAABAAAFAAIA4hEDAOUSAQC3BQABh7BQErgBF7BgEz8AAZQJAzowAAABAAAAAAALABAAAADfDOAM4QybDpwODBGOEY8RkBGRETUTcBMjgAKgEAAADN5ubwEDAPNmEAAAAQABACOAAqAAAAAMDmRyYwGEoFAAABGLoDAAABCkFAEQ8pgHAAABABOAArBgEtoAAWNnE4ADoLAAABQZoEAAAA6aZW9z") \ No newline at end of file diff --git a/pr/451/docs/search.index/25672b9a3070.js b/pr/451/docs/search.index/25672b9a3070.js new file mode 100644 index 000000000..b31379f81 --- /dev/null +++ b/pr/451/docs/search.index/25672b9a3070.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAABEABQCuBQAAMgwAAOgMAAAKEA0AcBEAAAGEArDQFLAAAaCQAAATrmJoAYQCsAARfgABsAARgAABNDaXBAAAh6AgAAAR3aAwAAARRKAwAAASqLBwD+IAActACDswAAABAAARARQAuAEAAKQFAAD4CAAA/ggAABsJAgAMDAAATgwBAHUMAQDrDAAAGA3tAMoPAADfDwEA1hAAAJIRAADwEgEA1BMFAPETAAB4FAAAkhQFAK4UAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/25fce273e5ba.js b/pr/451/docs/search.index/25fce273e5ba.js deleted file mode 100644 index 9c0528358..000000000 --- a/pr/451/docs/search.index/25fce273e5ba.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9OgRAAABAAEAAQAjAQKgcAAAEedhcgFsBwAAAQMAOzAAAAEAAAgAAgCKDwYAQRMBAPMAAmN0OzAAAAEAAFcAJwCfBAsAZQUAAAEJAQAVDAEAHQwCANwMAADoDAIA+gwBALQPAAC5DwEAvA8BAMYPAABMEAIAUxABAFgQAQB9EAAA9BAAAPoQAABGEQEAUxEBAGcRAQBrEQIAyxEBAN4RAgDkEQAAmRIAAKISAQCnEgEA8BIBAAATAQAFEwEAJhMBADYTAwA8EwEAnRMBALETAAAfFAAASBQEAFEUAgA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2602dc5a7bc0.js b/pr/451/docs/search.index/2602dc5a7bc0.js new file mode 100644 index 000000000..a60d9687a --- /dev/null +++ b/pr/451/docs/search.index/2602dc5a7bc0.js @@ -0,0 +1 @@ +rn_("AYADsAAT5gABsAAT6AABsHAUXwABNDZhAQQA9LUUAAABAAoAAQAjgQKgQAAAEV5tcAECADswAAABAAAOAAQAagcBAAgNAQBOEQkAjBEAAAEBADswAAABAAAIAAMA1hIAAEwTAADCFAYAAQEAOzAAAAEAAAoAAwByEQIAvhMDAKgUAwAjgAKgMAAAEFhkZ9cDAADGoIAAABKwQEaACDswAAABAAAYAAkA9Q8BABYRAQBKEQEAWhEDANERBQDCEwMAERQAAG8UAQCkFAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/2609108e1ba5.js b/pr/451/docs/search.index/2609108e1ba5.js deleted file mode 100644 index 2195bbc42..000000000 --- a/pr/451/docs/search.index/2609108e1ba5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA86QSAAABAAEAI4ECsSAUoAABY2sBBAGgQAAAEd5l8/IMAAAMBAEAAQAAOzAAAAEAAAUAAgDYEAMAbxMBAHOAhKAwAAAUHwgwAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/260cd4c5a9b2.js b/pr/451/docs/search.index/260cd4c5a9b2.js deleted file mode 100644 index a6a30261b..000000000 --- a/pr/451/docs/search.index/260cd4c5a9b2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkBoIAAABScZgF7EwAAAQIA9cEQAAABAAEAXQIBAPMAAml1OjAAAAEAAAAAAAcAEAAAAJwQ0xLBE8IT8hPzExQUFRQBCAD1JxQAAAEAAQABAAEA8wIBZvWAEAAAAQABAAEAAQATgAKwoBRxAAFydPMCAmFz87AQAAABAAEAAQQBsEAUSwABZfUPFAAAAQABAAEAAQABAgA7MAAAAQAACwACALcTBQB1FAUA8wMBbTswAAABAAANAAQAvQwAAFIRAADNEgUAMxQFAPOBAmNl84ACYWU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2685e428a65d.js b/pr/451/docs/search.index/2685e428a65d.js deleted file mode 100644 index 2da8ace07..000000000 --- a/pr/451/docs/search.index/2685e428a65d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoEAAABHeZfPyDAAADAQBAAEGAPT7EAAAAQC+AAEAE4EDoAAAABFDsSAUiAABYWRuAQUA9G4QAAABAAEAAQABFAD1qBQAAAEAAQABAAEAAQUBsIAURAABdjswAAABAAAHAAIAthEDAKUTAwAjgAKwcBHbAAFiZFcCAAGGsMATbwABoKAAABOSoJAAABNbBeAC9QoHAAABAAEA3QiSBAEGAPO4EgAAAQCsAPOAA3J0dgEDADswAAABAAAHAAIAoRMDAIwUAwDzAAFzAUcUAADzAAFzAd0RAADHAgCAh6CAAAATG6AwAAAML7AAE1YAAaAQAAAM2BNADg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/26b19c61c64e.js b/pr/451/docs/search.index/26b19c61c64e.js new file mode 100644 index 000000000..42c510e6e --- /dev/null +++ b/pr/451/docs/search.index/26b19c61c64e.js @@ -0,0 +1 @@ +rn_("AYkCsCAUcQABoDAAABR7aXATAAOgIAAAEy2ggAAAE0JlaXX1EAwAADoAAQABAAEAAYYCsJAUmQABoAAAABK5YmQBAgD0GgwAAAEAjgMBAAEAAPQ3CQAAAQABAAEAAQAAOzAAAAEAAAYAAgCOEwAA9hMFACOBArBAFGwAAWVz84ADZXh5AQAA9DMJAAABAAEAAQAjgAKwIBMhAAFyeEcBAADGsMATugABoBAAABFxsEAM5gABoDAAABD4AESAC/RwBQAAAQB7BwEA1wAAAIWgMAAAEGygMAAAE3kQARM6MAAAAQAAAAAACgAQAAAA+wj8CP0ILQkuCR8MhxCOEEgRSRH9Eg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2713334de97c.js b/pr/451/docs/search.index/2713334de97c.js new file mode 100644 index 000000000..414ef0230 --- /dev/null +++ b/pr/451/docs/search.index/2713334de97c.js @@ -0,0 +1 @@ +rn_("AQAA9CkTAAABAFoBAQDzBAFzOzAAAAEAAA0AAgCmEwcAnRQFAEcAAAGGoIAAABOGsQAUrAABsEAS4wABsIAUvAABsFATKwABFQAW9JYOAAABAAEAAQABBQDz/g8AAAEAAQBjgAOwQBCMAAFtbm8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2725d533e78b.js b/pr/451/docs/search.index/2725d533e78b.js new file mode 100644 index 000000000..164ab15b6 --- /dev/null +++ b/pr/451/docs/search.index/2725d533e78b.js @@ -0,0 +1 @@ +rn_("AQMA9gENAADrAloAAQDRAgEAAQQAOzAAAAEAAAoABACPEAUA0hIBADMTAQBZFAAAAYYCoAAAABNXoAAAABNYZHLzAgF2AYMTAADzgAJlbycCAADIoDAAAA/roHAAABKisRAUgQABoIAAABQosNATyQABoLAAABQoQGUICTswAAABAAAJAAMAvgUCAB8HBABaDAEAI4ACY0TMVIdIZHIBAAA6MAAAAQAAAAAACgAQAAAAUQlSCSIQcRNyE6IToxNNFKYUvhS/FBOAAqAwAAAQpWx0w4AEoAAAABCVoAAAABCWNDZhZQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/275265ed50a5.js b/pr/451/docs/search.index/275265ed50a5.js new file mode 100644 index 000000000..f9e3849c8 --- /dev/null +++ b/pr/451/docs/search.index/275265ed50a5.js @@ -0,0 +1 @@ +rn_("AYQCsAARfgABsAARgAABNDbzAAFh8nUMAAABACOAArCQEYQBImx0AYACsHATmQABsIAT0gABY3MBAAA6MAAAAQAAAAAACQAQAAAA3hHfEfoS+xLrE+wTQBRBFLwUvRTzgAJkZScBAICFoNAAABQRsBAEwQABoDAAABB4AggL") \ No newline at end of file diff --git a/pr/451/docs/search.index/27dc20cd64df.js b/pr/451/docs/search.index/27dc20cd64df.js new file mode 100644 index 000000000..306184a54 --- /dev/null +++ b/pr/451/docs/search.index/27dc20cd64df.js @@ -0,0 +1 @@ +rn_("AQUA9JQTAAABAAEAAQABAAD1egwAAAEAAQABAAEA84ACZ2sTgAKwsBPvAAFhbwGAAqAgAAAL+qBAAAAMM2V0AYUCsHAUFwABsDATawABZXMTAAOwQA/UAAGgYAAAEYxiZXM7MAAAAQAAEgAEAKsFAQDGBgoAURABAKkQAwABAwD0bRAAAAEABAABAAEKAaBAAAAUZWXyiRQAAAEAE4ACsGASugABY2nzgAJkcvMAAmVvOzAAAAEAAAkAAgCaBwUA3QcDAAECAbAwEVgAAW47MAAAAQAACQABAPALCQDzgAJvdAEDADswAAABAADuAAIAdgcAAAIL7QABAgGgcAAAEf9pOzAAAAEAAAABAgB1BwAAAgr/ABOAAqBAAAAMMnJ1AQQA9EoTAAABAJgAAQABAwDz1BIAAAEArQDzgAJhb9cHAACIsEAM/AABsTAUhgABNIEWOzAAAAEAAB4ADQC/BAEAggUBAAUMAAA4DAcAXAwBAOYPAQDXEAEAYxEAALARAwD0EQEAsBIAABITAQCsFAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/28125c7f5b42.js b/pr/451/docs/search.index/28125c7f5b42.js deleted file mode 100644 index 767ef40b0..000000000 --- a/pr/451/docs/search.index/28125c7f5b42.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQsA9FkUAAAMACIAEQABCgA6MAAAAQAAAAAABwAQAAAANBM1E9AT0RMKFAsUWhRbFPOBAm90EwCEsCATKABasDARLwABoFAAABEXEDACOzAAAAEAAAgABACDAAEArQADAEwHAQBhCQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/28183bdd4ff4.js b/pr/451/docs/search.index/28183bdd4ff4.js deleted file mode 100644 index 8a212e154..000000000 --- a/pr/451/docs/search.index/28183bdd4ff4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICsEASqwABsEASrQABNDYBCwDzqxMAAGwAAQABBgD0mRQAAAEACgABAPMCAXIB9RMAAPMDAWU7MAAAAQAABQACAGUQAwAVEQEA84ADYWVp") \ No newline at end of file diff --git a/pr/451/docs/search.index/285de43c88ed.js b/pr/451/docs/search.index/285de43c88ed.js deleted file mode 100644 index fbe65b30c..000000000 --- a/pr/451/docs/search.index/285de43c88ed.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABHCoDAAABKMYmgBsg8AAAETAbAAFGcAAXM7MAAAAQAABQABAIEUBQABBwDzjRIAAAEAAQDzgANkcnQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2888725fe937.js b/pr/451/docs/search.index/2888725fe937.js deleted file mode 100644 index 4a8a38fe6..000000000 --- a/pr/451/docs/search.index/2888725fe937.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQAzAwOwYBNqAIphcnXzYRAAAHcCAQAjgAKAEWAAEAFkcg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/28d7b5f5abc8.js b/pr/451/docs/search.index/28d7b5f5abc8.js deleted file mode 100644 index b5b094de1..000000000 --- a/pr/451/docs/search.index/28d7b5f5abc8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9u4MAADmAlgAAQDPAgEAAQQAOzAAAAEAAAoABAB1EAUAthIBABcTAQA+FAAAAYYCoAAAABM6oAAAABM7ZHLzAgF2AWYTAADzgAJlbycBAADHoDAAAA/ToHAAABKGoIAAABQNsNATrAABoLAAABQNQEUICTswAAABAAAJAAMAtAUCABMHBABIDAEAI4ACKBg73U/0ZHIBAAA6MAAAAQAAAAAACgAQAAAARAlFCQgQVBNVE4UThhMyFIoUohSjFBOAAqAwAAAQi2x0w4AEoAAAABB7oAAAABB8NDZhZQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/29212c997b44.js b/pr/451/docs/search.index/29212c997b44.js new file mode 100644 index 000000000..d9630d35e --- /dev/null +++ b/pr/451/docs/search.index/29212c997b44.js @@ -0,0 +1 @@ +rn_("AQEA9GcHAAB6COEAAQABBQD0SRQAAAEAAQABAAEFAPQ7EwAAAQCUAAEA8wICY3X1VhAAAAEAxgABAAEA84ACbnM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2923606a2790.js b/pr/451/docs/search.index/2923606a2790.js new file mode 100644 index 000000000..1a702708b --- /dev/null +++ b/pr/451/docs/search.index/2923606a2790.js @@ -0,0 +1 @@ +rn_("AQAEoAAAABCZsCARTAABoFAAABH4oEAAABQ7ZGxzdjswAAABAAATAAYArgUAADIMAADoDAAAChANAHARAAA2FAEAAQIA9NcRAAABAAEAAQAThAKwIBHbAAFhc/OAAmFl") \ No newline at end of file diff --git a/pr/451/docs/search.index/29503497fefb.js b/pr/451/docs/search.index/29503497fefb.js new file mode 100644 index 000000000..5b9c9310e --- /dev/null +++ b/pr/451/docs/search.index/29503497fefb.js @@ -0,0 +1 @@ +rn_("AYACoAAAABCVoAAAABCWNDYBgAOwABPmAAGwABPoAAGwcBRfAAE0NmEBBAD0tRQAAAEACgABACOBAqBAAAARXm1wAQEAOzAAAAEAAAgAAwDWEgAATBMAAMIUBgDHAQACxaBAAAAQWKCAAAASsEAGgAg7MAAAAQAAFgAIAPUPAQAWEQEAWhEDANERBQDCEwMAERQAAG8UAQCkFAEA8wACZXY6MAAAAQAAAAAAFQAQAAAAEwIUAhUCsAUCDa8PsA/ND+sPfxCAEIEQghAwETERxxLIEskSyhLIEzIUMxQBgQKwcBFaAAGwcBFcAAE0NiOAArAQB2EAAW5wAQMCsEATIQABoDAAABN5ZXf0hxAAAAcAugABABOBAqBwAAATs2dvAQQAOzAAAAEAAAYAAgAEDAAAEA0FAGOAA7BQD/UAAWRzdFcBAACFoBAAABBYsSAUbwABESEBOzAAAAEAAAoAAwB+AAAAlAAFANcRAwA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/29a4091c28ec.js b/pr/451/docs/search.index/29a4091c28ec.js new file mode 100644 index 000000000..7723da4fa --- /dev/null +++ b/pr/451/docs/search.index/29a4091c28ec.js @@ -0,0 +1 @@ +rn_("AQAA9HoTAAABAAEAAQABBADzwA8AAAEAAQABgAKgUAAAENagYAAAEUVpcgGAAqAgAAAMdKBgAAARRGVoAQEA83AMAAABAAEAE4ECoBAAAAxzYWMBAwA7MAAAAQAABQACAGwMAwBYEQEAAQYA9BITAAABAJkBAQATAAKwYBMUAAFmbfLiDAAAAQAjgAKgwAAAE+VucqcPAACIoEAAAA+9sEAPvgABNCAXOzAAAAEAABkAAwALAgAAbAQOAPALCQABgAOwcBOZAAGwMBNDAAGwgBPSAAFjcnMBAAGwIBOZAAFvOjAAAAEAAAAAAAkAEAAAAN4R3xH6EvsS6xPsE0AUQRS8FL0UAYACsCAJIQABsDATTgABcnT3BAAAyKAgAAAHdKBgAAAP7bCAE04AAUAGkgn2FwIAAHkC3QQBAJEKAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/29ac0c736fc1.js b/pr/451/docs/search.index/29ac0c736fc1.js deleted file mode 100644 index bd4892c24..000000000 --- a/pr/451/docs/search.index/29ac0c736fc1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9A0TAAABAFkBAQDzBAFzOzAAAAEAAA0AAgCJEwcAgRQFAEcAAAWGoIAAABNpsQAUkAABsEASxwABsIAUoAABsFATDwABFQAW9IEOAAABAAEAAQBDAISgIAAACR6g0AAAE3WgMAAACP4VAATz/wEAAAEAaAU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/29d270c7a752.js b/pr/451/docs/search.index/29d270c7a752.js deleted file mode 100644 index 4a87c955c..000000000 --- a/pr/451/docs/search.index/29d270c7a752.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9K8SAAABAKgAAQABCAD0bRMAAAEAiAABADMBA7BAEzwAAWFiZfXhDAAAAQA+A2cC1AABAAA7MAAAAQAABgACABsMAQCgDwQA84ACc3UjgQKwgBNnAAFydAGAArBwEu0AAaAQAAAMwW56AQYA9GsQAAABALYAAQDzAANiaXT1cAUAAAEA1Q5XAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/29dd87e5e6be.js b/pr/451/docs/search.index/29dd87e5e6be.js deleted file mode 100644 index 7785ff938..000000000 --- a/pr/451/docs/search.index/29dd87e5e6be.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBsAAM4AM/c/QXDAAAAQABAAEAAQIA9BUMAAABADAFAQAjAAKgQAAADNxlcvKvBAAAAQABgAKgQAAAE8CwMBLcAAFhcwECAaBAAAAUSmXybBQAAAEA84AEbG5ydg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2a56df498b28.js b/pr/451/docs/search.index/2a56df498b28.js deleted file mode 100644 index 47009c09b..000000000 --- a/pr/451/docs/search.index/2a56df498b28.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoFAAABRDcwGEEgAAAYICsEASqwABsEASrQABNDYBCwDzqxMAAGwAAQABBgD0mRQAAAEACgABAPMCAXIB9RMAAPMDAWU7MAAAAQAABQACAGUQAwAVEQEA84QDYWVpY4ADoFAAABR8Y2V0") \ No newline at end of file diff --git a/pr/451/docs/search.index/2a6d66bb35a1.js b/pr/451/docs/search.index/2a6d66bb35a1.js deleted file mode 100644 index b85fd694e..000000000 --- a/pr/451/docs/search.index/2a6d66bb35a1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA82MUAAABADsAAYADoEAAABNAsCAT/gABoCAAAA+7aGl5AQkBoIAAABScZgF7EwAAAQMBsAAUFgBQc/KTEAAAKgMjgAKgIAAAD7dhcgECAPXBEAAAAQABAF0CAQDzAANpdHU6MAAAAQAAAAAAEAAQAAAAcQcfCeAMHxCcENMSDRMOE8ETwhPIE/IT8xMUFBUUZxRoFAGAAqAwAAAURrCwE5cAAWFsAQEA8+EPAAABAAEAE4ACsMAUnQABdHUBAwDzpBIAAAEAAQABAQD1JBEAAAEAAQABAAEAI4ACsJAT6gABYXQBBAGgQAAAFEpl8mwUAAABAAECADswAAABAAALAAUAPxAFALQSAQDkEwEAPRQAAFwUAAATggKw8BSgAAFmZwGBArCAFHMAAaBgAAAUm2RzAQgA9ScUAAABAAEAAQABAPMCAWb1gBAAAAEAAQABAAEAI4ADoCAAAA+1sKAUcQABZHJ0d+8BAI2wEBQhAAGw4BQjAAGX1Q47MAAAAQAANwETAH4EAQCrBAIAnAUCALEGBADiBwEAFwwDAEUMAQDRDAEAiA4BAdgPAQCdEAEAsBACAN0QAQBwEQAAoxEDAM0RAQDGEgAAiRMHAIEUBQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2aa572615107.js b/pr/451/docs/search.index/2aa572615107.js new file mode 100644 index 000000000..a9dfa6ae9 --- /dev/null +++ b/pr/451/docs/search.index/2aa572615107.js @@ -0,0 +1 @@ +rn_("AQUBsEASwwABZTswAAABAAAFAAIA5xIBAEkUAwABggKgkAAAFDagkAAAFDcyMwEAhKAgAAASn6AgAAAI+LCAFLAAAbAwDgYAAQCRATswAAABAAAMAAYAngQAANYEAwBpCQAAsQ8DAAsUAAAcFAEA8wABcwEyEQAAAQUA9JQTAAABAAEAAQABAAD1egwAAAEAAQABAAEA84ACZ2tzgISwsBPvAAERSAABBQGgQAAAEfll8wUNAAAUBAEAE4ACoDAAAAwzZXIBgAOwUBQvAAGgEAAAC/qgcAAAE4NnbnKjAISgUAAAFGKgoAAAE/4UAQQ7MAAAAQAAEQAEAAECAQC5EAgAthEDAMASAgABhQKwcBQXAAGwMBNrAAFlcxMAA7BAD9QAAaBgAAARjGJlczswAAABAAASAAQAqwUBAMYGCgBREAEAqRADAAEDAPRtEAAAAQAEAAEAAQoBoEAAABRlZfKJFAAAAQATgAKwYBK6AAFjafOAAmRy8wACZW87MAAAAQAACQACAJoHBQDdBwMAAQIBsDARWAABbjswAAABAAAJAAEA8AsJAAEAAPSuEAAAAQABAAEAAQEAOzAAAAEAAO4AAgDGBQAAAQjtAGMAA7AQEycAAWVpczowAAABAAAAAAAOABAAAAALCUYJRwlICUkJGgwbDKkPqg/TD+oP2xAAEZATkRPjgISwIAdYAAERQAQBgYSwMAwGAAGgQAAADNGggAAAEV+wQAzSAAEAAB0BBQD14BMAAAEAAQB4AAEAE4ACoAAAAAb6YWcBAwD0mBIAAAEAbgEBAPOAAmlvAQEAOzAAAAEAAO4AAgB2BwAAAgvtAAEIAbBAFGYAAWX1KhQAAAEAAQABAAEA84ECZ3QBAQGgcAAAEf9pOzAAAAEAAAABAgB1BwAAAgr/AAEFAPPKEAAAAQABAPOAAmFvE4ACoEAAAAwycnUBgAKgAAAAB1qgEAAACQpkeBMAArAgDPwAAWVy8hwQAAABABOAArCwFLIAAXJ4AQoA9JsTAAABAAEAAQATgAKwEBEUAjxhZQEEAPSpEAAAAQABAAEAAQQAOjAAAAEAAAAAAAcAEAAAADsTPBNKE0sT0BPRE+MT5BMBAwDz1BIAAAEArQCXAQCAhbBAEvwAPLBAEQYAAYFIAce/AwCPoIAAABLWsGAM7AABsEAMXAABsTAUhgABP6kfOzAAAAEAAEsAIgB6AAAAfwAAAL8EAQDLBAAAggUBAEAHAQBQBwAAVAcBAD8JAABTCQcAawkBAAUMAAAdDAEAJAwBADgMBwBcDAEAeAwBAMgMAQDmDwEAVhABANcQAQDkEAEA+xAAAB0RAgAtEQEAPxEEAGMRAACwEQMA9BEBALASAAASEwEAFhMBAEYTAACsFAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/2b35b1fa753b.js b/pr/451/docs/search.index/2b35b1fa753b.js new file mode 100644 index 000000000..f999ecbe2 --- /dev/null +++ b/pr/451/docs/search.index/2b35b1fa753b.js @@ -0,0 +1 @@ +rn_("AQAAOjAAAAEAAAAAAAoAEAAAAOYP5w/XENgQYxH0EfUREhMTE6wUrRTzAQF58gwUAAABACMAA6BAAAARgqBQAAAUXmVvc/LGDwAA2gI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2b9e99969beb.js b/pr/451/docs/search.index/2b9e99969beb.js new file mode 100644 index 000000000..ddf9a4c0d --- /dev/null +++ b/pr/451/docs/search.index/2b9e99969beb.js @@ -0,0 +1 @@ +rn_("AQQA9goNAAABAD8DAQAuAQEAAQoA89QSAAABAK0AAQEA9x8JAAABAF0HAQBzAAEAFQEjgAKwYBPvAAFpbwECAPTIDAAAAQBNBgEAAQUA9AUUAAABAE4AAQD3CAAAiLCQE7wAAaAgAAAMyrCQEfoAAQTcAzowAAABAAAAAAAIABAAAACVBRAJEQljDGQMARECEQcTCBM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2bc95fa13920.js b/pr/451/docs/search.index/2bc95fa13920.js new file mode 100644 index 000000000..f4a08c59d --- /dev/null +++ b/pr/451/docs/search.index/2bc95fa13920.js @@ -0,0 +1 @@ +rn_("AQUBsEASwwABZfLnEgAAAQABAAA7MAAAAQAAEQAEAAECAQC5EAgAthEDAMASAgABCgGwQBRmAAFl9SoUAAABAAEAAQABAEcGAACHsCARFAI8sMAUsgABoCAAAAb6sDAHWAABGIkKOjAAAAEAAAAAAAwAEAAAAFQHVQc/CR0MHgwkDCUMeAx5DMgMyQwWExcT") \ No newline at end of file diff --git a/pr/451/docs/search.index/2be35184ddea.js b/pr/451/docs/search.index/2be35184ddea.js new file mode 100644 index 000000000..dfcb50a09 --- /dev/null +++ b/pr/451/docs/search.index/2be35184ddea.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwDLEQIAfhMBABQUAgABBADzzhEAAAEAAQAjAAKgYAAAEv1hcvPvEAAAAQDJAQGAAqEQAAAUo7CAFJkAAWl1NwQAgYexUBTAAAGgAAAAEM+wcBPeAAGhMAAAFLQKQBsjgAKgMAAAEppudg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2c10172e8da1.js b/pr/451/docs/search.index/2c10172e8da1.js new file mode 100644 index 000000000..f4042a06a --- /dev/null +++ b/pr/451/docs/search.index/2c10172e8da1.js @@ -0,0 +1 @@ +rn_("AQMBoEAAABH5ZfMFDQAAFAQBAAEGAPQWEQAAAQC+AAEAE4EDoAAAABFesSAUpAABYWRuAQUA9IgQAAABAAEAAQABFAD1xBQAAAEAAQABAAEAAQUBsIAUXwABdjswAAABAAAHAAIA0REDAMITAwAjgAKwcBH2AAFiZFcCAAGGsMATjAABoKAAABOvoJAAABN4BeACOzAAAAEAAAUAAgAVBwMAARABAAEGAPPUEgAAAQCtAPOAA3J0dgEDADswAAABAAAHAAIAvhMDAKgUAwDzAAFzAWIUAADzAAFzAfgRAABHBQCAiKCAAAATOKAwAAAMQbBwFIEAAbAAE3MAAaAQAAAM6xNBDg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2caeb0254b5a.js b/pr/451/docs/search.index/2caeb0254b5a.js deleted file mode 100644 index c9663972c..000000000 --- a/pr/451/docs/search.index/2caeb0254b5a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA82MUAAABADsAAYACoEAAABNAsCAT/gABaGkBAQDz4Q8AAAEAAQATgAKwwBSdAAF0dQGBArCAFHMAAaBgAAAUm2RzNwMAgIagQAAAFEawYBQWAFACkA4BAAGwUBDyAAFj9FEHAAABAAEABgLDgISgEAAADGWgUAAAEoYMQAQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2ccce52d6bb9.js b/pr/451/docs/search.index/2ccce52d6bb9.js new file mode 100644 index 000000000..eb0a2232b --- /dev/null +++ b/pr/451/docs/search.index/2ccce52d6bb9.js @@ -0,0 +1 @@ +rn_("AQUA9MsSAAABAKkAAQABCAD0ihMAAAEAhwABADMBA7BAE1kAAWFiZfX0DAAAAQBFA2kC1QABAAA7MAAAAQAABgACAC0MAQC4DwQA84ACc3UjgQKwgBOEAAFydAEGAPSFEAAAAQC3AAEAUwADsIATCQABYml09XoFAAABAOYOWAABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2d7629068747.js b/pr/451/docs/search.index/2d7629068747.js deleted file mode 100644 index 0be69c5d7..000000000 --- a/pr/451/docs/search.index/2d7629068747.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9mMUAAABAA8AAQAnAAQAAQMA86QSAAABAAEAI4ECsSAUoAABY2sTgAKgMAAAFB9uclMAA6BAAAAMDWVvcvQNEQAAfwHBAQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/2d9a6dddfba0.js b/pr/451/docs/search.index/2d9a6dddfba0.js new file mode 100644 index 000000000..5445c6f66 --- /dev/null +++ b/pr/451/docs/search.index/2d9a6dddfba0.js @@ -0,0 +1 @@ +rn_("AQIA9L4RAAABAAEAAQDzAAFl9MQQAAABAAEAAQABAwD0uhEAAAEAAQABAMcAAACFsKATHAABsCAMJAABsIAS+gABCAwG8wUHAAABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/2e327fd2bb24.js b/pr/451/docs/search.index/2e327fd2bb24.js deleted file mode 100644 index 5102fe636..000000000 --- a/pr/451/docs/search.index/2e327fd2bb24.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQBTAISgIAAACU6wYBNqAIoBAQnzYRAAAHcCAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2eec7becf814.js b/pr/451/docs/search.index/2eec7becf814.js new file mode 100644 index 000000000..157d704af --- /dev/null +++ b/pr/451/docs/search.index/2eec7becf814.js @@ -0,0 +1 @@ +rn_("AQACoCAAABOSsDAR6gABaXTy9hIAAAEAAQMAOzAAAAEAAAYAAQBkEQYAAQgA9PwTAAABAIsABQATAQOwkBRjAAGwUBNBAWZnbHA7MAAAAQAAEgAJAGkQAgDtEQEALhMBAFETAQCIEwEA7RMBACUUAQA0FAEAdRQBAAGAArAwEeYAAbAwEegAAXJ2AQQBsAAStgABZDswAAABAAAFAAIA4hEDAOUSAQC3BQABh7BQErgBF7BgEz8AAZQJAzowAAABAAAAAAALABAAAADfDOAM4QybDpwODBGOEY8RkBGRETUTcBMjgAKgEAAADN5ubwEDAPNmEAAAAQABACOAAqAAAAAMDmRyYwCEoFAAABGLoDAAABCkFAEQ8pgHAAABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2ef1f9ee4b1b.js b/pr/451/docs/search.index/2ef1f9ee4b1b.js deleted file mode 100644 index 67b37aa40..000000000 --- a/pr/451/docs/search.index/2ef1f9ee4b1b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9A0TAAABAFkBAQDzBAFzOzAAAAEAAA0AAgCJEwcAgRQFAEcAAAGGoIAAABNpsQAUkAABsEASxwABsIAUoAABsFATDwABFQAW9IEOAAABAAEAAQABBQDz5g8AAAEAAQBjgAOwQBByAAFtbm8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2f88dcc3b5f8.js b/pr/451/docs/search.index/2f88dcc3b5f8.js deleted file mode 100644 index 31366a83d..000000000 --- a/pr/451/docs/search.index/2f88dcc3b5f8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoJAAABNaaPIiEwAAAQDzAgFk8ggHAAABAAECADswAAABAAAFAAEAdRQFAPMLAW07MAAAAQAABQABADMUBQAjgAKwIAzZAAFhbwECAPPxCAAASwMBAAGKArAAE8kAAbAAE8sAATQ2I4ECoDAAAAxlZXIBgAKwQBHZAAFxBrXx7sVjdbcGAACHsBAQOQABsFAUTwABEVEJ9ZQEAAAKAMUAAQCVBw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2fd38d8465d2.js b/pr/451/docs/search.index/2fd38d8465d2.js deleted file mode 100644 index b70717c04..000000000 --- a/pr/451/docs/search.index/2fd38d8465d2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQsA9IcTAAABAA8AAQABBAGwQBM8AAFlAYcSAADzAAF0AUYUAAABDQDzXRQAADUAAQABEADzihQAABgAAQDzgQNrbXMBAAA7MAAAAQAABgACAHETAADZEwUAI4YCsEAUUQABZXMBBAD0qhAAAAEAAQABAPOAA2NydDOAA7BgEMkAAWFscg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/2febb6ceaf92.js b/pr/451/docs/search.index/2febb6ceaf92.js deleted file mode 100644 index 82c62ff82..000000000 --- a/pr/451/docs/search.index/2febb6ceaf92.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQAOjAAAAEAAAAAAAcAEAAAAMAPwQ+MEI0QBxMIE5kTmhMBBAD0wg8AAAEAAQABAAEAiKEQAAAUX4AI8gAazaBAAAAQ+rBgFE8AAaAQAAAMcbBQE+YAAbAwEVwAAbEgFHEAATnBAjowAAABAAAAAAARABAAAAAICQkJHAkdCfcL+Av5C/oLAAwFDMoMhRJtE24TgxOEE/YT9xNzgISggAAAE+8QQUA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/2ff30af08e21.js b/pr/451/docs/search.index/2ff30af08e21.js deleted file mode 100644 index 552eb521f..000000000 --- a/pr/451/docs/search.index/2ff30af08e21.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9wcRAAABAAEA1gIBAIsABQABAgD0mBAAAAEAAQABABOAAqDQAAAUWGNz84ACZW8jgQKwAAdGAAFsdCOAA7AABakAAbCgE+wAAWRydA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3012000b3b33.js b/pr/451/docs/search.index/3012000b3b33.js new file mode 100644 index 000000000..50de0ad39 --- /dev/null +++ b/pr/451/docs/search.index/3012000b3b33.js @@ -0,0 +1 @@ +rn_("AYICoFAAABLioQAAABSYY3QjAAKwQA/RAAFldTswAAABAAA3ABYAxAYBANUMAQD2DAEA1g8BAFkQBQCPEAUADREBAGsRAQD8EQIA0BIDAAITBAAzEwEAOxMBAEoTAQDQEwEA4xMBAAEUAQAFFAEAKRQAAFQUAQBYFAEAdxQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3067b7ed1d8a.js b/pr/451/docs/search.index/3067b7ed1d8a.js new file mode 100644 index 000000000..e5b0a2e90 --- /dev/null +++ b/pr/451/docs/search.index/3067b7ed1d8a.js @@ -0,0 +1 @@ +rn_("AQcA9eATAAABAAEAeAABAAEHAPPKEAAAAQABAAGAAqAAAAAHWqAQAAAJCmR48wEBZfIcEAAAAQDnAAAAhbBgDOwAAbBADFwAATIIAfTLBAAAhQLdCQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/306dd97f0179.js b/pr/451/docs/search.index/306dd97f0179.js deleted file mode 100644 index 244e8078a..000000000 --- a/pr/451/docs/search.index/306dd97f0179.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBsEARRgABZQGADgAAAQUAOzAAAAEAAAYAAQBFEAYAAQMAOzAAAAEAAAUAAQDEDAUA8wACcHM6MAAAAQAAAAAADQAQAAAAnwWgBbYGBQlHDFQM6xDsEJoSmxLyEvMS9BL1EjMAA7BQD7gA0WRwcjswAAABAAAPAAYAmQQAAF4FAwClDwAAxxEDAJUSAwDJEgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/313cbeca05af.js b/pr/451/docs/search.index/313cbeca05af.js new file mode 100644 index 000000000..b7f81f8a4 --- /dev/null +++ b/pr/451/docs/search.index/313cbeca05af.js @@ -0,0 +1 @@ +rn_("AYADsAAT5gABsAAT6AABsHAUXwABNDZhAQQA9LUUAAABAAoAAQAjgQKgQAAAEV5tcAEBADswAAABAAAIAAMA1hIAAEwTAADCFAYAxwEAAMWgQAAAEFiggAAAErBABoAIOzAAAAEAABYACAD1DwEAFhEBAFoRAwDREQUAwhMDABEUAABvFAEApBQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/32222f9e344c.js b/pr/451/docs/search.index/32222f9e344c.js new file mode 100644 index 000000000..a0d067b6f --- /dev/null +++ b/pr/451/docs/search.index/32222f9e344c.js @@ -0,0 +1 @@ +rn_("AQIA9CUTAAABAJIAAQABAgD0/hAAAAEAmgABAAEDAPRbEwAAAQCYAAEAAQIBoEAAABNGcjswAAABAAAHAAIA/BABAJMRBQDzAIQDQAg6MAAAAQAAAAAAFQAQAAAASgdLB0wH2A/ZD4UQhhCmEKcQPRE+EfAR8REjEyQTMBMxE0ITbRNuE7YTtxM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3257c10e66d5.js b/pr/451/docs/search.index/3257c10e66d5.js deleted file mode 100644 index 49fd9bc66..000000000 --- a/pr/451/docs/search.index/3257c10e66d5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsCARaQEhoJAAABQObXABggKwoBP+AAGwoBQAAAFjcgECAaBAAAARaGUBIRAAADOAhLAgEOYAAbAgD54AAVAUAPOAAm9y") \ No newline at end of file diff --git a/pr/451/docs/search.index/325a13350a7a.js b/pr/451/docs/search.index/325a13350a7a.js new file mode 100644 index 000000000..284acd3c3 --- /dev/null +++ b/pr/451/docs/search.index/325a13350a7a.js @@ -0,0 +1 @@ +rn_("AQEAOzAAAAEAAAYAAQDCFAYA8wYBZfRvFAAAAQA0AAEAEwCEoCAAAAlbsEAUmwABsGAThwCJAQEJ83sQAAB5AgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/32b740d07761.js b/pr/451/docs/search.index/32b740d07761.js deleted file mode 100644 index 2742c6809..000000000 --- a/pr/451/docs/search.index/32b740d07761.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQwA9KUTAAABAAEAAQABBgDzVxEAAAEAAQABggKwYBMrAAGw0BRNAAFpchOAArAQDNYAAWVpFwMAg4ixMBRsAAGgYAAAEVagUAAAEOqwkBL8AAGwQBDmAAG0EAfzAAFh9DAJAAABAPoHAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/32d5e2081a15.js b/pr/451/docs/search.index/32d5e2081a15.js deleted file mode 100644 index d23a9d2fc..000000000 --- a/pr/451/docs/search.index/32d5e2081a15.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAIwAAgBnBwAAZwmLAAGEArBwFB0AAaBAAAAQ/WlzAQcA89YQAAABABUBI4ACoLAAABK6YXQBAAOwMAkUAAGwMBN8AAGgIAAAB2hhZG8BEwIAADcDAICGoMAAABQVsAAT5gABAJgV") \ No newline at end of file diff --git a/pr/451/docs/search.index/32d9da1d0315.js b/pr/451/docs/search.index/32d9da1d0315.js deleted file mode 100644 index 105868666..000000000 --- a/pr/451/docs/search.index/32d9da1d0315.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCoCAAABEMoDAAABFyaXQBAQA7MAAAAQAAjAACAGcHAABnCYsA8wACZWz1rgQAAL0CvglTAgEAAQIAOzAAAAEAAO0AAgD7CwAABQ3sAMcAAACFsLATJgABsBATrAABsDANAwABFDACOzAAAAEAABkBCwDcAQYAmQUAAMMF7AA2BwcAKRACAIERDQCwEQIAiBIBAA8TAQBhEwEA+BMCAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/330ed03d9762.js b/pr/451/docs/search.index/330ed03d9762.js new file mode 100644 index 000000000..5fc76432e --- /dev/null +++ b/pr/451/docs/search.index/330ed03d9762.js @@ -0,0 +1 @@ +rn_("AQYA9BYRAAABAL4AAQATgQOgAAAAEV6xIBSkAAFhZG4BBQD0iBAAAAEAAQABAAEUAPXEFAAAAQABAAEAAQABBQGwgBRfAAF2OzAAAAEAAAcAAgDREQMAwhMDACOAArBwEfYAAWJkVwIAAYawwBOMAAGgoAAAE6+gkAAAE3gF4AI7MAAAAQAABQACABUHAwABEAEAAQYA89QSAAABAK0AAQABoGAAAA/tcgGQBAAA1wEAgMWgMAAADA0AAIoK") \ No newline at end of file diff --git a/pr/451/docs/search.index/337314d00c2a.js b/pr/451/docs/search.index/337314d00c2a.js new file mode 100644 index 000000000..118f9ae55 --- /dev/null +++ b/pr/451/docs/search.index/337314d00c2a.js @@ -0,0 +1 @@ +rn_("AQEBoGAAABSYc/LbEQAAAQABCAD1QhQAAAEAAQABAAEAowCEsCAJcQABoIAAABLvMEAIOzAAAAEAAAUAAgDyAQAAmhAEAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/337d979fd6b5.js b/pr/451/docs/search.index/337d979fd6b5.js deleted file mode 100644 index ae93f00b5..000000000 --- a/pr/451/docs/search.index/337d979fd6b5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9HMRAAABAAEAAQABAgA7MAAAAQAACgAEAHUQBQC2EgEAFxMBAD4UAADzgAJmbgEBADswAAABAAAIAAIAig8GAEETAQDzgAJkbgEBAPMiBwAAAQABAAGGAqAAAAATOqAAAAATO2Ry8wEBdgFmEwAAQwADsIASywABsLAUbgABa2xyOzAAAAEAAAwAAwAZBwgA8wwBAC4QAQABAgD16AcAAAEAAQABAAEA8wCEFEEAAX8AAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/33ee246893ce.js b/pr/451/docs/search.index/33ee246893ce.js new file mode 100644 index 000000000..49bac9d04 --- /dev/null +++ b/pr/451/docs/search.index/33ee246893ce.js @@ -0,0 +1 @@ +rn_("AYACsHASmAABsCAUsgABaW8BAAGw8BSyAAFrOzAAAAEAAAgAAwAZFAAAHhQGAEgUAADzBQFk8toSAAABAAECAPPcEAAAAQABAPMAAWk6MAAAAQAAAAAABwAQAAAAthDvEt4T3xMOFA8ULxQwFCODArCwFI4AAWFzAYECoKAAABNHoEAAABB0b3bzgIQBUAE=") \ No newline at end of file diff --git a/pr/451/docs/search.index/34191c056afb.js b/pr/451/docs/search.index/34191c056afb.js deleted file mode 100644 index a1ce7a515..000000000 --- a/pr/451/docs/search.index/34191c056afb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9HcTAAABAAEAAQABAAD1aAwAAAEAAQABAAEA84ACZ2sTgAKwsBPSAAFhbwGAAqAgAAAL6aBAAAAMIWV0AYUCsHAT+wABsDATTgABZXMTAAOwQA+8AAGgYAAAEXFiZXM7MAAAAQAAEgAEAKEFAQC7BgoANxABAI8QAwABAwD0UxAAAAEABAABAAEKAaBAAAAUSmXybBQAAAEAE4ACsGASngABY2nzgAJkcvMAAmVvOzAAAAEAAAkAAgCOBwUA0QcDAAECAbAwET0AAW47MAAAAQAACQABAN8LCQDzgAJvdAEDADswAAABAADtAAIAagcAAPIK7AABAgGgcAAAEeRpOzAAAAEAAP8AAgBpBwAA8wn+ABOAAqBAAAAMIHJ1AQQA9C0TAAABAJgAAQABAwDzuBIAAAEArADzgAJhb9cHAACIsEAM6QABsTAUaQABNIEWOzAAAAEAAB4ADQC2BAEAeAUBAPQLAAAmDAcASgwBAM4PAQC8EAEASBEAAJURAwDZEQEAlBIAAPYSAQCQFAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/34d17abef16f.js b/pr/451/docs/search.index/34d17abef16f.js new file mode 100644 index 000000000..a9313f8c0 --- /dev/null +++ b/pr/451/docs/search.index/34d17abef16f.js @@ -0,0 +1 @@ +rn_("AYECoBAAABNHoHAAABOPYWMBhwKwABPmAAGwABPoAAE0NlMAhKCQAAATb7CwFAwAAYEACfUkBwAANgA/CREBIAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3545cf7f836f.js b/pr/451/docs/search.index/3545cf7f836f.js new file mode 100644 index 000000000..344bd9683 --- /dev/null +++ b/pr/451/docs/search.index/3545cf7f836f.js @@ -0,0 +1 @@ +rn_("AQUA8x4TAAABAAEAAQAAOzAAAAEAAAUAAgD/CwMA9hEBAPMAAWT0/hAAAAEAmgABAFMAA7AwFFYAAWVwcvTkDwAAAQDvAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/358b5f8021f2.js b/pr/451/docs/search.index/358b5f8021f2.js new file mode 100644 index 000000000..bdc66b3c7 --- /dev/null +++ b/pr/451/docs/search.index/358b5f8021f2.js @@ -0,0 +1 @@ +rn_("AQIA9L4RAAABAAEAAQATAAKgMAAAEYlldPTEEAAAAQABAAEAAQMA9LoRAAABAAEAAQABgAKgAAAAEJWgAAAAEJY0NvMDAXYBAg0AAJcCAACIsKATHAABsCAMJAABsGATFgABsIAS+gABoCAAABEACUxGOjAAAAEAAAAAAAkAEAAAAAUHBgcHBzUM9w9fEakSqhKrEl4U") \ No newline at end of file diff --git a/pr/451/docs/search.index/358dcb9f0de5.js b/pr/451/docs/search.index/358dcb9f0de5.js new file mode 100644 index 000000000..7afd3ff68 --- /dev/null +++ b/pr/451/docs/search.index/358dcb9f0de5.js @@ -0,0 +1 @@ +rn_("AQQAOjAAAAEAAAAAAAcAEAAAANgP2Q+mEKcQIxMkE7YTtxMBBAD02g8AAAEAAQABAAEAAqAQAAAMg7BQFAMAAWlv8ikJAAABAPOAA2Vpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/35dbfa9e409c.js b/pr/451/docs/search.index/35dbfa9e409c.js deleted file mode 100644 index 46a6f00a4..000000000 --- a/pr/451/docs/search.index/35dbfa9e409c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCsAARYwABsAARZQABNDbzAAFh8mMMAAABACOAArCQEWkBIWx0AYACsHATfAABsIATtQABY3MBAAA6MAAAAQAAAAAACQAQAAAAwxHEEd4S3xLOE88TJRQmFKAUoRTzgAJkZScBAICFoNAAABP1sBAEuAABoDAAABBeAggL") \ No newline at end of file diff --git a/pr/451/docs/search.index/3654024d46ce.js b/pr/451/docs/search.index/3654024d46ce.js deleted file mode 100644 index bb7188101..000000000 --- a/pr/451/docs/search.index/3654024d46ce.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoDAAABEBoJAAABN2MGj15xEAAJcBAQABAAEAE4ACoDAAAA+0ZHIjAYSwEAjqAzuwIAvyAQqgMAAAD9wSABj1kgUAAC0AAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/36ce02943eb1.js b/pr/451/docs/search.index/36ce02943eb1.js new file mode 100644 index 000000000..594b1868a --- /dev/null +++ b/pr/451/docs/search.index/36ce02943eb1.js @@ -0,0 +1 @@ +rn_("AQEBoEAAABGJc/U1DAAAKgVKAQEAAQATggKgIAAADQxlcgECAPTuDwAAAQD3AAEAI4ECoBAAAAw0ZWkTgAKg8AAAFChucjMAA7AwCSYAAWVvdAHdEQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/36e021190be2.js b/pr/451/docs/search.index/36e021190be2.js deleted file mode 100644 index 73ec2d075..000000000 --- a/pr/451/docs/search.index/36e021190be2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHAUfQABoAAAAASdZmcBAQA7MAAAAQAABwABADAMBwABAQGgcAAAEoRhAQcHAAAjgAKhAAAAFIdkbnOAhKBgAAASgwBBSA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/378b1aab08c9.js b/pr/451/docs/search.index/378b1aab08c9.js new file mode 100644 index 000000000..b18d85209 --- /dev/null +++ b/pr/451/docs/search.index/378b1aab08c9.js @@ -0,0 +1 @@ +rn_("AQMBoEAAABGCZQHGDwAAAQIA8y8MAAABAAEAIwACsCAMHQABa3L28AYAAAEAFArtAAEANALzAAJyePLvCAAAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/381301744cd9.js b/pr/451/docs/search.index/381301744cd9.js deleted file mode 100644 index c765ac80b..000000000 --- a/pr/451/docs/search.index/381301744cd9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICsCARKwABsDARqQABZXABBQD0zg8AAAEA7QABAFOAA7CQEdkAAWFpcgGBArCgE/4AAbCgFAAAAWNyxwAAAIWwUAzjAAGgIAAACWGgIAAADA08AAE6MAAAAQAAAAAACgAQAAAAsgGzAXIE7wYSCRMJYxBkENYQ1xDsEQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/384f556a9613.js b/pr/451/docs/search.index/384f556a9613.js deleted file mode 100644 index 5aeddedf9..000000000 --- a/pr/451/docs/search.index/384f556a9613.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOjAAAAEAAAAAAAoAEAAAAM4Pzw+8EL0QSBHZEdoR9hL3EpAUkRTzAQF58vATAAABACMAA6BAAAARZ6BQAAAUQ2Vvc/KuDwAA1gI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/388d28ec6ee7.js b/pr/451/docs/search.index/388d28ec6ee7.js new file mode 100644 index 000000000..9b37bdee1 --- /dev/null +++ b/pr/451/docs/search.index/388d28ec6ee7.js @@ -0,0 +1 @@ +rn_("AQoBoIAAABS4ZgGYEwAAAQgA9UIUAAABAAEAAQABAPMDAWb1mhAAAAEAAQABAAEA84MCYXMBAgKgIAAAEd2gMAAAEqhiaAHKDwAAI4ACoIAAABNMYXDzAAJobQGmBAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/39e247c24669.js b/pr/451/docs/search.index/39e247c24669.js deleted file mode 100644 index 56dc85f46..000000000 --- a/pr/451/docs/search.index/39e247c24669.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9PsQAAABAL4AAQATgQOgAAAAEUOxIBSIAAFhZG4BBQD0bhAAAAEAAQABAAEUAPWoFAAAAQABAAEAAQABBQGwgBREAAF2OzAAAAEAAAcAAgC2EQMApRMDACOAArBwEdsAAWJkVwIAAYawwBNvAAGgoAAAE5KgkAAAE1sF4AL1CgcAAAEAAQDdCJIEAQYA87gSAAABAKwAAQABoGAAAA/VcgGHBAAA1wEAgMWgMAAAC/wAAIoK") \ No newline at end of file diff --git a/pr/451/docs/search.index/3a20dad21292.js b/pr/451/docs/search.index/3a20dad21292.js deleted file mode 100644 index 4ad6195a0..000000000 --- a/pr/451/docs/search.index/3a20dad21292.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9KMRAAABAAEAAQATAAKgMAAAEW5ldPSqEAAAAQABAAEAAQMA9J8RAAABAAEAAQABgAKgAAAAEHugAAAAEHw0NvMDAXYB7wwAAJcCAACIsKATAAABsCAMEgABsGAS+gABsIAS3gABoCAAABDlCUxGOjAAAAEAAAAAAAkAEAAAAPoG+wb8BiMM3w9EEY0SjhKPEkMU") \ No newline at end of file diff --git a/pr/451/docs/search.index/3a8f25986add.js b/pr/451/docs/search.index/3a8f25986add.js new file mode 100644 index 000000000..8aba7e1e3 --- /dev/null +++ b/pr/451/docs/search.index/3a8f25986add.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAEQAAgAeCQAAhAxDAAEFAPTEEAAAAQABAAEAE4ACsLATpAABYWwBBAD0sQ8AAAEAAQABAHMABKAQAAAQeGJwcnY7MAAAAQAACgACAGQRBgC6EQMAAYACsIAREwIjsOAUDAABaXIBAwGwQBFhAAFlAZUOAAABBQA7MAAAAQAABgABAF8QBgABAwA7MAAAAQAABQABANcMBQDzAAJwczowAAABAAAAAAANABAAAACpBaoFwQYSCVkMZgwGEQcRthK3Eg4TDxMQExETVwAAAIWwUBC2AjmwUA/QANOgQAAAEYxIgAM7MAAAAQAADwAGAKIEAABoBQMAvQ8AAOIRAwCxEgMA5RIBANMAhKBgAAAM6xAIBvOvAQAAAQAaCw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3ad93ce64a37.js b/pr/451/docs/search.index/3ad93ce64a37.js new file mode 100644 index 000000000..14c18044e --- /dev/null +++ b/pr/451/docs/search.index/3ad93ce64a37.js @@ -0,0 +1 @@ +rn_("AQEAOjAAAAEAAAAAAAcAEAAAAFETUhPtE+4TJRQmFHUUdhTzBgFpAXATAABDgAOwABSwAAGwUBNFAFpkZm4BAAGwQBMcAAFlOzAAAAEAAA0AAgDRBgwAEBEAACOAAqAwAAAMEWFlNwAAgMWwUBOZAAGwEBQ+AAGwYBOgAAEAApgI") \ No newline at end of file diff --git a/pr/451/docs/search.index/3b0d86fd7fb4.js b/pr/451/docs/search.index/3b0d86fd7fb4.js new file mode 100644 index 000000000..cdce689dd --- /dev/null +++ b/pr/451/docs/search.index/3b0d86fd7fb4.js @@ -0,0 +1 @@ +rn_("AYkCsCAUcQABoDAAABR7aXATAAOgIAAAEy2ggAAAE0JlaXX1EAwAADoAAQABAAEAAYYCsJAUmQABoAAAABK5YmQBgAKwMBMhAAGgMAAAEPhydtcAAAKFoDAAABBsoDAAABN5EAETOjAAAAEAAAAAAAkAEAAAAPsI/Aj9CC0JLgmHEI4QSBFJEf0SAQMBsDATPQABc/PKEAAAAQABAAEAADswAAABAAAKAAMAchECAL4TAwCoFAMAlwEAAIWgIAAABbGwIAf/AAFQGAH1egwAAAEAAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3b2c7cca574a.js b/pr/451/docs/search.index/3b2c7cca574a.js new file mode 100644 index 000000000..4c0258c35 --- /dev/null +++ b/pr/451/docs/search.index/3b2c7cca574a.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAO4AAgAMDAAAGA3tAPMAAW07MAAAAQAA7gACAKMFAADNBe0AAQMAOzAAAAEAAAgAAgCiDwYAXhMBAPMAAWM7MAAAAQAAVwAnAKgECwBvBQAADgkBACcMAQAvDAIA7wwAAPsMAgANDQEAzA8AANEPAQDUDwEA3g8AAGYQAgBtEAEAchABAJcQAAAPEQAAFREAAGERAQBuEQEAghEBAIYRAgDmEQEA+RECAP8RAAC1EgAAvhIBAMMSAQAMEwEAHBMBACETAQBDEwEAUxMDAFkTAQC6EwEAzhMAADoUAABjFAQAbBQCAPOAAnJ3AQkA834UAAABADwAAYACoEAAABNdsCAUGgABaGkBAQDz+Q8AAAEAAQATgAKwwBS5AAF0dQGBArCAFJAAAaBgAAAUt2RzNwMAgYagQAAAFGGwYBQxAFICkA4BiAKwABPmAAGwABPoAAE0NjOAA6AwAAARhWRuc2MAA6AQAAAEy2FlbwGGAAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3b71704824be.js b/pr/451/docs/search.index/3b71704824be.js deleted file mode 100644 index ffb19b6b4..000000000 --- a/pr/451/docs/search.index/3b71704824be.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9OgTAAABAFAAAQDzBAFj8usSAAABAAEFAPO7EgAAAQABAPOCAmZ0AQABsAAURAABZPTqCwAAAQABAAEA84ACZWkTgAKw4BRPAAF0dg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3bfe87876fca.js b/pr/451/docs/search.index/3bfe87876fca.js new file mode 100644 index 000000000..b060cd394 --- /dev/null +++ b/pr/451/docs/search.index/3bfe87876fca.js @@ -0,0 +1 @@ +rn_("AQMBgBOAABD4czswAAABAAAFAAEAYBMFACOCAqCQAAAUYmlzAQEA9CUTAAABAJIAAQAjgAKwoBQMAAFjbgGEArCgFBoAAbCgFBwAAWNyAQIAOzAAAAEAAAYAAQBfEAYAE4ICoCAAABP+ZXUBAgDzzhEAAAEAAQABgAKwIBQ+AAGgYAAAE49vdmOBA6AQAAATR2FjcwEDADswAAABAAAFAAEA1wwFAAEBAPMbCQAAAQABAAEBAPT+EAAAAQCaAAEAU4ADoMAAABRubnB1AYACsDAQHAABoWAAABSnYWUBgQKwQBLHAAGwQBLJAAE0NgEFAaBQAAAUJ2jyuhIAAAEAAQUA9HUQAAABALMAAQBzgISwMBNzAAEBoAQBCADzbxMAAAgAHAATgAKwQBHyAAFhZQEKAPPIEwAAagABAAEAAaAgAAAQl3IBYBEAAAEAAPTMEwAAAQBKAAEApwEAgIWgMAAAEPugEAAAERsJICEBCQGgQAAAFGVl8okUAAABAEOAhKAgAAAUc6CQAAATy6EwAAAUiBBJAAECAPRbEwAAAQCYAAEAE4ACsEATJwABaXkBAQKgMAAAEv2gQAAAE0ZncjswAAABAAAHAAIA/BABAJMRBQABgAKwABPmAAGwABPoAAE0NgEGAPS1FAAAAQAKAAEA8wICcnYBERQAAPMCAWU7MAAAAQAABQACAH8QAwAwEQEA84ACZHTn1x8AEaAwAAARHLDwFLIAAbBwE5AAATBhYmNlaGlrbG5vcHJzdHV2OzAAAAEAABgBRADDBAAAqQUBALEFAACzBQEAwQYAACQHAABKBwIAWgcAAGwHBQBzBwAA1AcAAPkHAQD/CAEABQkBABIJAABxCQEAdQmMAP8LAwAODAAAEgwDACAMAABZDAAAZgwAAK0PAQDYDwUAWBAAAIUQAQCZEAAAphABAO8QAQDzEAMABhEBAD0RAQBeEQAAexECAKoRAADKEQAA7BEAAPARAQD2EQEAAhIEAKcSAAC2EgEAuRIAAMUSAQDXEgIADhMDACMTAQAwEwEAORMBAD8TAQBCEwAARRMAAFcTAABtEwEAjBMBAJsTBAC2EwEAyRMBAMwTAQDSEwEA3RMAABcUAgAeFAYAPBQBAEgUAABfFAEAixQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3c4d6a050636.js b/pr/451/docs/search.index/3c4d6a050636.js new file mode 100644 index 000000000..19633e0e1 --- /dev/null +++ b/pr/451/docs/search.index/3c4d6a050636.js @@ -0,0 +1 @@ +rn_("AQMA82YQAAABAAEAAYACoLAAABQnsDATcwABbnRDAAOgIAAAERuwUBHyAAFlaGk7MAAAAQAABgADAA4MAAADEgMA0hMBAPOAAmRy") \ No newline at end of file diff --git a/pr/451/docs/search.index/3c791ceb8da1.js b/pr/451/docs/search.index/3c791ceb8da1.js deleted file mode 100644 index 4fe4e1166..000000000 --- a/pr/451/docs/search.index/3c791ceb8da1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAYAAQBFEAYAAQMAOzAAAAEAAAUAAQDEDAUAYwADoJAAABNaaHBzOjAAAAEAAAAAAA8AEAAAAJ8FoAW2BgUJRwxUDOsQ7BCaEpsS8hLzEvQS9RIiEyMT") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d12e183783e.js b/pr/451/docs/search.index/3d12e183783e.js deleted file mode 100644 index 999bc861c..000000000 --- a/pr/451/docs/search.index/3d12e183783e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBoEAAABFuc/UjDAAAIQVJAQEAAQATggKgIAAADPllcgECAPTWDwAAAQD0AAEAI4ECoBAAAAwiZWkTgAKg8AAAFA1ucjMAA7AwCRkAAWVvdAHCEQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d1419c1550f.js b/pr/451/docs/search.index/3d1419c1550f.js deleted file mode 100644 index 5e915d2a8..000000000 --- a/pr/451/docs/search.index/3d1419c1550f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQoBoIAAABScZgF7EwAAAQgA9ScUAAABAAEAAQABAPMDAWb1gBAAAAEAAQABAAEA84MCYXMBAgKgIAAAEcKgMAAAEoxiaAGyDwAAI4ACoIAAABMvYXDzAAJobQGdBAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d8d9ef3a31c.js b/pr/451/docs/search.index/3d8d9ef3a31c.js deleted file mode 100644 index 1718191fa..000000000 --- a/pr/451/docs/search.index/3d8d9ef3a31c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAUAAQB1FAUA8w0BbTswAAABAAAFAAEAMxQFAEcAAACFsAAURAABsEASpwABoQAAABR7sAATagCKOAgCOzAAAAEAABwABwDqCwMAGBEHAMsSAQBJEwQAJxQEAC4UAwCcFAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d9548e24189.js b/pr/451/docs/search.index/3d9548e24189.js new file mode 100644 index 000000000..7278d3ef3 --- /dev/null +++ b/pr/451/docs/search.index/3d9548e24189.js @@ -0,0 +1 @@ +rn_("AQMA9H8MAAABAAEAAQABAAGwUBQ+AAFyAcMEAABTAYSwQBK+AAGwQBR8AAGYAAQ6MAAAAQAAAAAAKAAQAAAAjQSOBI8EmAUMCQ0JMglmCdQMyw8+ED8QTBB8ENwQ3RDeECcRnxL+Ev8SABMBEy0TPRM+E2sTbBPbE9wT4BPhE+IT8hPzE1oUWxRxFHIUmRSaFA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d985c0366b5.js b/pr/451/docs/search.index/3d985c0366b5.js new file mode 100644 index 000000000..33d986a30 --- /dev/null +++ b/pr/451/docs/search.index/3d985c0366b5.js @@ -0,0 +1 @@ +rn_("AYACsHARdwABsCAUXAABYWQBBQD0ShMAAAEAmAABAPMAAWMBsBIAAEOAA7AwE6AAAbCgFD4AAWFzeQEGAPQWEQAAAQC+AAEAk4CEoAAAABFesSAUpAABCSAB84ACZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3d998da360d9.js b/pr/451/docs/search.index/3d998da360d9.js deleted file mode 100644 index 83a8575ab..000000000 --- a/pr/451/docs/search.index/3d998da360d9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOzAAAAEAAAcAAQBGCQcAAQIA9zIQAADaALcCAQABAHoAAQATgAKwMBDOAKRpdPMAAXPycRQAAAEAM4ADsRAT4gABZG5w") \ No newline at end of file diff --git a/pr/451/docs/search.index/3dc6d08edc6b.js b/pr/451/docs/search.index/3dc6d08edc6b.js deleted file mode 100644 index dc64ce70f..000000000 --- a/pr/451/docs/search.index/3dc6d08edc6b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQ8A9AoUAAABAE8AAQABBAD0aRAAAAEAtgABAAECADowAAABAAAAAAAJABAAAADDEcQR3hLfEs4TzxMlFCYUoBShFPMAAW/yZQkAAAEAU4ADsQAUbgABaG9ww4CEoBAAABNAsIATgwABBQAG") \ No newline at end of file diff --git a/pr/451/docs/search.index/3e3f10d5e550.js b/pr/451/docs/search.index/3e3f10d5e550.js deleted file mode 100644 index 683d4a6a6..000000000 --- a/pr/451/docs/search.index/3e3f10d5e550.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAIwAAgDcEAAA7RGLAAEAAbBwE68AAWXyyg8AAAEAIwACoEAAABQJZXP0MxAAAAEASwQBADOAA6BgAAARj2Rpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3e66de6aea10.js b/pr/451/docs/search.index/3e66de6aea10.js new file mode 100644 index 000000000..1fac986e4 --- /dev/null +++ b/pr/451/docs/search.index/3e66de6aea10.js @@ -0,0 +1 @@ +rn_("AYQCsAARfgABsAARgAABNDbzAgFh8nUMAAABAAEAAPbFEgAAAQBzAAEAUQEBAPMAAWTz1BIAAAEArQBjgAOgIAAACPlhZW8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3ea7fcccf5b3.js b/pr/451/docs/search.index/3ea7fcccf5b3.js new file mode 100644 index 000000000..acf8d196b --- /dev/null +++ b/pr/451/docs/search.index/3ea7fcccf5b3.js @@ -0,0 +1 @@ +rn_("AQABoLAAABPOYvNYEwAALgEBAAGAAqAgAAAPz6AwAAAT2mFy8wABdAF9BwAAAQQBoEAAABRlZfKJFAAAAQABBQA7MAAAAQAACwAFAFkQBQDQEgEAARQBAFgUAAB3FAAA4wGEoDAAAA/NhQACOzAAAAEAAAsABQCmBQIAvAYEAFcMAQCLEQAA4hIAAGOAA6AAAAATV2Rucg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3ebb6f16d447.js b/pr/451/docs/search.index/3ebb6f16d447.js new file mode 100644 index 000000000..9e9672863 --- /dev/null +++ b/pr/451/docs/search.index/3ebb6f16d447.js @@ -0,0 +1 @@ +rn_("AQ0A9eATAAABAAEAeAABAAECAPQKEQAAAQCSAQEAAQMA9HURAAABAHwBAQDzAAJidQEbEAAAAQMA9AgRAAABAJIBAQDzhAJkcwECAPMMCQAAAQBZAPOAAml0ARgA88YUAAABAAEAI4ECsMAT3gABY20BBQGwYBLFAAFy9CEMAAABAPcDAQABCQD28BIAAAEAAAGHADYAAQATgAKwYBDIAAFkbgEDAaBwAAATLWf0WwcAAAEACAIBAAEJAPN+FAAAAQA8AAGBArCAFJAAAaBgAAAUt2RzFwEAiYWw0BS5AAGgQAAAFGGwYBQxAFICkApnugAAjLBwES0AAbCwEz0AAaCgAAAS77FQFLAAAbDwFA4AAW3BD/SHAAAAAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/3eddab2797bd.js b/pr/451/docs/search.index/3eddab2797bd.js deleted file mode 100644 index 17e4958e4..000000000 --- a/pr/451/docs/search.index/3eddab2797bd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAABYACQA/EAUAUBEBAOERAgC0EgEA5hIEAOQTAQAOFAAAPRQAAFwUAAATgAKw8BSgAAFmZyOAArBQE58AAW1uAQUA8+QTAAABAHcA8wQBYwFwEQAAAQEBsHAUQQABcDswAAABAAAJAAIAyQcHALcQAQABAgA7MAAAAQAACwACALcTBQB1FAUA8wEBbTswAAABAAANAAQAvQwAAFIRAADNEgUAMxQFABMAAqAwAAAMwWVz8uURAAABAAGCArBwFB0AAaBAAAAQ/Wlz9wEAgIawkBP+AAGQSQg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3f128b5def95.js b/pr/451/docs/search.index/3f128b5def95.js deleted file mode 100644 index ca8211b72..000000000 --- a/pr/451/docs/search.index/3f128b5def95.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoAAAABPIcwHeEAAAAQMA9G4QAAABAAEAAQATgAKwUBByAAFhaQGAArBwFJQAAbBwFCEAAWVyAYACoLAAABRYoDAAABEKbG6XAgAAhqAgAAASg6AgAAAI67AwDfIAARCRBTowAAABAAAAAAARABAAAACVBM0EzgTPBNAEWwmZD5oPmw+cDzATahPuE/QTABQBFB0UHhQBAgA6MAAAAQAAAAAACgAQAAAARAlFCQgQVBNVE4UThhMyFIoUohSjFAEAADswAAABAAAGAAIAcRMAANkTBQABAgGwMBNBAAFjOjAAAAEAAAAAABMAEAAAAOgMtA9MEE0QThBTEFQQWBBZEPQQZxFrEWwRbRHkETYTNxMfFEgUSRQBAwDzqxMAAGwAAQATgQKwQBFTAAFhZROAAqCwAAAUX2FlAQEBsEAUSwABZfUPFAAAAQABAAEAAQATgAKgEAAAC/5hb4cPAADIsDAI/AdhsEAUUQABoIAAABJ+QEyICfPmBwAAIAQ+AAGAAqCAAAATW6CQAAATM2FrAQMA86QSAAABAAEAI4CEsSAUoAABoAAAAAkfoDAAAA+7BAQGAQgA9agUAAABAAEAAQABABOKArBAFH8AAWFyk4CEoMAAABMvoQAAABP9AUAGAQMBoEAAABHeZfPyDAAADAQBAAEFAPShEwAAAQABAAEAo4CEoIAAABMbsAATVgABEQAGAQYA9IwUAAABAAEAAQDzCAFt9JASAAABAAEAAQABAgA7MAAAAQAABQABAHUUBQDzDQFtOzAAAAEAAAUAAQAzFAUAAQQA9/oMAAABAJ4FDgABAJAAAQABAwGAE2MAEPpzOzAAAAEAAAUAAQBDEwUAI4ICoJAAABRHaXMBBQGgUAAAFAxo8p4SAAABABOAArAwE1YAAW50AYACoIAAABN2sEAR1wABYWUBgAKgEAAAEQCgAAAAEUVucgEJAaBAAAAUSmXybBQAAAEAI4ACoJAAABOuaWzHIwAACqAwAAARAbBQEwsAAbDwFJYAAbBwE3MAAaDQAAAUUzBiY2VoaWtub3Y7MAAAAQAAHgALAP0LAADYEAMA5xEEAG8TAQB+EwMAtRMBAP0TAAACFAYAIRQBAC0UAABEFAEAAQICoLAAABQ9oLAAABQ+Y3PztRAAAAEAXgABAQKwUBHXAAGgwAAAFAxoafK1EwAAAQDzgAJlaQEBAbCQE28AAXP0mBAAAAEAAQABAAEDAaAAAAAQ03MBOxAAAPOAA2hpdAEBAaBQAAAS72MBvAwAAAEFAbCAFEQAAXY7MAAAAQAABwACALYRAwClEwMAAQEA9FsQAAABALIAAQDjgISwcBHbAAEOCAD3ZQ8A0bCQEnwAAaBQAAAQ+aEAAAAUe6CgAAATkqAQAAAHQkA/vyk7MAAAAQAAPAAZAL0EAAC/BAAAjwUCAAoHAgDqCwMAvwwBAKgPAgC2DwAA4A8AAOkPAAAJEAEAFxEIAG4RAAC+EgEAxxIBAMsSAQArEwEASRMEAJ8TAQCpEwEArhMAACcUBAAuFAMAexQAAJwUAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/3f639926587b.js b/pr/451/docs/search.index/3f639926587b.js new file mode 100644 index 000000000..98985247f --- /dev/null +++ b/pr/451/docs/search.index/3f639926587b.js @@ -0,0 +1 @@ +rn_("AQQA9K4QAAABAAEAAQABAAA6MAAAAQAAAAAACgAQAAAA5g/nD9cQ2BBjEfQR9RESExMTrBStFPMBAXnyDBQAAAEAAQADoKAAABRZoCAAABOSsDAR6gABZWl08vYSAAABAAEDADswAAABAAAGAAEAZBEGAAEIAPT8EwAAAQCLAAUAEwEDsJAUYwABsFATQQFmZ2xwOzAAAAEAABIACQBpEAIA7REBAC4TAQBREwEAiBMBAO0TAQAlFAEANBQBAHUUAQABgAKwMBHmAAGwMBHoAAFydgECAbAAErYAAWQ7MAAAAQAABQACAOIRAwDlEgEAI4ECoJAAABRYaW8BBwD0dBQAAAwAIwARAHcWAAGJsFAUDAABsFASuAEXsGATPwABtQkDOjAAAAEAAAAAAA4AEAAAAN8M4AzhDJsOnA7QENEQDBEvEY4RjxGQEZERNRNwE0OAA6AgAAAM0aAQAAAM3m1ubwEDAPRmEAAAAQABABoBAYACoLAAABQnsDATcwABbnRDAAOgIAAAERuwUBHyAAFlaGk7MAAAAQAABgADAA4MAAADEgMA0hMBAPOAAmRyhwsAAAqAAfgAEAGAAfsAEAGgUAAAEYugUAAAFF6gMAAAEJmgMAAAEKQwMWNlaW9zdHV39ZgHAAABAC0I2gJeAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/3fb5642e59ae.js b/pr/451/docs/search.index/3fb5642e59ae.js deleted file mode 100644 index c54201544..000000000 --- a/pr/451/docs/search.index/3fb5642e59ae.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwCwEQIAYRMBAPgTAgABBwD2hRMAAAEArABYABgAAQABBADzsxEAAAEAAQAjAAKgYAAAEuFhcvPUEAAAAQDIAQGAAqEQAAAUh7CAFH0AAWl1dwgAAIixUBSkAAGgAAAAELSwcBPBAAGhMAAAFJgKQRvyTgkAAD0HAYECoFAAABD5oKAAABMza3ABAwA7MAAAAQAAAwECANEMAQCIDgEB84ACbHTzgAJhZQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/40fbc4e3aee2.js b/pr/451/docs/search.index/40fbc4e3aee2.js new file mode 100644 index 000000000..878137268 --- /dev/null +++ b/pr/451/docs/search.index/40fbc4e3aee2.js @@ -0,0 +1 @@ +rn_("AQIBoQAAABSAZPJgBwAACgIBBgGgMAAAEe90Aa8TAAABAwGwUBL4AAFl9RMJAABUAwEAEQQBAAEBAaEAAAAUdGTysgUAAD4CFwcAAIewoBL2AAGg4AAAFEewEAWzAAEegArzYgQAAAEAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4145ba7520f0.js b/pr/451/docs/search.index/4145ba7520f0.js deleted file mode 100644 index ee4aa4ae1..000000000 --- a/pr/451/docs/search.index/4145ba7520f0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9VEJAAABAAEAAQABACOAAqCgAAAUe2ZwRwAAAIWwMA+XAAGgUAAAEF6wcBDrAAGxQBSmAAGBKQDybAUAAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/41556e3d4595.js b/pr/451/docs/search.index/41556e3d4595.js deleted file mode 100644 index 561c9a685..000000000 --- a/pr/451/docs/search.index/41556e3d4595.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYECoBAAABMqoHAAABNyYWMBhwKwABPJAAGwABPLAAE0NlMAhKCQAAATUrCwE/AAAYEACfUYBwAANgAxCRABIAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4177e23c85bd.js b/pr/451/docs/search.index/4177e23c85bd.js new file mode 100644 index 000000000..228db7e24 --- /dev/null +++ b/pr/451/docs/search.index/4177e23c85bd.js @@ -0,0 +1 @@ +rn_("AYACsHAS2gABoLAAABQZZW8jAQOwcBIAAAGAE4AAEPhtbnM7MAAAAQAAGgAGAGoHAQDxCAUACA0BAE4RCQCMEQAAYBMFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/41c9d94a3575.js b/pr/451/docs/search.index/41c9d94a3575.js deleted file mode 100644 index cd1eef90d..000000000 --- a/pr/451/docs/search.index/41c9d94a3575.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCsAARYwABsAARZQABNDbzAgFh8mMMAAABAAEAAPapEgAAAQByAAEAUQEBAPMAAWTzuBIAAAEArABjgAOgIAAACOxhZW8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/42b98052bb96.js b/pr/451/docs/search.index/42b98052bb96.js deleted file mode 100644 index a2b23d90e..000000000 --- a/pr/451/docs/search.index/42b98052bb96.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAO0AAgD7CwAABQ3sAPMAAW07MAAAAQAA7QACAJkFAADDBewAAQMAOzAAAAEAAAgAAgCKDwYAQRMBAPMAAWM7MAAAAQAAVwAnAJ8ECwBlBQAAAQkBABUMAQAdDAIA3AwAAOgMAgD6DAEAtA8AALkPAQC8DwEAxg8AAEwQAgBTEAEAWBABAH0QAAD0EAAA+hAAAEYRAQBTEQEAZxEBAGsRAgDLEQEA3hECAOQRAACZEgAAohIBAKcSAQDwEgEAABMBAAUTAQAmEwEANhMDADwTAQCdEwEAsRMAAB8UAABIFAQAURQCAPOAAnJ3AQkA82MUAAABADsAAYACoEAAABNAsCAT/gABaGkBAQDz4Q8AAAEAAQATgAKwwBSdAAF0dQGBArCAFHMAAaBgAAAUm2RzNwMAgYagQAAAFEawYBQWAFACkA4BiAKwABPJAAGwABPLAAE0NjOAA6AwAAARamRuc2MAA6AQAAAEwmFlbwGFAAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/42d3b763f13b.js b/pr/451/docs/search.index/42d3b763f13b.js new file mode 100644 index 000000000..d7406b0ec --- /dev/null +++ b/pr/451/docs/search.index/42d3b763f13b.js @@ -0,0 +1 @@ +rn_("AYICsEAUYwABsGATOQABZXIBBQD0nxAAAAEAAQABAPOAAmZtAQYAOjAAAAEAAAAAAAgAEAAAAN8P4A+SEfAS8RLxE3gUrhSvFAEAArCwFAkAAaFAAAAUp2Vn9HkHAAABAAEAAQDDAISgsAAAE9qwQBC3AAEUIAT37wYAAN8FgQMBADoBLgEXAWOAA7AwEBwAAWFlaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4314e4d51001.js b/pr/451/docs/search.index/4314e4d51001.js deleted file mode 100644 index ab7d5d50f..000000000 --- a/pr/451/docs/search.index/4314e4d51001.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9FsHAABuCN8AAQABBQD0LhQAAAEAAQABAAEFAPQeEwAAAQCUAAEA8wICY3X1PBAAAAEAxQABAAEA84ACbnM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/43819ce322e0.js b/pr/451/docs/search.index/43819ce322e0.js new file mode 100644 index 000000000..6b63db2e2 --- /dev/null +++ b/pr/451/docs/search.index/43819ce322e0.js @@ -0,0 +1 @@ +rn_("AYQCoCAAABEnoDAAABGNaXQBAQA7MAAAAQAAjQACAHMHAAB1CYwAYwADsAACGgABZGVsOjAAAAEAAAAAAAcAEAAAALcEhQWGBYcFdwdEEZkTmhM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/43bef58741e0.js b/pr/451/docs/search.index/43bef58741e0.js deleted file mode 100644 index 00a2ca981..000000000 --- a/pr/451/docs/search.index/43bef58741e0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCoFAAABD5oKAAABMza3ABAAGwQBJ8AAFs9F0JAAABAGsHAQABCAD0kBIAAAEAAQABAAEEAaAAAAAQ03MBOxAAAAECAaBQAAAS72MBvAwAALcAAACGsEAM+gABoJAAABMboDAAAAv+lRABOzAAAAEAAAgABAC/BAAAjwUCAL8MAQCoDwIAo4CEsDAJHAABoBAAABNpkQBAARABsAAUZwABczswAAABAAAFAAEAgRQFAAEDAPRdCQAAAQBrBwEAY4ADoAAAAAv+bnJ1AQACsNATrwABoDAAAA+rYmr2cgUAAAEAAQBJAAEAdQEBAAGwgBPOAAF0OzAAAAEAAEUABAC7BQAABwkAABEJAAByDEIAAQkA87ESAAABAAEA84ADY2d0AYMCoJAAABQboJAAABQcMjMBgAKgUAAAFEeggAAAE2ZjZfMAAnN08jQHAAABAAEAAaCwAAATsWLzOxMAAC4BAQAjgAKgAAAAEzpkciOAAqBQAAAUDWNl84ACaXkBBAD1JBEAAAEAAQABAAEA8wEBbgEqBwAAAQEAOzAAAAEAAAkAAgDXBwcA+BIBAAEGAPShEwAAAQABAAEAAQ8A9IwUAAABAAEAAQDzgQJobQEAAbBAFEsAAWX1DxQAAAEAAQABAAEAU4ADoCAAABHGbG50120AgMugIAAAFHywYBC3AAGwQA/kAAHAGzgK") \ No newline at end of file diff --git a/pr/451/docs/search.index/444d4497d47d.js b/pr/451/docs/search.index/444d4497d47d.js deleted file mode 100644 index 3d1ace2f5..000000000 --- a/pr/451/docs/search.index/444d4497d47d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9cMTAAABAAEAegABAAEHAPOwEAAAAQABAAGAAqAAAAAHTqAQAAAI/WR48wEBZfICEAAAAQDnAAAAhbBgDNkAAbBADEoAATIIAfTCBAAAggLOCQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/4471911fea22.js b/pr/451/docs/search.index/4471911fea22.js new file mode 100644 index 000000000..172aa1c1a --- /dev/null +++ b/pr/451/docs/search.index/4471911fea22.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAYAAQBfEAYAAQMAOzAAAAEAAAUAAQDXDAUAYwADoJAAABN3aHBzOjAAAAEAAAAAAA8AEAAAAKkFqgXBBhIJWQxmDAYRBxG2ErcSDhMPExATERM/E0AT") \ No newline at end of file diff --git a/pr/451/docs/search.index/4513c73d7beb.js b/pr/451/docs/search.index/4513c73d7beb.js deleted file mode 100644 index 99c832462..000000000 --- a/pr/451/docs/search.index/4513c73d7beb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9AkTAAABAJEAAQABAwD0PhMAAAEAmAABAPMCAmJ1OjAAAAEAAAAAABMAEAAAAD4HPwdAB8APwQ9rEGwQjBCNECIRIxHVEdYRBxMIExQTFRMlE5kTmhM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/451831b31f8f.js b/pr/451/docs/search.index/451831b31f8f.js deleted file mode 100644 index 1b84f65f8..000000000 --- a/pr/451/docs/search.index/451831b31f8f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoEAAABQfZfJNFAAAAQABAAD08hIAAAEAAQABAPMEAWT0lRIAAAEAAQABAKcAAAKFsKAUOwABsEAS8AABsHAT4gABFYgAAQ0UAAABAAGwgBQhAAFw8h0UAAABAEMAA6AgAAARRaBAAAASmWRlc/PgDwAASwMBAPOAAmVp") \ No newline at end of file diff --git a/pr/451/docs/search.index/4539bd8aa823.js b/pr/451/docs/search.index/4539bd8aa823.js deleted file mode 100644 index 16c277acb..000000000 --- a/pr/451/docs/search.index/4539bd8aa823.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9AMJAAABAE0DAQABAwDzTgwAAAEAAQDzgAJpbwEBAbBQEPIAAWP0UQcAAAEAAQAGAiOBAqBQAAAQtGNuAQQAOzAAAAEAAAcAAwCwEQIAYRMBAPgTAgABAQA6MAAAAQAAAAAABwAQAAAA0hHTERITExNrE2wTGRQaFPMJAWkB8RAAANcCAACHsMATVgABoFAAAA+doRAAABRDFAEXOzAAAAEAACIAAQC2ASIA") \ No newline at end of file diff --git a/pr/451/docs/search.index/454c52ea575e.js b/pr/451/docs/search.index/454c52ea575e.js new file mode 100644 index 000000000..4608a695f --- /dev/null +++ b/pr/451/docs/search.index/454c52ea575e.js @@ -0,0 +1 @@ +rn_("AQUA9EkUAAABAAEAAQABBQD0OxMAAAEAlAABAPMAAmN19VYQAAABAMYAAQABAAEJAPN+FAAAAQA8AAGBArCAFJAAAaBgAAAUt2RzFwEAhIWw0BS5AAGgQAAAFGGwYBQxAFICkAojgAKgcAAAEV9kcgELAaBAAAAUZWXyiRQAAAEAE4ACsDAQcgABZHLzAgFl9N0HAAABAAEAAQAjgAKwIAwGAAFleQEKAPPAEgAAAQABAAELADswAAABAAAHAAEAphMHAAECAPQFFAAAAQBOAAEAtwAAgIWwcBL2AAGw0BSsAAFgIAYBBgDzzRIAAAEAAQABAwGwQBR8AAF08tsTAAABACOFAqBAAAAUC2JzIwIDsEARAwABoFAAABFwYWJm9wcJAAABAEgAeAcBAGIAAQDzgAJpcAEEAPNvEAAAAQABAPMAAXA7MAAAAQAADAABAN8GDAAjgAOgMAAADBawcBO6AAFhZWnzgANkbW4BAAA6MAAAAQAAAAAABwAQAAAAEglmDLYStxIOEw8TEBMRE/MCAWQ7MAAAAQAACQADAOIRAwCxEgMA5RIBACOAAqCQAAAUWGlvAYADsDATbQABgBOwABABsFARIAABY3N48wABZQHMBAAA5xUAgImgAAAADHegQAAADNGwQAzSAAGQSF0=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4571532c4b5c.js b/pr/451/docs/search.index/4571532c4b5c.js deleted file mode 100644 index 37c3f2110..000000000 --- a/pr/451/docs/search.index/4571532c4b5c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9K8SAAABAKgAAQABCAD0bRMAAAEAiAABADMBA7BAEzwAAWFiZfXhDAAAAQA+A2cC1AABAAA7MAAAAQAABgACABsMAQCgDwQA84ACc3UjgQKwgBNnAAFydAEGAPRrEAAAAQC2AAEAUwADsIAS7QABYml09XAFAAABANUOVwABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4581b8194ca6.js b/pr/451/docs/search.index/4581b8194ca6.js deleted file mode 100644 index f33b5d984..000000000 --- a/pr/451/docs/search.index/4581b8194ca6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsIAS7QABsKATZwABaXQBgAKw4BRpAAGwQBA5AAFscgEGAPTPEAAAAQABAAEARwUAAIeg0AAAFEOwUBA3AAGgAAAAE2mw8BQdAAEFkQPy3A8AAK4A") \ No newline at end of file diff --git a/pr/451/docs/search.index/458f9d447ca8.js b/pr/451/docs/search.index/458f9d447ca8.js new file mode 100644 index 000000000..fd3fafcfe --- /dev/null +++ b/pr/451/docs/search.index/458f9d447ca8.js @@ -0,0 +1 @@ +rn_("AYMCoFAAABEUoKAAABNQa3ABAAGwQBKYAAFs9GsJAAABAHgHAQABCAD0rBIAAAEAAQABAAEEAaAAAAAQ7nMBVRAAAAECAaBQAAATC2MBzwwAALcAAACGsEANDQABoJAAABM4oDAAAAwPlRABOzAAAAEAAAgABADIBAAAmQUCANIMAQDADwIAo4CEsDAJKQABoBAAABOGkQBAARABsAAUhAABczswAAABAAAFAAEAnRQFAAEDAPRrCQAAAQB4BwEAY4ADoAAAAAwPbnJ1AQACsNATzAABoDAAAA/DYmr2fAUAAAEAAQBJAAEAdwEBAAGwgBPrAAF0OzAAAAEAAEYABADFBQAAFAkAAB4JAACEDEMAAQkA880SAAABAAEA84ADY2d0AYMCoJAAABQ2oJAAABQ3MjMBgAKgUAAAFGKggAAAE4NjZfMAAnN08kAHAAABAAEAAaCwAAATzmLzWBMAAC4BAQAjgAKgAAAAE1dkciOAAqBQAAAUKGNl84ACaXkBBAD1PxEAAAEAAQABAAEA8wEBbgE2BwAAAQEAOzAAAAEAAAkAAgDjBwcAFBMBAAEGAPS+EwAAAQABAAEAAQ8A9KgUAAABAAEAAQDzgQJobQEAAbBAFGYAAWX1KhQAAAEAAQABAAEAU4ADoCAAABHhbG50120AgMugIAAAFJiwYBDSAAGwQA/8AAHAGzgK") \ No newline at end of file diff --git a/pr/451/docs/search.index/45b43961206f.js b/pr/451/docs/search.index/45b43961206f.js new file mode 100644 index 000000000..b334d93f6 --- /dev/null +++ b/pr/451/docs/search.index/45b43961206f.js @@ -0,0 +1 @@ +rn_("AQIBsEAUfAABdDswAAABAAANAAYA/hIDAGsTAQDbEwEA8hMBAHEUAQCZFAEAE4ACsDATMgFJaXABBQDzsBMAAAEAAQATgAKgMAAAFAtleVMCA7BwFBcAAWJlcwFAEAAAEwACoCAAAAWkZm31ggAAAOMGAQDVAQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/45ccc7ad5291.js b/pr/451/docs/search.index/45ccc7ad5291.js new file mode 100644 index 000000000..595cb8328 --- /dev/null +++ b/pr/451/docs/search.index/45ccc7ad5291.js @@ -0,0 +1 @@ +rn_("AQABoCAAAAzrbQGEBQAAAQAA9A4TAAABAAEAAQDzBAFk9LESAAABAAEAAQAjhISwoBRWAAGwQBMMAAGwcBP/AAEVgAAzgAOw0BRAAAFhZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/45d9dd3f98fe.js b/pr/451/docs/search.index/45d9dd3f98fe.js new file mode 100644 index 000000000..6df7f5498 --- /dev/null +++ b/pr/451/docs/search.index/45d9dd3f98fe.js @@ -0,0 +1 @@ +rn_("AQcAOzAAAAEAAI0AAgD3EAAACBKMAAEDADswAAABAACMAAEACA6MAAECADswAAABAAAHAAEAQgwHAPMAA2JpdDswAAABAACNAAIAoQQAANsEjAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/46221eeede87.js b/pr/451/docs/search.index/46221eeede87.js new file mode 100644 index 000000000..eb4d2c9ae --- /dev/null +++ b/pr/451/docs/search.index/46221eeede87.js @@ -0,0 +1 @@ +rn_("AQAAOjAAAAEAAAAAAAoAEAAAAOYP5w/XENgQYxH0EfUREhMTE6wUrRQBBADz4Q8AAOEAAQDzAAFx9woNAAABAD8DAQDNAGEAAQDzAAJleTowAAABAAAAAAAOABAAAAAJCVMMVAx3EIwQjRAiESMRJBH8E/0TDBQNFIgUjRQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4683fef789b0.js b/pr/451/docs/search.index/4683fef789b0.js deleted file mode 100644 index 92b00555f..000000000 --- a/pr/451/docs/search.index/4683fef789b0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9JQQAAABAAEAAQDzAgFz9TkJAAABAAEAAQB/BgEGAPToEQAAAQABAAEAAQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQAzBgOwYBNqAIphcnXzYRAAAHcCAQDzgAJhZQEDAPSbEQAAAQABAAEAAQIA9LAPAAABAE0EAQABggKgEAAAE0CwQBLeAAFhcwGAAqAAAAAMBrBwEHIAAXJz5wcAAIegcAAAEeeRBUE6MAAAAQAAAAAADAAQAAAA4wHkAeUB5gFsB9UH1gdCCUMM7g/vD/APARE=") \ No newline at end of file diff --git a/pr/451/docs/search.index/46dda721da8a.js b/pr/451/docs/search.index/46dda721da8a.js new file mode 100644 index 000000000..7a340409f --- /dev/null +++ b/pr/451/docs/search.index/46dda721da8a.js @@ -0,0 +1 @@ +rn_("AQEAOzAAAAEAAAYAAQDCFAYA8wYBZfRvFAAAAQA0AAEAEwMDsEAUmwABsGAThwCJYXJ183sQAAB5AgEAI4ACgBF7ABABZHI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4701aa0c1499.js b/pr/451/docs/search.index/4701aa0c1499.js deleted file mode 100644 index c9d3b1562..000000000 --- a/pr/451/docs/search.index/4701aa0c1499.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYYCoAAAABM6oAAAABM7ZHIBgAKgcAAAE82gMAAAEP1mdAELAaBAAAAUH2XyTRQAAAEAI4ECsFAQ1AABY3YBAAGwgBQhAAFw8h0UAAABAEMGA6AgAAARRaBAAAASmWRlc/PgDwAASwMBAPMABGlyc3Y7MAAAAQAADwAFAOkGBQDAEgUA7RIBAGYTAABYFAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/4702bdda8368.js b/pr/451/docs/search.index/4702bdda8368.js deleted file mode 100644 index 8a0d05dc5..000000000 --- a/pr/451/docs/search.index/4702bdda8368.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQ0A9cMTAAABAAEAegABAAECAPTvEAAAAQCRAQEAAQMA9FoRAAABAHsBAQDzAAJidQEBEAAAAQMA9O0QAAABAJEBAQDzhAJkcwECAPP/CAAAAQBYAPOAAml0ARgA86oUAAABAAEAI4ECsMATwQABY20BBQGwYBKpAAFy9A8MAAABAO8DAQABCQD21BIAAAEA/wCJADUAAQATgAKwYBCuAAFkbgEDAaBwAAATEWf0TwcAAAEABgIBAAEJAPNjFAAAAQA7AAGBArCAFHMAAaBgAAAUm2RzFwEAiYWw0BSdAAGgQAAAFEawYBQWAFACkApnugAAjLBwERIAAbCwEyAAAaCgAAAS07FQFJQAAbDwE/IAAW3BD/SGAAAAAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/4715969cb3bf.js b/pr/451/docs/search.index/4715969cb3bf.js new file mode 100644 index 000000000..188aa23e0 --- /dev/null +++ b/pr/451/docs/search.index/4715969cb3bf.js @@ -0,0 +1 @@ +rn_("AQ0A9eATAAABAAEAeAABAAEHAPPIEwAAagABACOAAqEQAAAUGW5yAQcA9LYRAAABAAEAAQAzAASgIAAACSiw4BQOAAExZWlyOjAAAAEAAAAAAAkAEAAAAGAEYQROBwoJbAxtDG4MbwxYEVkRAQIA9AoRAAABAJIBAQABAwD0dREAAAEAfAEBAPMAAmJ1ARsQAAABAwD0CBEAAAEAkgEBAPOEAmRzAQIA8wwJAAABAFkA84ACaXQBGADzxhQAAAEAAQAjgQKwwBPeAAFjbQENAbBAFGYAAWX1KhQAAAEAAQABAAEAE4ICsAARIAABa3MBBAD0rhAAAAEAAQABAAGBAqAgAAARG7BAE3MAAWVp84ICZXQBBAGwYBLFAAFy9CEMAAABAPcDAQDzgANhZW8BCQD28BIAAAEAAAGHADYAAQATgAKwYBDIAAFkbgEGADswAAABAAAFAAIA8xADAIwTAQABAwGgcAAAEy1n9FsHAAABAAgCAQABCQDzfhQAAAEAPAABgQKwgBSQAAGgYAAAFLdkcxcBAIiFsNAUuQABoEAAABRhsGAUMQBSApAKAQQA9MwTAAABAEoAAQBTgAOwkBSyAAFlaG8BAAGgQAAAEYJlAcYPAAAjgQKwUBEtAAFrdHfsAwCOoZAAABSnsLATPQABoKAAABLvsVAUsAAB/cEPOzAAAAEAACEACACBAAAAhwADAHIFAQA3BwcAMwkHAEIMBwBTEAEAmhIAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/482faea55872.js b/pr/451/docs/search.index/482faea55872.js new file mode 100644 index 000000000..93e875f8a --- /dev/null +++ b/pr/451/docs/search.index/482faea55872.js @@ -0,0 +1 @@ +rn_("AQQBoEAAABGDZQE7EAAAAQgA9MYRAAABAAEAAQABAwGgQAAAEYJlAcYPAAABAgDzLwwAAAEAAQAjAAKwIAwdAAFrcvbwBgAAAQAUCu0AAQA0AvOAAnJ4AQMBsIAUXwABdjswAAABAAALAAMAFhEBANERBQDCEwMAI4ACsGAQhwDBYXIBgAKgsAAAE+qwMA/EAAFlaAEDAaBAAAARFWXyFQkAAAEA1xMAAIugIAAAB2awsBM5AAGgkAAAEvyggAAAEcqgAAAAE0afGEw6MAAAAQAAAAAADAAQAAAADAJ8BH0EfgQGDAcMcAxxDHIMBg4HDkcTXRM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/48769e729a3a.js b/pr/451/docs/search.index/48769e729a3a.js deleted file mode 100644 index 72a2b33d3..000000000 --- a/pr/451/docs/search.index/48769e729a3a.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBoKAAABNSZPLnBgAAAQABgQKgMAAAFFigUAAAE+FjcwEEA7AgETEAAaBQAAAR3aBAAAAUIGxzdvIbFAAAAQDzAANhZW87MAAAAQAACAADAIIEAQDzCwAA/QwFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/48cb78020f8e.js b/pr/451/docs/search.index/48cb78020f8e.js new file mode 100644 index 000000000..e88abd865 --- /dev/null +++ b/pr/451/docs/search.index/48cb78020f8e.js @@ -0,0 +1 @@ +rn_("AYMCsQAUvgABsNAUrgABa20BCwDzARQAAAEAdQAzAoSgYAAAEpWhAAAAFHkEgAM7MAAAAQAANAACAE8HAACgBzMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/491826f98ad3.js b/pr/451/docs/search.index/491826f98ad3.js new file mode 100644 index 000000000..35e981ed4 --- /dev/null +++ b/pr/451/docs/search.index/491826f98ad3.js @@ -0,0 +1 @@ +rn_("AQkA834UAAABADwAAYACoEAAABNdsCAUGgABaGkBAQDz+Q8AAAEAAQATgAKwwBS5AAF0dQGBArCAFJAAAaBgAAAUt2RzNwMAg4agQAAAFGGwYBQxAFICkA4BEgGwABSEAAFzOzAAAAEAAAUAAQCdFAUAAQDGoFAAABLioAAAABCZsCARTAABoFAAABH4oQAAABSYoEAAABQ7AAMCCzswAAABAAAVAAcArgUAADIMAADoDAAAChANAHARAAD4EgEANhQBAAEBAbCwFIsAAWw7MAAAAQAADAADACUHCAAGDQEASBABAAEBAaAwAAARKGjzAQkAAC4AsQgBAgD01xEAAAEAAQABABOBArAgEdsAAWFz84ICbm/3AQCAh6AgAAAFrbBADBgAARUBBw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/493e1a0b131a.js b/pr/451/docs/search.index/493e1a0b131a.js new file mode 100644 index 000000000..e59658a9a --- /dev/null +++ b/pr/451/docs/search.index/493e1a0b131a.js @@ -0,0 +1 @@ +rn_("AQgA9HUQAAABALMAAQABAAGggAAAFLhm8voIAACeCgEHAPVmEwAAAQABAAEAAQAjgAKgUAAAEWNhb/MCAmZzOzAAAAEAAAgABABNBwAAfgcFAAQNAACbEQAAM4ADoEAAAAwNYWVv") \ No newline at end of file diff --git a/pr/451/docs/search.index/49737cee27c8.js b/pr/451/docs/search.index/49737cee27c8.js new file mode 100644 index 000000000..f4a702dc8 --- /dev/null +++ b/pr/451/docs/search.index/49737cee27c8.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwDLEQIAfhMBABQUAgABBADzzhEAAAEAAQAjAAKgYAAAEv1hcvPvEAAAAQDJAQGAAqEQAAAUo7CAFJkAAWl1NwQAgYexUBTAAAGgAAAAEM+wcBPeAAGhMAAAFLQKQBsBAAA7MAAAAQAAjAABAAgOjAABAgD2ohMAAAEAqgBZABgAAQAjgQKgIAAAEQ9ydvOAAmVvI4ACoCAAABKaZHQBAAA7MAAAAQAA8AAEAMYFAAABCO0AgwwAAHERAADzgANkbnY=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4ae5903ada8f.js b/pr/451/docs/search.index/4ae5903ada8f.js new file mode 100644 index 000000000..88c25b840 --- /dev/null +++ b/pr/451/docs/search.index/4ae5903ada8f.js @@ -0,0 +1 @@ +rn_("AQkBoEAAABRlZfKJFAAAAQABgAKwAAzpAAGggAAAE8tkcAECA7BgE0gAAbDQFGgAAaCAAAAT6mlycwFzFAAA1wAAgIWwMA/EAAGhMAAAFIiQSQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4b5efd7b6a23.js b/pr/451/docs/search.index/4b5efd7b6a23.js new file mode 100644 index 000000000..cacb27d33 --- /dev/null +++ b/pr/451/docs/search.index/4b5efd7b6a23.js @@ -0,0 +1 @@ +rn_("AQEA9UoJAAABAAEAAQABAPMAAWnyshQAAAEAAQQCoCAAABHdoDAAABKoYmgByg8AAKcAAIDFoBAAAAzusBAUmwABoDAAABB8BBAoAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4bd0dc5c36db.js b/pr/451/docs/search.index/4bd0dc5c36db.js deleted file mode 100644 index b41bd2c91..000000000 --- a/pr/451/docs/search.index/4bd0dc5c36db.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA8+EPAAABAAEAAYmEoIAAABSboEAAABRGoGAAABRmoJAAABSfAoAKE4EDoKAAABPUsNAUPwABY2l1M4ADsMAUnQABYXR1") \ No newline at end of file diff --git a/pr/451/docs/search.index/4c6ccd5ef6f9.js b/pr/451/docs/search.index/4c6ccd5ef6f9.js new file mode 100644 index 000000000..54cc71c63 --- /dev/null +++ b/pr/451/docs/search.index/4c6ccd5ef6f9.js @@ -0,0 +1 @@ +rn_("AQMAOzAAAAEAAAUAAQDcEgUAAQQBoFAAABQnaPK6EgAAAQABAAKwQBO6AAGgoAAAFEdlcjowAAABAAAAAAAHABAAAADCBsMGYQdiB+4P7w/mEOcQ84ADZ2lz") \ No newline at end of file diff --git a/pr/451/docs/search.index/4d134b527dd8.js b/pr/451/docs/search.index/4d134b527dd8.js deleted file mode 100644 index 0e3005109..000000000 --- a/pr/451/docs/search.index/4d134b527dd8.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAcAAwCwEQIAYRMBAPgTAgABBADzsxEAAAEAAQAjAAKgYAAAEuFhcvPUEAAAAQDIAQGAAqEQAAAUh7CAFH0AAWl1NwQAgYexUBSkAAGgAAAAELSwcBPBAAGhMAAAFJgKQBsBAAA7MAAAAQAAiwABAPQNiwABAgD2hRMAAAEArABYABgAAQAjgQKgIAAAEPRydvOAAmVvI4ACoCAAABJ+ZHQBAAA7MAAAAQAA7wAEALwFAAD1B+wAcQwAAFYRAADzgANkbnY=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4d420ba30216.js b/pr/451/docs/search.index/4d420ba30216.js deleted file mode 100644 index 4a784b7f7..000000000 --- a/pr/451/docs/search.index/4d420ba30216.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoLAAABOxYvM7EwAALgEBAAGAAqAgAAAPt6AwAAATvWFy8wABdAFxBwAAAQQBoEAAABRKZfJsFAAAAQABBQA7MAAAAQAACwAFAD8QBQC0EgEA5BMBAD0UAABcFAAA4wGEoDAAAA+1hQACOzAAAAEAAAsABQCcBQIAsQYEAEUMAQBwEQAAxhIAAGOAA6AAAAATOmRucg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4d60442fb3dc.js b/pr/451/docs/search.index/4d60442fb3dc.js deleted file mode 100644 index 0b55a2ee4..000000000 --- a/pr/451/docs/search.index/4d60442fb3dc.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9A0TAAABAFkBAQDzBAFzOzAAAAEAAA0AAgCJEwcAgRQFAEcAAAGGoIAAABNpsQAUkAABsEASxwABsIAUoAABsFATDwABFQAW9IEOAAABAAEAAQABBQDz5g8AAAEAAQBjgISwQBByAAGgAAAABtMA8AA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4dd22e56451b.js b/pr/451/docs/search.index/4dd22e56451b.js new file mode 100644 index 000000000..14c67a960 --- /dev/null +++ b/pr/451/docs/search.index/4dd22e56451b.js @@ -0,0 +1 @@ +rn_("AQIBsIAT6wABdAEUCQAAAYECoBAAABNHoHAAABOPYWPzAQFy8qoRAAAgAPOAAmVpAQAA9BIMAAABAAEAAQDzgAJkcvMAAWX0jwAAAGEMTgcBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4e2f866a26d7.js b/pr/451/docs/search.index/4e2f866a26d7.js deleted file mode 100644 index 9407e2269..000000000 --- a/pr/451/docs/search.index/4e2f866a26d7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsIAUIQABcPIdFAAAAQBDBQOgIAAAEUWgQAAAEplkZXPz4A8AAEsDAQABAgA7MAAAAQAAEwABAAsQEwATgYSgUAAAEPWgEAAADBSgIAAADN0cQAAzgAOwUBNnAAFhZm4=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4e73de40c9f5.js b/pr/451/docs/search.index/4e73de40c9f5.js new file mode 100644 index 000000000..44946cf57 --- /dev/null +++ b/pr/451/docs/search.index/4e73de40c9f5.js @@ -0,0 +1 @@ +rn_("AQkA834UAAABADwAAYADoEAAABNdsCAUGgABoCAAAA/TaGl5AQkBoIAAABS4ZgGYEwAAAQMBsAAUMQBSc/KtEAAALQMjgAKgIAAAD89hcgECAPXcEAAAAQABAF8CAQDzAANpdHU6MAAAAQAAAAAAEAAQAAAAfQcsCfMMORC2EO8SKRMqE94T3xPlEw4UDxQvFDAUhBSFFAGAAqAwAAAUYbCwE7QAAWFsAQEA8/kPAAABAAEAE4ACsMAUuQABdHUBAwDzwBIAAAEAAQABAQD1PxEAAAEAAQABAAEAI4ACsJAUBwABYXQBBAGgQAAAFGVl8okUAAABAAECADswAAABAAALAAUAWRAFANASAQABFAEAWBQAAHcUAAATggKw8BS8AAFmZwGBArCAFJAAAaBgAAAUt2RzAQgA9UIUAAABAAEAAQABAPMCAWb1mhAAAAEAAQABAAEAI4ADoCAAAA/NsKAUjgABZHJ0d+8BAI2wEBQ8AAGw4BQ+AAGX1Q47MAAAAQAAOgETAIcEAQC0BAIApgUCALwGBADuBwEAKQwDAFcMAQDkDAEAnQ4EAfAPAQC3EAEAyhACAPgQAQCLEQAAvhEDAOgRAQDiEgAAphMHAJ0UBQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4ea9347ec9c1.js b/pr/451/docs/search.index/4ea9347ec9c1.js new file mode 100644 index 000000000..7c8afc83f --- /dev/null +++ b/pr/451/docs/search.index/4ea9347ec9c1.js @@ -0,0 +1 @@ +rn_("AQcA9AMSAAABAAEAAQAjAAKgcAAAEgJhcgF4BwAAAQAA9iARAAABAJQDAQAKAAEAAQAAOzAAAAEAAAYAAgCOEwAA9hMFACOIArBAFGwAAWVzAQ0BsEAUZgABZfUqFAAAAQABAAEAAQABBQA7MAAAAQAACAADAPwRAgACEwQAKRQAANcDAACGBbqAxTPrFIQG9cEEAAABAAkIAQABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4ea99a0432f7.js b/pr/451/docs/search.index/4ea99a0432f7.js new file mode 100644 index 000000000..fcaa04b67 --- /dev/null +++ b/pr/451/docs/search.index/4ea99a0432f7.js @@ -0,0 +1 @@ +rn_("AQIA9asRAAABAAEAAQABAAGAArBgExYAAaAgAAARAG95AQABoMAAABQ7afRvEwAACAAcABsAAQMA9JQTAAABAAEAAQABBQD0qBQAAAEAAQABABOAArBwFIEAAWVpAQIA9P4QAAABAJoAAQABAgGgQAAAE0ZyOzAAAAEAAAcAAgD8EAEAkxEFAPOAAmFvZwcAAIewgBNFAFqwoBO8AAEMMAc7MAAAAQAADgAEAJUEBQAcDAAAxhEDAKwSAwCnAACAhaDwAAATz7BQEE8AAaCQAAARipSBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4ec9d96b60ba.js b/pr/451/docs/search.index/4ec9d96b60ba.js deleted file mode 100644 index a9c08babc..000000000 --- a/pr/451/docs/search.index/4ec9d96b60ba.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAgAEAAAAPwIXRAHEQgRCRHfE+ATaxRwFAEBAqBAAAAUU6AwAAARDWVo9fQIAAAuACoDAQB4BQECAPS8EQAAAQABAAEAE4ECsCARwAABYXPzgAJubwECAPSYEAAAAQABAAEAEwACoNAAABRYY3PyCRAAAAEA84ADZWlv") \ No newline at end of file diff --git a/pr/451/docs/search.index/4f020f79d021.js b/pr/451/docs/search.index/4f020f79d021.js new file mode 100644 index 000000000..27976a6a5 --- /dev/null +++ b/pr/451/docs/search.index/4f020f79d021.js @@ -0,0 +1 @@ +rn_("AQACoDAAABEcoJAAABOTMGj1AhIAAJkBAQABAAEAE4ACoDAAAA/MZHIjAYSwEAj3A0CwIAwDAQygMAAAD/QSABj1nAUAAC0AAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/4f8c49c523f4.js b/pr/451/docs/search.index/4f8c49c523f4.js deleted file mode 100644 index 83eb054cf..000000000 --- a/pr/451/docs/search.index/4f8c49c523f4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9OgRAAABAAEAAQAjAAKgcAAAEedhcgFsBwAAAQAA9gURAAABAJMDAQAKAAEAAQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQ0BsEAUSwABZfUPFAAAAQABAAEAAQABBQA7MAAAAQAACAADAOERAgDmEgQADhQAANcDAACGI4//c3rnFIQG9bgEAAABAP8HAQABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/4fbe24f6d57c.js b/pr/451/docs/search.index/4fbe24f6d57c.js new file mode 100644 index 000000000..88517138d --- /dev/null +++ b/pr/451/docs/search.index/4fbe24f6d57c.js @@ -0,0 +1 @@ +rn_("AQUA9AUUAAABAE4AAQDzAwFj8gcTAAABACOAArBwFLwAAWhvAYICsAAS4wABsAAR6gABZXNDAAOgoAAAFFmgIAAAE5JlaXT19hIAAAEAiQABAPgAAYACsHAS2gABoLAAABQZZW8BAgA7MAAAAQAABgABAGQRBgABAgGgQAAAFDpl8mgUAAABAPOAAmlvAQgA9PwTAAABAIsABQATAQOwkBRjAAGwUBNBAWZnbHA7MAAAAQAAEgAJAGkQAgDtEQEALhMBAFETAQCIEwEA7RMBACUUAQA0FAEAdRQBAAGAArBAErgBF6AgAAARKGFpAQMA89cSAAABAAEAI4ECsDAUDAABY2kBAAD0KRMAAAEAWgEBAPMDAXM7MAAAAQAADwADAOgRAQCmEwcAnRQFAAEDAPXmEQAAAQAlAQEAYQHzgAJydgEAADswAAABAAAFAAIAthIBAA4TAwDzAgFkOzAAAAEAAAkAAwDiEQMAsRIDAOUSAQAjgAKgkAAAFFhpbxOAArDwFKwAAWxvAQUA9HQUAAAMACMAEQAjgAKgYAAAE4ZncyOAArCQFFYAAWJs9xcDAI+wcBIAAAGwYBSOAAGwcBP/AAGwYBM/AAGwUBMrAAG1+Rc7MAAAAQAAPgAZALUFAABqBwEA8QgFAAEJAAAvCQAADwwAAF4MAQDfDAIACA0BAJYOAwCbDgEA/A8EANAQAQAMEQAALxEAAE4RCQCMEQAAjhEDAOARAAA1EwAAThMBAGATBQBwEwAAKBQAAIEUAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/4fc7afbad082.js b/pr/451/docs/search.index/4fc7afbad082.js deleted file mode 100644 index 9d81af04f..000000000 --- a/pr/451/docs/search.index/4fc7afbad082.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQoA9IcTAAABAA8AAQABgwKgIAAAEDKgMAAAEM5pdAEAAPTyEgAAAQABAAEA8wQBZPSVEgAAAQABAAEAI4aEsKAUOwABsEAS8AABsHAT4gABFYAAAQACsDAQJAABsBATnwABc3TyjQUAAHoGAYACsMAUaQABoAAAAAdEdHnzgIURwQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5012ed2e4dc3.js b/pr/451/docs/search.index/5012ed2e4dc3.js new file mode 100644 index 000000000..13e3a114f --- /dev/null +++ b/pr/451/docs/search.index/5012ed2e4dc3.js @@ -0,0 +1 @@ +rn_("AQsA9HQUAAAMACMAEQABCgA6MAAAAQAAAAAABwAQAAAAURNSE+0T7hMlFCYUdRR2FPOBAm90EwCEsCATRQBasDARSgABoFAAABEyEDACOzAAAAEAAAgABACEAAEArgADAFgHAQBvCQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/5043dc5e7d82.js b/pr/451/docs/search.index/5043dc5e7d82.js deleted file mode 100644 index ef209609b..000000000 --- a/pr/451/docs/search.index/5043dc5e7d82.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsFAQ6wABoLAAABPhY3MBBADzqA8AAAEAAQABgAKgUAAAELugYAAAESppcgGAAqAgAAAMYqBgAAARKWVoAQEA814MAAABAAEAE4ECoBAAAAxhYWMBgAKwEBOsAAGwMA0DAAFlcyMAAqBAAAAUSmV38mwUAAABAAEDADswAAABAAAFAAIAWgwDAD0RAQABBgD09hIAAAEAmQEBABMAArBgEvgAAWZt8s8MAAABAAEBAPT2EAAAAQCwAAEAI4ACoLAAABPIZGcBggKgoAAAEyqgQAAAEFpvdvOAA2ducgGAAqBAAAAS76AwAAAPpWhstz8AAIqwQA+mAAE0YFc7MAAAAQAAGgAEAAcCAABjBA4A3wsJALwMAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/504ca52d3ae9.js b/pr/451/docs/search.index/504ca52d3ae9.js new file mode 100644 index 000000000..d45566c4f --- /dev/null +++ b/pr/451/docs/search.index/504ca52d3ae9.js @@ -0,0 +1 @@ +rn_("AQcA9AMSAAABAAEAAQAjAQKgcAAAEgJhcgF4BwAAAQMAOzAAAAEAAAgAAgCiDwYAXhMBAPMAAmN0OzAAAAEAAFcAJwCoBAsAbwUAAA4JAQAnDAEALwwCAO8MAAD7DAIADQ0BAMwPAADRDwEA1A8BAN4PAABmEAIAbRABAHIQAQCXEAAADxEAABURAABhEQEAbhEBAIIRAQCGEQIA5hEBAPkRAgD/EQAAtRIAAL4SAQDDEgEADBMBABwTAQAhEwEAQxMBAFMTAwBZEwEAuhMBAM4TAAA6FAAAYxQEAGwUAgA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/508290633409.js b/pr/451/docs/search.index/508290633409.js new file mode 100644 index 000000000..c597cc7e4 --- /dev/null +++ b/pr/451/docs/search.index/508290633409.js @@ -0,0 +1 @@ +rn_("AQQBsEAUZgABZfUqFAAAAQABAAEAAQABAgA7MAAAAQAACwACANQTBQCSFAUA8wEBbTswAAABAAANAAQA0AwAAG0RAADpEgUAThQFAPMBAWXyABIAAAEAM4CEsBAQjAABoCAAAAwXFEAE") \ No newline at end of file diff --git a/pr/451/docs/search.index/5106fdea4a6c.js b/pr/451/docs/search.index/5106fdea4a6c.js new file mode 100644 index 000000000..0c5af044d --- /dev/null +++ b/pr/451/docs/search.index/5106fdea4a6c.js @@ -0,0 +1 @@ +rn_("AQUA9AUUAAABAE4AAQDzBAFj8gcTAAABAAEFAPPXEgAAAQABAPOCAmZ0AQABsAAUXwABZPT7CwAAAQABAAEA84ACZWkTgAKw4BRqAAF0dg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5132048f5f5c.js b/pr/451/docs/search.index/5132048f5f5c.js deleted file mode 100644 index 69535aff6..000000000 --- a/pr/451/docs/search.index/5132048f5f5c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQAOjAAAAEAAAAAAAcAEAAAAMAPwQ+MEI0QBxMIE5kTmhMBBAD0wg8AAAEAAQABAAEAAqAQAAAMcbBQE+YAAWlv8hwJAAABAPOAA2Vpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/51d0879dcf6d.js b/pr/451/docs/search.index/51d0879dcf6d.js new file mode 100644 index 000000000..fded95d74 --- /dev/null +++ b/pr/451/docs/search.index/51d0879dcf6d.js @@ -0,0 +1 @@ +rn_("AYACsFAUFwABoAAAAAkKY3QBAQD0lBMAAAEAAQABAAEDAPTvDAAA3waeAAEAAYACoBAAAAx3oFAAABKiY2QBAgDz/ggAAFADAQDzAAFt86cEAADGAAEAAYECoBAAABNHoHAAABOPYWPzAAFy81oHAABQCiAA5wcAgIewMA/QANMpICM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/51f65056eec1.js b/pr/451/docs/search.index/51f65056eec1.js deleted file mode 100644 index 9244a06d9..000000000 --- a/pr/451/docs/search.index/51f65056eec1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsCATKABaoFAAABEXbXNDgAOw4BPqAAEL31J7XT9kZ3IBAwDzuxIAAAEAAQAjgAKwMBPwAAFjaTOAA7AADPADYmFpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5219aed485bd.js b/pr/451/docs/search.index/5219aed485bd.js new file mode 100644 index 000000000..a830de700 --- /dev/null +++ b/pr/451/docs/search.index/5219aed485bd.js @@ -0,0 +1 @@ +rn_("AQAAOzAAAAEAAAYAAgC2EQMAwBICAAEKAbBAFGYAAWX1KhQAAAEAAQABAAEA8wACaXP0JAwAAAEAUwABACOBAqDgAAAUTWV5") \ No newline at end of file diff --git a/pr/451/docs/search.index/523d09888781.js b/pr/451/docs/search.index/523d09888781.js deleted file mode 100644 index 661af410b..000000000 --- a/pr/451/docs/search.index/523d09888781.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAEMAAgARCQAAcgxCAAEFAPSqEAAAAQABAAEAE4ACsLAThwABYWwBBAD0mQ8AAAEAAQABAHMABKAQAAAQXmJwcnY7MAAAAQAACgACAEkRBgCfEQMAAYACsIAQ+AIisOAT8AABaXIBAwGwQBFGAAFlAYAOAAABBQA7MAAAAQAABgABAEUQBgABAwA7MAAAAQAABQABAMQMBQDzAAJwczowAAABAAAAAAANABAAAACfBaAFtgYFCUcMVAzrEOwQmhKbEvIS8xL0EvUSVwAAAIWwUBCcAjewUA+4ANGgQAAAEXFIgAM7MAAAAQAADwAGAJkEAABeBQMApQ8AAMcRAwCVEgMAyRIBANMAhKBgAAAM2BAIBvOsAQAAAQAKCw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/52703ee3070c.js b/pr/451/docs/search.index/52703ee3070c.js new file mode 100644 index 000000000..3cc4b4124 --- /dev/null +++ b/pr/451/docs/search.index/52703ee3070c.js @@ -0,0 +1 @@ +rn_("AQABoJAAABN3aPI/EwAAAQDzAgFk8hMHAAABAAECADswAAABAAAFAAEAkhQFAPMLAW07MAAAAQAABQABAE4UBQAjgAKwIAzsAAFhbwECAPP+CAAAUAMBAAGKArAAE+YAAbAAE+gAATQ2I4ECoDAAAAx3ZXIBgAKwQBH0AAFIy3gCD45jdbcGAACHsBAQUwABsFAUagABEVEJ9Z0EAAAKAMYAAQCeBw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/528006bc24f9.js b/pr/451/docs/search.index/528006bc24f9.js deleted file mode 100644 index 2c7fd63d9..000000000 --- a/pr/451/docs/search.index/528006bc24f9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA82MUAAABADsAI4ACsAAQxAABbW4BgAOgQAAAE0CwIBP+AAGgIAAAD7toaXkBCQGggAAAFJxmAXsTAAABAwGwABQWAFBz8pMQAAAqA0OAA7AgD7cBKLBQEqAAAWFvcgECAPXBEAAAAQABAF0CAQDzAANpdHU6MAAAAQAAAAAAEQAQAAAAcQcfCeAMHxCcEMYR0xINEw4TwRPCE8gT8hPzExQUFRRnFGgUAQYA9PYSAAABAJkBAQATAgKwYBL4AAFmbfLPDAAAAQAjggKwQBMmAAFlbAGAAqAwAAAURrCwE5cAAWFsAQYA88kPAADfAAEAAQUA9OgTAAABAFAAAQDzAgFj8usSAAABAAECAaAwAAAQXWEBRAwAAAGAArBQERAAAbAgD5MAAWNu84ACbnQBBQA7MAAAAQAADgACANUMAADxDw0AAQIAOzAAAAEAAAcAAQBGCQcA5wUAAAmgMAAADEOgsAAAFBSggAAAEY+w0BQjAAEwYWZpa21ydXY7MAAAAQAAZwIFAPwBAABlA+wAagcAAPIK7AD0DYsAAQEA8+EPAAABAAEAE4ACsMAUnQABdHUBAwDzpBIAAAEAAQABAQD1JBEAAAEAAQABAAEAAQEBsDAP0gDudPZcBwAAAQCKAO4HqQD7AWOAA7CQE+oAAWFtdAEDAaBAAAAUSmXybBQAAAEAE4ACsGAUoAABZXIBAwGgAAAAE8hzAd4QAAABAgA6MAAAAQAAAAAACgAQAAAARAlFCQgQVBNVE4UThhMyFIoUohSjFIMABKCAAAASfrBwEVMAAbAgEPQA8GZucnYB5gcAAAGBAqAAAAAJH6AwAAAPu3N0xwEAgMawABQhAAGwkBJ8AAGgEAAAB0IAEogoAQIAOzAAAAEAAA0ABgA/EAUAUBEBALQSAQDkEwEAPRQAAFwUAAATggKw8BSgAAFmZwGAArCAFHMAAaBgAAAUm2RzE4ACsMATrwABaW8BCAD1JxQAAAEAAQABAAEA8wIBZvWAEAAAAQABAAEAAQAjgAOgIAAAD7WwoBRxAAFkcnT3+wcAj6AwAAAPq5fXDzswAAABAABPAR4AfgQBAKsEAgByBQIAnAUCAL0FAQCxBgQAMwcAAEsHAADiBwEA9QgCACMJAQAXDAMARQwBAG0MAwDRDAEAiA4BAdgPAQBVEAIAnRABALAQAgDdEAEAcBEAAKMRAwDNEQEAhhIAAMYSAAD+EgEAZxMBAIkTBwCBFAUA") \ No newline at end of file diff --git a/pr/451/docs/search.index/52ccda238ef2.js b/pr/451/docs/search.index/52ccda238ef2.js deleted file mode 100644 index e7afe6d90..000000000 --- a/pr/451/docs/search.index/52ccda238ef2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9N8TAAABAIsABQATAAOwkBRIAAGwUBMkAWdnbHA7MAAAAQAAEgAJAE8QAgDSEQEAEhMBADQTAQBrEwEA0BMBAAoUAQAZFAEAWhQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/52ddf1446ab6.js b/pr/451/docs/search.index/52ddf1446ab6.js new file mode 100644 index 000000000..e82176981 --- /dev/null +++ b/pr/451/docs/search.index/52ddf1446ab6.js @@ -0,0 +1 @@ +rn_("AQUA9MsSAAABAKkAAQABCAD0ihMAAAEAhwABADMAA7BAE1kAAWFiZTowAAABAAAAAAAMABAAAAAxBzIHMwf0DPUMxw86EIgQiRCKEIsQoxJ4Ew==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5376c77b4a8d.js b/pr/451/docs/search.index/5376c77b4a8d.js deleted file mode 100644 index 96e540421..000000000 --- a/pr/451/docs/search.index/5376c77b4a8d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYICsEAUSAABsGATHAABZXIBBQD0hRAAAAEAAQABAPOAAmZtAQYAOjAAAAEAAAAAAAgAEAAAAMcPyA93EdQS1RLUE10UkhSTFAEAArCwE+wAAaFAAAAUi2Vn9G0HAAABAAEAAQDDAISgsAAAE72wQBCdAAEUIAT35AYAANcFegMBADkBLQEWAWOAA7AwEAIAAWFlaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/53dbf4b2839c.js b/pr/451/docs/search.index/53dbf4b2839c.js deleted file mode 100644 index 772927ea0..000000000 --- a/pr/451/docs/search.index/53dbf4b2839c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAUAAgBtDAMA/hIBAAECAPNgEQAAAQABAAEGAPSMFAAAAQABAAEA8wYBbfSQEgAAAQABAAEA84ACbnbzgAJhaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/53ffd91bdfe9.js b/pr/451/docs/search.index/53ffd91bdfe9.js new file mode 100644 index 000000000..4561b5d6d --- /dev/null +++ b/pr/451/docs/search.index/53ffd91bdfe9.js @@ -0,0 +1 @@ +rn_("AQMA9BAJAAABAFIDAQABAwDzYAwAAAEAAQDzgAJpbwEBAbBQEQ0AAWP0XQcAAAEAAQAIAiOBAqBQAAAQz2NuAQQAOzAAAAEAAAcAAwDLEQIAfhMBABQUAgABAQA6MAAAAQAAAAAABwAQAAAA7RHuES4TLxOIE4kTNBQ1FPMJAWkBDBEAANcCAACHsMATcwABoFAAAA+1oRAAABReFAEXOzAAAAEAACMAAQC5ASMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/54278f1995a0.js b/pr/451/docs/search.index/54278f1995a0.js deleted file mode 100644 index f1fc533bc..000000000 --- a/pr/451/docs/search.index/54278f1995a0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkBoEAAABRKZfJsFAAAAQABgAKwAAzWAAGggAAAE65kcAECA7BgEysAAbDQFE0AAaCAAAATzWlycwFYFAAA1wAAgIWwMA+sAAGhMAAAFGuQSQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5450ecaeec3d.js b/pr/451/docs/search.index/5450ecaeec3d.js new file mode 100644 index 000000000..f7807e0d2 --- /dev/null +++ b/pr/451/docs/search.index/5450ecaeec3d.js @@ -0,0 +1 @@ +rn_("AQMBgBOAABD4czswAAABAAAFAAEAYBMFACOCAqCQAAAUYmlzBwEAhMWwUBMnAAGgoAAAE8uwcBOQAAGg0AAAFG6AARgI") \ No newline at end of file diff --git a/pr/451/docs/search.index/54a3a24e98f0.js b/pr/451/docs/search.index/54a3a24e98f0.js deleted file mode 100644 index 5dcde6e96..000000000 --- a/pr/451/docs/search.index/54a3a24e98f0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9ZARAAABAAEAAQABAAGAArBgEvoAAaAgAAAQ5W95AQABoMAAABQgafRSEwAACAAcABsAAQYA9IwUAAABAAEAAQABAgD04xAAAAEAmgABAAECAaBAAAATKXI7MAAAAQAABwACAOEQAQB4EQUA84ACYW9nAwAAhrCAEygAWrCgE58AAQwwBjswAAABAAAIAAMACgwAAKsRAwCQEgMA84ICZWkjAAKggAAAFJxmaPLtCAAAjgoBBQDzAhMAAAEAAQABAQA7MAAAAQAABQACAO4LAwDbEQEAUwADsDAUOwABZXBy9MwPAAABAOwAAQABAAOwQBQAAAGhQAAAFHCgUAAAEdFiY2YB/QYAAPOAAm1uAQGEoLAAABO9sEAQnQABsMAT7AABoGAAABF3FCAE9uQGAABRCQEAOQEtARYBAQUAOzAAAAEAAAUAAQDAEgUAAQcA9UkTAAABAAEAAQABACOAAqBQAAARSGFv8wCFIEkCOzAAAAEAAAgABABBBwAAcgcFAPEMAACAEQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/54aec76a13de.js b/pr/451/docs/search.index/54aec76a13de.js deleted file mode 100644 index dca6f636c..000000000 --- a/pr/451/docs/search.index/54aec76a13de.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9FsQAAABALIAAQABAAGggAAAFJxm8u0IAACOCgEHAPVJEwAAAQABAAEAAQAjgAKgUAAAEUhhb/MCAmZzOzAAAAEAAAgABABBBwAAcgcFAPEMAACAEQAAM4ADoEAAAAv8YWVv") \ No newline at end of file diff --git a/pr/451/docs/search.index/5525682f4073.js b/pr/451/docs/search.index/5525682f4073.js new file mode 100644 index 000000000..e064d1550 --- /dev/null +++ b/pr/451/docs/search.index/5525682f4073.js @@ -0,0 +1 @@ +rn_("AQEA9GcHAAB6COEAAQABBQD0SRQAAAEAAQABAAEFAPQ7EwAAAQCUAAEA8wICY3X1VhAAAAEAxgABAAEAYwAEoDAAAAw2oHAAABHhZW5zdvN0EAAAnwAjAg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/559b896a9166.js b/pr/451/docs/search.index/559b896a9166.js deleted file mode 100644 index d093d9394..000000000 --- a/pr/451/docs/search.index/559b896a9166.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA87MRAAABAAEAAYICsEASqwABsEASrQABNDYBCwDzqxMAAGwAAQABBgD0mRQAAAEACgABAPMCAXIB9RMAAPMCAWU7MAAAAQAABQACAGUQAwAVEQEAE4ACoFAAABLhZHTzAIQRAQH31BAAAAEAbgAdAAEAAQA7AQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/56a96cba0f24.js b/pr/451/docs/search.index/56a96cba0f24.js deleted file mode 100644 index a401a807d..000000000 --- a/pr/451/docs/search.index/56a96cba0f24.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9eQSAAABAPEAgAABABOAArAwExYBSmlwAQYA85MTAAABAAEA8wICYnMBJhAAAPMBAWYBWgcAAAECAPQ+EwAAAQCYAAEAM4ADsEATCwABaXV5") \ No newline at end of file diff --git a/pr/451/docs/search.index/56f51737f884.js b/pr/451/docs/search.index/56f51737f884.js new file mode 100644 index 000000000..9cb37da96 --- /dev/null +++ b/pr/451/docs/search.index/56f51737f884.js @@ -0,0 +1 @@ +rn_("AQIA9asRAAABAAEAAQABAAGAArBgExYAAaAgAAARAG95AQABoMAAABQ7afRvEwAACAAcABsAAQUA9KgUAAABAAEAAQATgAKwcBSBAAFlaQECAPT+EAAAAQCaAAEAAQIBoEAAABNGcjswAAABAAAHAAIA/BABAJMRBQDzgAJhb2cDAACGsIATRQBasKATvAABDDAGOzAAAAEAAAgAAwAcDAAAxhEDAKwSAwDzgAJlaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5718d5014774.js b/pr/451/docs/search.index/5718d5014774.js new file mode 100644 index 000000000..1faad6f1b --- /dev/null +++ b/pr/451/docs/search.index/5718d5014774.js @@ -0,0 +1 @@ +rn_("AQEA9qEFAAABACkNAQCpAAEAAQAAOzAAAAEAAAcAAwARAgEAaQwBAB4QAwABgwKwcBOZAAGwgBPSAAFjcwEFAPRJFAAAAQABAAEAAQUA9DsTAAABAJQAAQDzCAJjdfVWEAAAAQDGAAEAAQABAgD0vhEAAAEAAQABABMAAqAwAAARiWV09MQQAAABAAEAAQABAwD0uhEAAAEAAQABAAGAAqAAAAAQlaAAAAAQljQ28wMBdgECDQAAlwEAAIawoBMcAAGwIAwkAAGwgBL6AAEJDAY6MAAAAQAAAAAACQAQAAAABQcGBwcHNQz3D18RqRKqEqsSXhQBAgD0JRMAAAEAkgABAAEDAPRbEwAAAQCYAAEA8wACYnU6MAAAAQAAAAAAFQAQAAAASgdLB0wH2A/ZD4UQhhCmEKcQPRE+EfAR8REjEyQTMBMxE0ITbRNuE7YTtxMBAQD0nQUAAAEAUwcBALcAAICFsQAUQAABsAAHVAABDAhEAQgAOzAAAAEAAAUAAQDcEgUA1w0AAIiwQBEtAAGgIAAADQy5AAs7MAAAAQAADwAFAHwAAQD9BgcAYwcBAHUQAQApEQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/571c0d56c455.js b/pr/451/docs/search.index/571c0d56c455.js deleted file mode 100644 index e704c89db..000000000 --- a/pr/451/docs/search.index/571c0d56c455.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBoGAAABR8c/LAEQAAAQABCAD1JxQAAAEAAQABAAEAowCEsCAJYwABoIAAABLTMEAIOzAAAAEAAAUAAgDuAQAAgBAEAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/573b61c91606.js b/pr/451/docs/search.index/573b61c91606.js deleted file mode 100644 index 05d4d3cb1..000000000 --- a/pr/451/docs/search.index/573b61c91606.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYADoIAAABK6sFAS4AA7sHAUFAABYWNzAQABoIAAABScZvLtCAAAjgoBBwD1SRMAAAEAAQABAAEAI4ACoFAAABFIYW/zAAJmczswAAABAAAIAAQAQQcAAHIHBQDxDAAAgBEAAPOAAmR5") \ No newline at end of file diff --git a/pr/451/docs/search.index/5775f8d2dd1c.js b/pr/451/docs/search.index/5775f8d2dd1c.js deleted file mode 100644 index 16404ba84..000000000 --- a/pr/451/docs/search.index/5775f8d2dd1c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQADsCARMQABoFAAABHdoEAAABQgbHN28hsUAAABAAECAPS8EQAAAQABAAEAE4QCsCARwAABYXPzgAJhZROAAn+dwfRDMHJzAQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQAzBwOwYBNqAIphcnXzYRAAAHcCAQAjgAKgEAAADAZlcgEAADswAAABAAAiAA4AuwQBAH4FCgAZCQEAGwwBANMMAQCFDgAAoA8EALcPAADaDwEAzRABAN8QAAByEQAAzxEBANQRAAABAQA6MAAAAQAAAAAADAAQAAAA/AhBDEIMXRByEHMQBxEIEQkR3xPgE2sUcBQBgQKwABFjAAGwABFlAAE0NgEFAPToEwAAAQBQAAEA8wQBY/LrEgAAAQABAwGgQAAAFB9l8k0UAAABAAEFAPO7EgAAAQABAAEEAPPwEgAAAQBiAQEAAPTyEgAAAQABAAEA8wQBZPSVEgAAAQABAAEA5xIAAYmwoBQ7AAGgMAAAEQ2wYBRxAAGwcBPiAAG1yAQ6MAAAAQAAAAAACgAQAAAA9AgiCUwMTQzmD+cP6A/FETETMhMNFBGBAhOwIBHAAAFhcwECAPOxEgAAAQABAPcAAIDFoBAAABCLgAA4CAEFAPSvEgAAAQCoAAEAAQgA9G0TAAABAIgAAQAzAQOwQBM8AAFhYmX14QwAAAEAPgNnAtQAAQAAOzAAAAEAAAYAAgAbDAEAoA8EAPOCAnN1AQYA9GsQAAABALYAAQDzAAJidPVwBQAAAQDVDlcAAQABDwD0ChQAAAEATwABAAEEAPRpEAAAAQC2AAEAI4ACsQAUbgABb3BjgQOwgBODAAFjc3QBCwA7MAAAAQAABgABAAIUBgABDAD0axMAAAEArQABACOAAqBwAAAULWd384ACZW8BDQDzXRQAADUAAQABEADzihQAABgAAQABAQGwYBKpAAFy96wFAAABADgCKgQBAO8DAQABAwGwAAzgAz9z9BcMAAABAAEAAQABAwD0FQwAAAEAMAUBAAGAAqBAAAATwLAwEtwAAWFz84ADbnJ2AQkA9NURAAABAD4BAQABBwD09hAAAAEAsAABAPOAAmhpAQECsCAM3gABsAAMVwABZXPyVwcAAAEAAQUA8wITAAABAAEA8wUBcvTMDwAAAQDsAAEA84ACYmPXngEAjqAgAAAHWaBQAAARb6DgAAAT9bBAFJ0AAbDQE+YAAZf1CzowAAABAAAAAAAcABAAAAACAgMCBAJcBF0EBgkRDFUMVgxZDMQMxQzGDMcMyAzJDIAOXxBgELMQXBFdEakRqhHvEzsUPBRPFFAUAYACoLAAABRYoDAAABEKbG4TAAKwgBQhAAFlcPRqEwAAigApAAEAAQMBsEAUSwABZfUPFAAAAQABAAEAAQABBgD0oRMAAAEAAQABAAEPAPSMFAAAAQABAAEAAQIAOzAAAAEAAAUAAQB1FAUA8w0BbTswAAABAAAFAAEAMxQFAAEEAPWZEgAADgABAJAAAQABAAKgEAAAEUWw8BSWAAFlazswAAABAAAKAAQA/RMAAAIUBgAtFAAARBQBAAECAPSYEAAAAQABAAEAAQIA9FsQAAABALIAAQD3kwEAjaBQAAAQ+aEAAAAUe7EAFH8AAaCgAAATM728AzswAAABAAAmAA0A6gsDAOAPAAAJEAEAGBEHAG4RAAC+EgEAxxIBAMsSAQArEwEASRMEACcUBAAuFAMAnBQAAAECADswAAABAAALAAIAtxMFAHUUBQDzAQFtOzAAAAEAAA0ABAC9DAAAUhEAAM0SBQAzFAUAEwECoDAAAAzBZXPy5REAAAEAAYkCsCAUVgABoDAAABRgaXATAAOgIAAAExGggAAAEyVlaXX1/wsAADkAAQABAAEAAYYCsJAUfQABoAAAABKdYmQBgAKwMBMFAAGgMAAAEN1ydtcAAAOFoDAAABBSoDAAABNcEAETOjAAAAEAAAAAAAkAEAAAAO4I7wjwCCAJIQltEHQQLREuEeESAQYAOjAAAAEAAAAAAAcAEAAAAMAPwQ+MEI0QBxMIE5kTmhMBBwD0fhMAAAEAAQABAHcAAACGoHAAABOWoEAAABMpoHAAABELQ0AFOzAAAAEAABAABwAYAgAATwcBAFYJAQDhEAEA7RABAHgRBQB/EgEAd3cAgIuwsBRpAAGgYAAAEQu1QQ8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/579724b12c01.js b/pr/451/docs/search.index/579724b12c01.js deleted file mode 100644 index edb5a1e22..000000000 --- a/pr/451/docs/search.index/579724b12c01.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYAOzAAAAEAAAkAAQAzEQkAI4ADsGATZwABsDAPsAABaXN0AQgA9ScUAAABAAEAAQABAPMBAWb1gBAAAAEAAQABAAEAY4ADoDAAABMpZWd0") \ No newline at end of file diff --git a/pr/451/docs/search.index/57b5a794dbfd.js b/pr/451/docs/search.index/57b5a794dbfd.js new file mode 100644 index 000000000..be13974a4 --- /dev/null +++ b/pr/451/docs/search.index/57b5a794dbfd.js @@ -0,0 +1 @@ +rn_("AQAA9HoTAAABAAEAAQATAAKwgBNOAAFzdvRtCQAAAQCRCgEAE4ACsDARDQABbG4BgAKgAAAAEJWgAAAAEJY0NvMDAXYBAg0AABMAAqBAAAARiWFz9zUMAADCA2gBSgEBAAEAswEBAgD0JRMAAAEAkgABAAEDAPRbEwAAAQCYAAEA8wACYnU6MAAAAQAAAAAAEwAQAAAASgdLB0wH2A/ZD4UQhhCmEKcQPRE+EfAR8REjEyQTMBMxE0ITthO3E/OAAmR0E4ACoCAAAA0MZXIBBwGgQAAAFDpl8mgUAAABAAEBADswAAABAAAFAAEAXgkFAEcAAACFsDAPrwABoFAAABB4sHARBgABsUAUwgABgSkA8nYFAAABAPOAAmx0lwEAgIaxMBQ8AAGxEBRqAAGgsAAAE10RRQg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/57d12a4129f3.js b/pr/451/docs/search.index/57d12a4129f3.js deleted file mode 100644 index e2d3bdabf..000000000 --- a/pr/451/docs/search.index/57d12a4129f3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsEATVAABoMAAABQUZW8BAgD0vBEAAAEAAQABABMAA7AgEcAAAbCAFCUAAWFzdAHLDAAAAQQA8+YPAAABAAEAs4CEsEATZwABQQAG") \ No newline at end of file diff --git a/pr/451/docs/search.index/5877afb34948.js b/pr/451/docs/search.index/5877afb34948.js new file mode 100644 index 000000000..95e16b370 --- /dev/null +++ b/pr/451/docs/search.index/5877afb34948.js @@ -0,0 +1 @@ +rn_("AYkCsCAUcQABoDAAABR7aXATAAOgIAAAEy2ggAAAE0JlaXX1EAwAADoAAQABAAEAAYYCsJAUmQABoAAAABK5YmQBgAKwMBMhAAGgMAAAEPhydtcAAACFoDAAABBsoDAAABN5EAETOjAAAAEAAAAAAAkAEAAAAPsI/Aj9CC0JLgmHEI4QSBFJEf0S") \ No newline at end of file diff --git a/pr/451/docs/search.index/58b912fa1a43.js b/pr/451/docs/search.index/58b912fa1a43.js deleted file mode 100644 index f997c7198..000000000 --- a/pr/451/docs/search.index/58b912fa1a43.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9K8SAAABAKgAAQABCAD0bRMAAAEAiAABADMBA7BAEzwAAWFiZTowAAABAAAAAAAMABAAAAAlByYHJwfhDOIMrw8gEG4QbxBwEHEQhxJbEwEAADswAAABAAAiAA4AuwQBAH4FCgAZCQEAGwwBANMMAQCFDgAAoA8EALcPAADaDwEAzRABAN8QAAByEQAAzxEBANQRAAABgQKwABFjAAGwABFlAAE0NiOAArCAFHEAAW9wAQABsEATOAABZfNuEQAAWQEBALcBAICFoGAAABELEAEL") \ No newline at end of file diff --git a/pr/451/docs/search.index/58fc7e75991d.js b/pr/451/docs/search.index/58fc7e75991d.js deleted file mode 100644 index f90da6a4c..000000000 --- a/pr/451/docs/search.index/58fc7e75991d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBsIATUAABdfI3CQAAAQAjgAKwEAk1AAEzNgECAPNFBwAA7gEBAAECAPQwCQAAAQD6BwEAAQIA8z4MAAABAAEAE4ACsBAJLgABaXUBAgA7MAAAAQAACwACALcTBQB1FAUA8wkBbTswAAABAAAMAAMAUhEAAM0SBQAzFAUAdzAAAIqgMAAAE82gcAAAEeSgIAAACTKwMBGpAAGgUAAAD+kzhQs7MAAAAQAAMAIEAPoBAAAgAi8BaQcAAPMJ/gA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/596040a74c77.js b/pr/451/docs/search.index/596040a74c77.js deleted file mode 100644 index 7bebba27a..000000000 --- a/pr/451/docs/search.index/596040a74c77.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBgBNjABD6czswAAABAAAFAAEAQxMFACOCAqCQAAAUR2lzAQEA9AkTAAABAJEAAQAjgAKwoBPwAAFjbgGEArCgE/4AAbCgFAAAAWNyAQIAOzAAAAEAAAYAAQBFEAYAE4ICoCAAABPhZXUBAgDzsxEAAAEAAQABgAKwIBQjAAGgYAAAE3JvdmOBA6AQAAATKmFjcwEDADswAAABAAAFAAEAxAwFAAEBAPMOCQAAAQABAAEBAPTjEAAAAQCaAAEAU4ADoMAAABRTbnB1AYACsDAQAgABoWAAABSLYWUBgQKwQBKrAAGwQBKtAAE0NgEFAaBQAAAUDGjynhIAAAEAAQUA9FsQAAABALIAAQBzgISwMBNWAAEBoAQBCADzUhMAAAgAHAATgAKwQBHXAAFhZQEKAPOrEwAAbAABAAEAAaAgAAAQfXIBRREAAAEAAPSvEwAAAQBLAAEApwEAgIWgMAAAEOCgEAAAEQAJICEBCQGgQAAAFEpl8mwUAAABAEOAhKAgAAAUWKCQAAATrqEwAAAUaxBJAAECAPQ+EwAAAQCYAAEAE4ACsEATCwABaXkBAQKgMAAAEuGgQAAAEylncjswAAABAAAHAAIA4RABAHgRBQABgAKwABPJAAGwABPLAAE0NgEGAPSZFAAAAQAKAAEA8wICcnYB9RMAAPMCAWU7MAAAAQAABQACAGUQAwAVEQEA84ACZHTn1x8AEaAwAAARAbDwFJYAAbBwE3MAATBhYmNlaGlrbG5vcHJzdHV2OzAAAAEAABgBRAC6BAAAnwUBAKcFAACpBQEAtgYAABgHAAA+BwIATgcAAGAHBQBnBwAAyAcAAO0HAQDyCAEA+AgBAAUJAABjCQEAZwmLAO4LAwD9CwAAAQwDAA4MAABHDAAAVAwAAJUPAQDADwUAPhAAAGsQAQB/EAAAjBABANQQAQDYEAMA6xABACIRAQBDEQAAYBECAI8RAACvEQAA0REAANURAQDbEQEA5xEEAIsSAACaEgEAnRIAAKkSAQC7EgIA8hIDAAcTAQAUEwEAHBMBACITAQAlEwAAKBMAADoTAABQEwEAbxMBAH4TBACZEwEArBMBAK8TAQC1EwEAwBMAAPsTAgACFAcAIRQBAC0UAABEFAEAbhQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/59718cd9c46f.js b/pr/451/docs/search.index/59718cd9c46f.js new file mode 100644 index 000000000..e14b0f428 --- /dev/null +++ b/pr/451/docs/search.index/59718cd9c46f.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAYBAgDkDAEAnQ4EAQEBAaAAAAATTXP1xgQAAAgL+AMBAAQAAQECoGAAABPOoOAAABQpZXQ7MAAAAQAACAABAM0ECAABgAOwIBKWAAGwsBLQAAGwsBLSAAFiY3PzgARjbHR2") \ No newline at end of file diff --git a/pr/451/docs/search.index/598001827816.js b/pr/451/docs/search.index/598001827816.js deleted file mode 100644 index 6424cb58a..000000000 --- a/pr/451/docs/search.index/598001827816.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9T0JAAABAAEAAQABAPMAAWnylhQAAAEAAQEBoEAAABQJZfJ/FAAAAQABBAKgIAAAEcKgMAAAEoxiaAGyDwAA5wAAgMWgEAAADNugMAAAEGIEECgB") \ No newline at end of file diff --git a/pr/451/docs/search.index/598f05b02570.js b/pr/451/docs/search.index/598f05b02570.js new file mode 100644 index 000000000..aabcc114e --- /dev/null +++ b/pr/451/docs/search.index/598f05b02570.js @@ -0,0 +1 @@ +rn_("AQAA9AoRAAABAJIBAQAjgQKgYAAAE0JiaQEAAPTyDwAAAQD1AAcBI4ECsFATVQABZXMTgAKAB1Ee8AFhcgGJArAgFHEAAaAwAAAUe2lwAQIA9CUTAAABAJIAAQABAwD0WxMAAAEAmAABAPOFAmJ1AQIA9HURAAABAHwBAQDzgAJpbLcBAACFoCAAABMtEgEMOjAAAAEAAAAAABYAEAAAAHIH+wf8B/0H/gcQDEoMSwxMDE0MGxDhEHMTdBOvEzEUfhR/FIMUkBSRFLcUuxQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/59b3fb7f8021.js b/pr/451/docs/search.index/59b3fb7f8021.js deleted file mode 100644 index 2bc88f58f..000000000 --- a/pr/451/docs/search.index/59b3fb7f8021.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABN1sDARzwABaXTy2hIAAAEAAQMAOzAAAAEAAAYAAQBJEQYAAQgA9N8TAAABAIsABQATAQOwkBRIAAGwUBMkAWdnbHA7MAAAAQAAEgAJAE8QAgDSEQEAEhMBADQTAQBrEwEA0BMBAAoUAQAZFAEAWhQBAAGAArAwEcsAAbAwEc0AAXJ2AQQBsAASmgABZDswAAABAAAFAAIAxxEDAMkSAQC3BQABh7BQEpwBFrBgEyIAAZQJAzowAAABAAAAAAALABAAAADMDM0MzgyGDocO8RBzEXQRdRF2ERkTUxMjgAKgEAAADMtubwEDAPNMEAAAAQABACOAAqAAAAAL/WRyYwGEoFAAABFwoDAAABCKFAEQ8owHAAABABOAA7BgEr4AAbAAD5MAAWNnchOAA6CwAAAT/aBAAAAOhWVvcw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/59d3892a5c9c.js b/pr/451/docs/search.index/59d3892a5c9c.js new file mode 100644 index 000000000..ada6e4a9c --- /dev/null +++ b/pr/451/docs/search.index/59d3892a5c9c.js @@ -0,0 +1 @@ +rn_("AQQA9goNAAABAD8DAQAuAQEAAQoA89QSAAABAK0AAQEA9x8JAAABAF0HAQBzAAEAFQEjgAKwYBPvAAFpbwECAPTIDAAAAQBNBgEAAQUA9AUUAAABAE4AAQD3BAAAh6AgAAAMyrCQEfoAAQScAzowAAABAAAAAAAIABAAAACVBRAJEQljDGQMARECEQcTCBM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5a5e2d8166b8.js b/pr/451/docs/search.index/5a5e2d8166b8.js new file mode 100644 index 000000000..d700ccff7 --- /dev/null +++ b/pr/451/docs/search.index/5a5e2d8166b8.js @@ -0,0 +1 @@ +rn_("AYMCsQAUvgABsNAUrgABa20BCwDzARQAAAEAdQAzAYSgYAAAEpWhAAAAFHkEgAM7MAAAAQAANAACAE8HAACgBzMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/5a7dfa305296.js b/pr/451/docs/search.index/5a7dfa305296.js new file mode 100644 index 000000000..514f87c2a --- /dev/null +++ b/pr/451/docs/search.index/5a7dfa305296.js @@ -0,0 +1 @@ +rn_("AQAA9CkTAAABAFoBAQDzBAFzOzAAAAEAAA0AAgCmEwcAnRQFAEcAAAGGoIAAABOGsQAUrAABsEAS4wABsIAUvAABsFATKwABFQAW9JYOAAABAAEAAQABBQDz/g8AAAEAAQBjgISwQBCMAAGgAAAABt4A8AA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5aaa90b55fc2.js b/pr/451/docs/search.index/5aaa90b55fc2.js deleted file mode 100644 index 6c2422dc8..000000000 --- a/pr/451/docs/search.index/5aaa90b55fc2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEAUYQABdDswAAABAAANAAYA4hIDAE4TAQC+EwEA1RMBAFYUAQB9FAEAE4ACsDATFgFKaXABBQDzkxMAAAEAAQATgAKgMAAAE+5leVMBA7BwE/sAAWJlcwEmEAAAE4ADsDAUcQABsCAG8AABZWxz") \ No newline at end of file diff --git a/pr/451/docs/search.index/5ad143d05f59.js b/pr/451/docs/search.index/5ad143d05f59.js deleted file mode 100644 index 5f64db57d..000000000 --- a/pr/451/docs/search.index/5ad143d05f59.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsAAQCQABsCAMTAABZWkBiQKwIBRWAAGgMAAAFGBpcBMAA6AgAAATEaCAAAATJWVpdfX/CwAAOQABAAEAAQABhgKwkBR9AAGgAAAAEp1iZAGAArAwEwUAAaAwAAAQ3XJ21wAAAIWgMAAAEFKgMAAAE1wQARM6MAAAAQAAAAAACgAQAAAA7gjvCPAIIAkhCQ0MbRB0EC0RLhHhEmOAA7AQCOIAAWRtdA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5b1f64117211.js b/pr/451/docs/search.index/5b1f64117211.js new file mode 100644 index 000000000..93ed73bd8 --- /dev/null +++ b/pr/451/docs/search.index/5b1f64117211.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwDLEQIAfhMBABQUAgABBADzzhEAAAEAAQAjAAKgYAAAEv1hcvPvEAAAAQDJAQGAAqEQAAAUo7CAFJkAAWl1NwQAgoexUBTAAAGgAAAAEM+wcBPeAAGhMAAAFLQKQBsTgAKwEBMJAAFpcg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5b4386429020.js b/pr/451/docs/search.index/5b4386429020.js deleted file mode 100644 index 73c0fd837..000000000 --- a/pr/451/docs/search.index/5b4386429020.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABFoZQEhEAAAAQgA9KsRAAABAAEAAQABAwGgQAAAEWdlAa4PAAABAgDzHQwAAAEAAQAjAAKwIAwLAAFrcvblBgAAAQAECu0AAQA0AvOAAnJ4AQMBsIAURAABdjswAAABAAALAAMA+xABALYRBQClEwMAI4ACsGAQbQDAYXIBgAKgsAAAE82wMA+sAAFlaAEDAaBAAAAQ+mXyCAkAAAEA1xMAAIugIAAAB1qwsBMcAAGgkAAAEuCggAAAEa+gAAAAEymfGEw6MAAAAQAAAAAADAAQAAAACAJzBHQEdQT1C/YLXgxfDGAM8g3zDSoTQBM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5bbccf0d1c5c.js b/pr/451/docs/search.index/5bbccf0d1c5c.js deleted file mode 100644 index 4a6125429..000000000 --- a/pr/451/docs/search.index/5bbccf0d1c5c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYECoCAAABR8sEAP5AABYnABBQD0LRMAAAEAmAABAPMDAWMBlBIAAAEMAbAAFGcAAXM7MAAAAQAABQABAIEUBQDzgAJudgGAArAwDEUAAaAwAAAMR2lvAQMBoAAAABRHcwHdEQAAAQQA9AEMAAABAAEAAQDXAwCAhrAABWoAAZFIAQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5bff1c7ebbc7.js b/pr/451/docs/search.index/5bff1c7ebbc7.js new file mode 100644 index 000000000..cc904f3b2 --- /dev/null +++ b/pr/451/docs/search.index/5bff1c7ebbc7.js @@ -0,0 +1 @@ +rn_("AQgA9OsTAAABAFQAAQABAgGgYAAAFJhz8tsRAAABAAGAArAgECMAAaCQAAAUR2FlAQcBsEAUZgABZfUqFAAAAQABAAEAAQABAwGwQBR8AAF08tsTAAABACOEAqBAAAAUC2JzE4ACsMAUsAABdXkBAQD01xEAAAEAAQABABOAArAwEQMAAWRsN3QAAIuwQBO6AAGgUAAAEXCgkAAAE66wUBMJAAGgMAAADGuzmAc7MAAAAQAAZQATAAkCAQBoBAMAxQUAAMIGAQBhBwEABwkBABQJAAAeCQAAUAkAAIQMQwDeDAAA7g8BABgQAADIEAEA5hABACsRAQCrEQQAvBIBADcTAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5c04ad06faf3.js b/pr/451/docs/search.index/5c04ad06faf3.js deleted file mode 100644 index 2b79ce6fd..000000000 --- a/pr/451/docs/search.index/5c04ad06faf3.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCoJAAABQboJAAABQcMjMBgAKgUAAAFEeggAAAE2ZjZfMAAnN08jQHAAABAAEAAaCwAAATsWLzOxMAAC4BAQAjgAKgAAAAEzpkcgEAAbCAFCEAAXDyHRQAAAEAQwADoCAAABFFoEAAABKZZGVz8+APAABLAwEAE4ICoCAAABQNZWnzgAJjZQEEADswAAABAAAJAAQATwkBAIUQAwDHEAEA0hMBALOAhKCQAAAUexABQg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5c06b39c26ac.js b/pr/451/docs/search.index/5c06b39c26ac.js new file mode 100644 index 000000000..00b6f59b7 --- /dev/null +++ b/pr/451/docs/search.index/5c06b39c26ac.js @@ -0,0 +1 @@ +rn_("AQACoKAAABM3oDAAAAzOYWkBeRMAAAEAAbAwEUoAAW7yWAcAAAEAAQEA89QHAADZBwEAEwADsCAQGAMfoFAAABCoZWlz9VsEAAABAAEAAQABAAGEArCgFBoAAbCgFBwAAWNy8wEBdPTsEQAA6wABAAEAAQMBsGAUagABZjswAAABAAAHAAMACAwDAIoTAQASFAEA84ACYWXzgIQAKAU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5c14c7898b88.js b/pr/451/docs/search.index/5c14c7898b88.js new file mode 100644 index 000000000..4dc9b63e2 --- /dev/null +++ b/pr/451/docs/search.index/5c14c7898b88.js @@ -0,0 +1 @@ +rn_("AQIA9LARAAABAAEAAQABAQDzzhEAAAEAAQATgAIxPL7KBa9lcwEKAPTCEwAAAQABAAEAAYACsCAUPgABoGAAABOPb3YBgAKwMBLFAAGgAAAAE0djZPOAhQUQBg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5c174cbd368c.js b/pr/451/docs/search.index/5c174cbd368c.js deleted file mode 100644 index 4ef9427ba..000000000 --- a/pr/451/docs/search.index/5c174cbd368c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoEAAABCTdPfgDAAAPwPuAgEAugCfAAEA8wABczswAAABAAAcAAgAqwQCABcMAwDYDwEAnRABAN0QAQDNEQEAiRMHAIEUBQAjgAKxEBSIAAFhdA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5c2ba014f923.js b/pr/451/docs/search.index/5c2ba014f923.js deleted file mode 100644 index b8553c6c9..000000000 --- a/pr/451/docs/search.index/5c2ba014f923.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsEATnQABZfK3BgAAAQABBADzVRAAAAEAAQBDAAOwQBMAAAGxABPkAAFlZnA7MAAAAQAAJAAGABUCAADGBgwA1AYMAIoPBgD1EAAAQRMBAGOAA7AwDAAABWFlaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5c53e8bde1cc.js b/pr/451/docs/search.index/5c53e8bde1cc.js deleted file mode 100644 index 29953d66b..000000000 --- a/pr/451/docs/search.index/5c53e8bde1cc.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYkCsCAUVgABoDAAABRgaXATAAOgIAAAExGggAAAEyVlaXX1/wsAADkAAQABAAEAAYYCsJAUfQABoAAAABKdYmQBgAKwMBMFAAGgMAAAEN1ydtcAAAKFoDAAABBSoDAAABNcEAETOjAAAAEAAAAAAAkAEAAAAO4I7wjwCCAJIQltEHQQLREuEeESAQMBsDATIAABc/OwEAAAAQABAAEAADswAAABAAAKAAMAVxECAKETAwCMFAMAlwEAAIWgIAAABaewIAfzAAFQGAH1aAwAAAEAAQABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/5d4cba66f76d.js b/pr/451/docs/search.index/5d4cba66f76d.js deleted file mode 100644 index 7d776b212..000000000 --- a/pr/451/docs/search.index/5d4cba66f76d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQA9vcMAAABADgDAQAtAQEAAQoA87gSAAABAKwAAQEA9xIJAAABAFAHAQByAAEAFQEjgAKwYBPSAAFpbwECAPS1DAAAAQBEBgEAAQUA9OgTAAABAFAAAQD3BAAAh6AgAAAMt7CQEd8AAQScAzowAAABAAAAAAAIABAAAACLBQMJBAlRDFIM5hDnEOsS7BI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5d6612847f3a.js b/pr/451/docs/search.index/5d6612847f3a.js new file mode 100644 index 000000000..d47b6f0a5 --- /dev/null +++ b/pr/451/docs/search.index/5d6612847f3a.js @@ -0,0 +1 @@ +rn_("AYACsFARBgABoLAAABP+Y3MBBADzwA8AAAEAAQABgAKgUAAAENagYAAAEUVpcgGAAqAgAAAMdKBgAAARRGVoAQEA83AMAAABAAEAE4ECoBAAAAxzYWMBgAKwEBPJAAGwMA0WAAFlcyMAAqBAAAAUZWV38okUAAABAAEDADswAAABAAAFAAIAbAwDAFgRAQABBgD0EhMAAAEAmQEBABMAArBgExQAAWZt8uIMAAABAAEBAPQREQAAAQCwAAEAI4ACoLAAABPlZGcBggKgoAAAE0egQAAAEHRvdvOAA2ducgGAAqBAAAATC6AwAAAPvWhsV38AAIugcAAAEzewQA++AAE8YFc7MAAAAQAAGgAEAAsCAABsBA4A8AsJAM8MAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5d7b5c402287.js b/pr/451/docs/search.index/5d7b5c402287.js new file mode 100644 index 000000000..9445644df --- /dev/null +++ b/pr/451/docs/search.index/5d7b5c402287.js @@ -0,0 +1 @@ +rn_("AQEA9yIRAAABAAEA2AIBAIsABQABAgD0shAAAAEAAQABABOAAqDQAAAUc2Nz84ACZW8jgQKwAAdSAAFsdCOAA7AABbMAAbCgFAkAAWRydA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/5dd78db990e0.js b/pr/451/docs/search.index/5dd78db990e0.js deleted file mode 100644 index 2f6f58861..000000000 --- a/pr/451/docs/search.index/5dd78db990e0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9csRAAABACQBAQBiAQEBAPcHEQAAAQABANYCAQCLAAUAAQIA9JgQAAABAAEAAQATgAKg0AAAFFhjc/OAAmVvI4ACsAAHRgABbHTzAAJhcvKTDwAAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5e2391be14db.js b/pr/451/docs/search.index/5e2391be14db.js deleted file mode 100644 index a0ba7dfa9..000000000 --- a/pr/451/docs/search.index/5e2391be14db.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9C4UAAABAAEAAQABBQD0HhMAAAEAlAABAPMAAmN19TwQAAABAMUAAQABAAEJAPNjFAAAAQA7AAGBArCAFHMAAaBgAAAUm2RzFwEAhIWw0BSdAAGgQAAAFEawYBQWAFACkAojgAKgcAAAEURkcgELAaBAAAAUSmXybBQAAAEAE4ACsDAQWAABZHLzAgFl9NEHAAABAAEAAQAjgAKwIAv1AAFleQEKAPOkEgAAAQABAAELADswAAABAAAHAAEAiRMHAAECAPToEwAAAQBQAAEAtwAAgIWwcBLaAAGw0BSQAAFgIAYBBgDzsRIAAAEAAQABAwGwQBRhAAF08r4TAAABACOFAqBAAAAT7mJzIwIDsEAQ6AABoFAAABFVYWJm9/oIAAABAEgAawcBAGEAAQDzgAJpcAEEAPNVEAAAAQABAPMAAXA7MAAAAQAADAABANQGDAAjgAOgMAAADAWwcBOdAAFhZWnzgANkbW4BAAA6MAAAAQAAAAAABwAQAAAABQlUDJoSmxLyEvMS9BL1EvMCAWQ7MAAAAQAACQADAMcRAwCVEgMAyRIBACOAAqCQAAAUPWlvAYADsDATUAABgBOTABABsFARBQABY3N48wABZQHDBAAA5xUAgImgAAAADGWgQAAADL6wQAy/AAGQSF0=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5e3e1ae9d4f1.js b/pr/451/docs/search.index/5e3e1ae9d4f1.js deleted file mode 100644 index 8239434d0..000000000 --- a/pr/451/docs/search.index/5e3e1ae9d4f1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUAOzAAAAEAAAMBAgDRDAEAiA4BAQEBAaAAAAATMHP1vQQAAPkK8wMBAAQAAQECoGAAABOxoOAAABQOZXQ7MAAAAQAACAABAMQECAABgAOwIBJ6AAGwsBK0AAGwsBK2AAFiY3PzgARjbHR2") \ No newline at end of file diff --git a/pr/451/docs/search.index/5e4bf3861ee8.js b/pr/451/docs/search.index/5e4bf3861ee8.js new file mode 100644 index 000000000..68cb7dfa8 --- /dev/null +++ b/pr/451/docs/search.index/5e4bf3861ee8.js @@ -0,0 +1 @@ +rn_("AQUA9IgQAAABAAEAAQABEwD1xBQAAAEAAQABAAEAE4ACoMAAABNMYW8BAgKgsAAAFFigsAAAFFljc/PQEAAAAQBeAAGBArBQEfIAAaDAAAAUJ2hp84ACZWkjgAKwsBOMAAFpdAEFAbCAFF8AAXY7MAAAAQAABwACANERAwDCEwMAI4ACsHAR9gABYmR3AgAAhqCgAAATr6CQAAATeAXgAjswAAABAAAFAAIAFQcDAAEQAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5e4f7698639b.js b/pr/451/docs/search.index/5e4f7698639b.js new file mode 100644 index 000000000..d493f7392 --- /dev/null +++ b/pr/451/docs/search.index/5e4f7698639b.js @@ -0,0 +1 @@ +rn_("AQABsPAUsgABazswAAABAAAIAAMAGRQAAB4UBgBIFAAAEwACsQAUmwABZG7y2hIAAAEAE4ACsBAMUwABZW8BBQA7MAAAAQAADQABAJwRDQABAgDzBxAAAAEAAQABAgD0AxAAAAEAAQABAAEDADswAAABAAAIAAEAuRAIAAGAAqBAAAARELDAFFwAAWVzAQEAOzAAAAEAABMAAQAlEBMAI4ACsJATxgABaW03NgAAiqAQAAAMJqBQAAAUJ7BwFAcAAaAgAAAM8JxVETswAAABAAAGAAMAVgcAAOoQAwC6EgEA84ACbHQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5e60a22686e2.js b/pr/451/docs/search.index/5e60a22686e2.js deleted file mode 100644 index f4747a0a8..000000000 --- a/pr/451/docs/search.index/5e60a22686e2.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9u4MAADmAlgAAQDPAgEAAQQAOzAAAAEAAAoABAB1EAUAthIBABcTAQA+FAAAAYYCoAAAABM6oAAAABM7ZHLzAgF2AWYTAADzgAJlbycBAADHoDAAAA/ToHAAABKGoIAAABQNsNATrAABoLAAABQNQEUICTswAAABAAAJAAMAtAUCABMHBABIDAEAI4ACKBg73U/0ZHIBAAA6MAAAAQAAAAAACgAQAAAARAlFCQgQVBNVE4UThhMyFIoUohSjFBOAAqAwAAAQi2x084ACYWU=") \ No newline at end of file diff --git a/pr/451/docs/search.index/5f1f66ca795b.js b/pr/451/docs/search.index/5f1f66ca795b.js deleted file mode 100644 index c00b987f1..000000000 --- a/pr/451/docs/search.index/5f1f66ca795b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9gURAAABAJMDAQAKAAEAAQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQ0BsEAUSwABZfUPFAAAAQABAAEAAQABBQA7MAAAAQAACAADAOERAgDmEgQADhQAANcBAACFI4//c3rnFIQC87gMAAABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6024cbf2ac8d.js b/pr/451/docs/search.index/6024cbf2ac8d.js new file mode 100644 index 000000000..d6a1a9ad3 --- /dev/null +++ b/pr/451/docs/search.index/6024cbf2ac8d.js @@ -0,0 +1 @@ +rn_("AQEBsIATbQABdfJECQAAAQAjgAKwEAlCAAEzNgECAPNRBwAA7wEBAAECAPQ9CQAAAQAICAEAAQIA81AMAAABAAEAE4ACsBAJOwABaXUBAgA7MAAAAQAACwACANQTBQCSFAUA8wkBbTswAAABAAAMAAMAbREAAOkSBQBOFAUAdzAAAIqgMAAAE+qgcAAAEf+gIAAACT+wMBHEAAGwUBABAAEzhQs7MAAAAQAANAIEAP4BAAAkAjIBdQcAAAIK/wA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6025c125c951.js b/pr/451/docs/search.index/6025c125c951.js deleted file mode 100644 index 5579a9018..000000000 --- a/pr/451/docs/search.index/6025c125c951.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBsEAUSwABZfUPFAAAAQABAAEAAQABAgA7MAAAAQAACwACALcTBQB1FAUA8wEBbTswAAABAAANAAQAvQwAAFIRAADNEgUAMxQFAPMBAWXy5REAAAEAM4CEsBAQcgABoCAAAAwGFEAE") \ No newline at end of file diff --git a/pr/451/docs/search.index/60287e11cc71.js b/pr/451/docs/search.index/60287e11cc71.js new file mode 100644 index 000000000..a94ca7392 --- /dev/null +++ b/pr/451/docs/search.index/60287e11cc71.js @@ -0,0 +1 @@ +rn_("AQIA9CUTAAABAJIAAQABAwD0WxMAAAEAmAABAPMFAmJ1OjAAAAEAAAAAAAcAEAAAAIUQhhA9ET4R8BHxETATMRMBAgD0dREAAAEAfAEBAAEFAPN7EQAAAQABAAEEAPTLEgAAAQCpAAEA84CEEQkA") \ No newline at end of file diff --git a/pr/451/docs/search.index/604e947796a4.js b/pr/451/docs/search.index/604e947796a4.js deleted file mode 100644 index 0ab4b8dc9..000000000 --- a/pr/451/docs/search.index/604e947796a4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9O8QAAABAJEBAQAjgQKgYAAAEyViaQEAAPTaDwAAAQDyAAcBI4ECsFATOAABZXMTgAKAB0Ue4AFhcgGJArAgFFYAAaAwAAAUYGlwAQIA9AkTAAABAJEAAQABAwD0PhMAAAEAmAABAPOFAmJ1AQIA9FoRAAABAHsBAQDzgAJpbLcBAACFoCAAABMREgEMOjAAAAEAAAAAABYAEAAAAGYH7wfwB/EH8gf/CzgMOQw6DDsMARDGEFYTVxOSExYUYxRkFGYUcxR0FJsUnxQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/607b24e31083.js b/pr/451/docs/search.index/607b24e31083.js new file mode 100644 index 000000000..f4253903a --- /dev/null +++ b/pr/451/docs/search.index/607b24e31083.js @@ -0,0 +1 @@ +rn_("AYMCoJAAABQ2oJAAABQ3MjMBgAKgUAAAFGKggAAAE4NjZfMAAnN08kAHAAABAAEAAaCwAAATzmLzWBMAAC4BAQAjgAKgAAAAE1dkcgEAAbCAFDwAAXDyOBQAAAEAQwADoCAAABFgoEAAABK1ZGVz8/gPAABQAwEAE4ICoCAAABQoZWnzgAJjZQEEADswAAABAAAJAAQAXAkBAJ8QAwDiEAEA7xMBAPOAA2VpeQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/608f704c3516.js b/pr/451/docs/search.index/608f704c3516.js deleted file mode 100644 index bfd262ed8..000000000 --- a/pr/451/docs/search.index/608f704c3516.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQQBoEAAABQJZfJ/FAAAAQABAQA7MAAAAQAABgABAKYUBgDzBgFl9FQUAAABADMAAQAzAQOwYBNqAIphcnXzYRAAAHcCAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/60e98a5178e4.js b/pr/451/docs/search.index/60e98a5178e4.js new file mode 100644 index 000000000..d217b0300 --- /dev/null +++ b/pr/451/docs/search.index/60e98a5178e4.js @@ -0,0 +1 @@ +rn_("AQYAOzAAAAEAAAkAAQBOEQkAI4ADsGAThAABsDAPyAABaXN0AQgA9UIUAAABAAEAAQABAPMBAWb1mhAAAAEAAQABAAEAY4ADoDAAABNGZWd0") \ No newline at end of file diff --git a/pr/451/docs/search.index/60eabdb6a059.js b/pr/451/docs/search.index/60eabdb6a059.js new file mode 100644 index 000000000..90fd8382b --- /dev/null +++ b/pr/451/docs/search.index/60eabdb6a059.js @@ -0,0 +1 @@ +rn_("AYMCsCARhAEioJAAABQpbXABggKwoBQaAAGwoBQcAAFjcgECAaBAAAARg2UBOxAAADOAhLAgEQEAAbAgD7YAAVAUAPOAAm9y") \ No newline at end of file diff --git a/pr/451/docs/search.index/60fbf2ca92b7.js b/pr/451/docs/search.index/60fbf2ca92b7.js deleted file mode 100644 index 5dcc5de1d..000000000 --- a/pr/451/docs/search.index/60fbf2ca92b7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA8wITAAABAAEAAQAAOzAAAAEAAAUAAgDuCwMA2xEBAPMAAWT04xAAAAEAmgABAFMAA7AwFDsAAWVwcvTMDwAAAQDsAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6116929fe6af.js b/pr/451/docs/search.index/6116929fe6af.js deleted file mode 100644 index accf5acf0..000000000 --- a/pr/451/docs/search.index/6116929fe6af.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsPAUlgABazswAAABAAAIAAMA/RMAAAIUBgAtFAAAEwACsQAUfwABZG7yvhIAAAEAE4ACsBAMQQABZW8BBQA7MAAAAQAADQABAIERDQABAgDz7g8AAAEAAQABAgD06g8AAAEAAQABAAEDADswAAABAAAIAAEAnxAIAAGAAqBAAAAQ9bDAFEEAAWVzAQEAOzAAAAEAABMAAQALEBMAI4ACsJATqQABaW03NgAAiqAQAAAMFKBQAAAUDLBwE+oAAaAgAAAM3ZxVETswAAABAAAGAAMASgcAAM8QAwCeEgEA84ACbHQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/61405852662c.js b/pr/451/docs/search.index/61405852662c.js deleted file mode 100644 index 1f223cf09..000000000 --- a/pr/451/docs/search.index/61405852662c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9ScUAAABAAEAAQABAPMAAWY7MAAAAQAABQACAO4BAACAEAQAAQIAOzAAAAEAAAsAAgC3EwUAdRQFAPMGAW07MAAAAQAADQAEAL0MAABSEQAAzRIFADMUBQBjgAOwAAkUAAFlbXM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/617ee73e3969.js b/pr/451/docs/search.index/617ee73e3969.js new file mode 100644 index 000000000..ab9f38a15 --- /dev/null +++ b/pr/451/docs/search.index/617ee73e3969.js @@ -0,0 +1 @@ +rn_("AYACsAARfgABsAARgAABNDYBCwA7MAAAAQAABgABAB4UBgAjgAKggAAAFEhlbyMCA6BgAAAT3bBAFLkAAWVucAEjDAAA8wACYXYB3gYAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/618145397e6f.js b/pr/451/docs/search.index/618145397e6f.js deleted file mode 100644 index bae3a90ad..000000000 --- a/pr/451/docs/search.index/618145397e6f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYAOzAAAAEAAAUAAQBDEwUAg4YEsFATCwABsHATcwABoNAAABRTYm5vdhOAArAQEtwAAXByAQABoDAAABHUdAGSEwAA8wIBc/KOEAAACAMTgAKgYAAAEH9ldQECAPb3DAAAAQA4AwEALQEBAAGAArAAEscAAYAOhTSgAWVzAQEA80sHAAAcDAEAY4ADsIAT5gABYm5084ADYW9yARQAOzAAAAEAAAUAAQCBFAUAAQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQQBsDATQQABY/NrEQAAAQABAAEFADswAAABAAAIAAMA4RECAOYSBAAOFAAA8wIDY2Vw87gMAAABAAEA84ACb3UBAAGwYBPyAAFlASwUAADzAgFz9hYJAAABAAEAEgg/ACEBAQgA86QSAAABAAEAQ4ADoBAAABN1oOAAABP9ZG9zAQIBoFAAABFqcgELEQAAAYACoEAAABCLoHAAABQ+YWUBAAA7MAAAAQAACwACAOkGBQDAEgUA84ACdHYjAAKwEAfiAAFucvYLCQAAVwAHBwEAtgABAPMAhzCRDDowAAABAAAAAAAIABAAAADABMEE6BDpENoS2xJjE2QTXhQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6186d9628089.js b/pr/451/docs/search.index/6186d9628089.js deleted file mode 100644 index 2b04d427b..000000000 --- a/pr/451/docs/search.index/6186d9628089.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9NAPAAABAO0AAQCDAISgQAAADOiwcBNUAAGwIAxmAAEQBQI7MAAAAQAACQADALIEAwB1BQIAsxECAAGAAqAgAAARCrAgFCEAAWR0AYQCsLAUcQABsAATwQABYXPTgISgsAAAFFhAKAQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/61fa9fe339d6.js b/pr/451/docs/search.index/61fa9fe339d6.js deleted file mode 100644 index 008588a98..000000000 --- a/pr/451/docs/search.index/61fa9fe339d6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQcA9oUTAAABAKwAWAAYAAEA8wEBaQFOCQAAAQAA9VEJAAABAAEAAQABAEcBAIDGfQsTF3n8oKAAABR7sGAT6gABoBAAAAklAAogig==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6243561514b9.js b/pr/451/docs/search.index/6243561514b9.js deleted file mode 100644 index b9d7e2f3f..000000000 --- a/pr/451/docs/search.index/6243561514b9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAcAEAAAADQTNRPQE9ETChQLFFoUWxTzBgFpAVMTAABDgAOwABSUAAGwUBMoAFpkZm4BAAGwQBMAAAFlOzAAAAEAAA0AAgDGBgwA9RAAACOAAqAwAAAMAGFlNwAAgMWwUBN8AAGwEBQjAAGwYBODAAEAApgI") \ No newline at end of file diff --git a/pr/451/docs/search.index/62d9bdfe377f.js b/pr/451/docs/search.index/62d9bdfe377f.js new file mode 100644 index 000000000..2a9aad45b --- /dev/null +++ b/pr/451/docs/search.index/62d9bdfe377f.js @@ -0,0 +1 @@ +rn_("AYMCsQAUvgABsNAUrgABa20BCwA7MAAAAQAABgABAB4UBgAjgAKggAAAFEhlbyMEA6BgAAAT3bBAFLkAAWVucAEjDAAAAQsA8wEUAAABAHUAdwAAAIWgYAAAEpWhAAAAFHkEgQM7MAAAAQAAOwAEALgFBQBPBwAAoAczAIURAABDgAOwgBGEASKgAAAAAhhka3Q=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6327cdc02f4b.js b/pr/451/docs/search.index/6327cdc02f4b.js new file mode 100644 index 000000000..118b4adf3 --- /dev/null +++ b/pr/451/docs/search.index/6327cdc02f4b.js @@ -0,0 +1 @@ +rn_("AQgA834UAAABADwAI4ACsAAQ3wABbW4BgAOgQAAAE12wIBQaAAGgIAAAD9NoaXkBCQGggAAAFLhmAZgTAAABAwGwABQxAFJz8q0QAAAtA0OAA7AgD88BK7BQErwAAWFvcgECAPXcEAAAAQABAF8CAQDzAANpdHU6MAAAAQAAAAAAEQAQAAAAfQcsCfMMORC2EOER7xIpEyoT3hPfE+UTDhQPFC8UMBSEFIUUAQYA9BITAAABAJkBAQATAgKwYBMUAAFmbfLiDAAAAQAjggKwQBNDAAFlbAGAAqAwAAAUYbCwE7QAAWFsAQYA8+EPAADhAAEAAQUA9AUUAAABAE4AAQDzAgFj8gcTAAABAAECAaAwAAAQd2EBVgwAAAGAArBQESsAAbAgD6sAAWNu84ACbnQBBQA7MAAAAQAADgACAOgMAAAKEA0AAQIAOzAAAAEAAAcAAQBTCQcA5wUAAAmgMAAADFWgsAAAFC+ggAAAEaqw0BQ+AAEwYWZpa21ydXY7MAAAAQAAagIFAAACAABtA+0AdgcAAAIL7QAIDowAAQEA8/kPAAABAAEAE4ACsMAUuQABdHUBAwDzwBIAAAEAAQABAQD1PxEAAAEAAQABAAEAAQEBsDAP6gDxdPZoBwAAAQCKAPoHqwD9AWOAA7CQFAcAAWFtdAEDAaBAAAAUZWXyiRQAAAEAE4ACsGAUvAABZXIBAwGgAAAAE+VzAfkQAAABAgA6MAAAAQAAAAAACgAQAAAAUQlSCSIQcRNyE6IToxNNFKYUvhS/FIMABKCAAAASmrBwEW4AAbAgEQ8A8GZucnYB8gcAAAGBAqAAAAAJLKAwAAAP03N0xwEAgMawABQ8AAGwkBKYAAGgEAAAB04AEogoAQIAOzAAAAEAAA0ABgBZEAUAaxEBANASAQABFAEAWBQAAHcUAAATggKw8BS8AAFmZwGAArCAFJAAAaBgAAAUt2RzE4ACsMATzAABaW8BCAD1QhQAAAEAAQABAAEA8wIBZvWaEAAAAQABAAEAAQAjgAOgIAAAD82woBSOAAFkcnT3+wcAj6AwAAAPw5fXDzswAAABAABSAR4AhwQBALQEAgB8BQIApgUCAMcFAQC8BgQAPwcAAFcHAADuBwEAAgkCADAJAQApDAMAVwwBAH8MAwDkDAEAnQ4EAfAPAQBvEAIAtxABAMoQAgD4EAEAixEAAL4RAwDoEQEAohIAAOISAAAaEwEAhBMBAKYTBwCdFAUA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6344cc548748.js b/pr/451/docs/search.index/6344cc548748.js new file mode 100644 index 000000000..ad3a2f768 --- /dev/null +++ b/pr/451/docs/search.index/6344cc548748.js @@ -0,0 +1 @@ +rn_("AYICsEASxwABsEASyQABNDYBCwDzyBMAAGoAAQABBgD0tRQAAAEACgABAPMCAXIBERQAAPMDAWU7MAAAAQAABQACAH8QAwAwEQEA84ADYWVp") \ No newline at end of file diff --git a/pr/451/docs/search.index/639489df947c.js b/pr/451/docs/search.index/639489df947c.js deleted file mode 100644 index 8eb1924bc..000000000 --- a/pr/451/docs/search.index/639489df947c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMAOzAAAAEAAAkABABPCQEAhRADAMcQAQDSEwEAAQIBsEAUYQABdDswAAABAAANAAYA4hIDAE4TAQC+EwEA1RMBAFYUAQB9FAEAE4ACsDATFgFKaXABiAKwABPJAAGwABPLAAE0NhOBAqAwAAARamRzAYACoCAAAAxlsGAT+wABbngBBQDzkxMAAAEAAQATgAKgMAAAE+5lefMAhBIAA/ImEAAA5QDzgAJydA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/639fc837a5d5.js b/pr/451/docs/search.index/639fc837a5d5.js deleted file mode 100644 index 0cc514e16..000000000 --- a/pr/451/docs/search.index/639fc837a5d5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsRAUfwABsBAMQQABZW8BBQA7MAAAAQAADQABAIERDQABAgDz7g8AAAEAAQABAgD06g8AAAEAAQABAAEDADswAAABAAAIAAEAnxAIAIcHAACHsKATqQABsNAUQQABoFAAABQMjBQROzAAAAEAAAYAAwBKBwAAzxADAJ4SAQDzgAJsdBOAA6AgAAAMu7CgFCUAAWFkbg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/642891ddd2e0.js b/pr/451/docs/search.index/642891ddd2e0.js new file mode 100644 index 000000000..7c2786d53 --- /dev/null +++ b/pr/451/docs/search.index/642891ddd2e0.js @@ -0,0 +1 @@ +rn_("AYACsIATCQABsKAThAABaXQBgAKw4BSGAAGwQBBTAAFscgEGAPTqEAAAAQABAAEARwUAAIeg0AAAFF6wUBBRAAGgAAAAE4aw8BQ4AAEFkQPy9A8AALAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/643b6936a58b.js b/pr/451/docs/search.index/643b6936a58b.js new file mode 100644 index 000000000..5e579385b --- /dev/null +++ b/pr/451/docs/search.index/643b6936a58b.js @@ -0,0 +1 @@ +rn_("AQEBsIATbQABdfJECQAAAQAjgAKwEAlCAAEzNgEGAPPxEAAAAQAVAROAArAAD7YAAW91AQIA81EHAADvAQEAAQMA880SAAABAAEAI4ACsAARxAABaGkBAAA7MAAAAQAAEwABAIQHEwAjgAKgMAAADN1hZfOAAnJ0E4ACsDATvAABYWwBAAGggAAAFLhm8voIAACeCgEHAPVmEwAAAQABAAEAAQAjgAKgUAAAEWNhb/MCAmZzOzAAAAEAAAgABABNBwAAfgcFAAQNAACbEQAAE4ACoEAAAAwNYW8BAwGwQBR8AAF08tsTAAABACOFAqBAAAAUC2JzIwADsEARAwABoFAAABFwYWJm9wcJAAABAEgAeAcBAGIAAQATgQKgUAAAEf9ndAGAAqBAAAAR7KAgAAAT6mFpAQIA9D0JAAABAAgIAQABAgDzUAwAAAEAAQATgAOwEAk7AAGwMBQcAAFpdXkBAgA7MAAAAQAACwACANQTBQCSFAUA8wgBbTswAAABAAAMAAMAbREAAOkSBQBOFAUAAYECoIAAABLWoHAAABQvYXPzgAJucrfbAQCNoUAAABSNoCAAAAk/sFAQAQABN5UPOzAAAAEAADUCBQD+AQAAJAIyAQgHAAB1BwAAAgr/AA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6441524172dd.js b/pr/451/docs/search.index/6441524172dd.js deleted file mode 100644 index c787160fe..000000000 --- a/pr/451/docs/search.index/6441524172dd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9JkPAAABAAEAAQDzAgFzARcRAAABAAKwgBRzAAGgYAAAFJtkc/LqCAAAOwOTgISwwBOvAAGgEAAAEcIAwUA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/644655b6a37b.js b/pr/451/docs/search.index/644655b6a37b.js new file mode 100644 index 000000000..99228ec90 --- /dev/null +++ b/pr/451/docs/search.index/644655b6a37b.js @@ -0,0 +1 @@ +rn_("AQIBsEATugABZfLCBgAAAQABBADzbxAAAAEAAQBDAAOwQBMcAAGxABQBAAFlZnA7MAAAAQAAJAAGABkCAADRBgwA3wYMAKIPBgAQEQAAXhMBAGOAA7AwDBEABWFlaQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6483b5e7deef.js b/pr/451/docs/search.index/6483b5e7deef.js new file mode 100644 index 000000000..d11d89b9c --- /dev/null +++ b/pr/451/docs/search.index/6483b5e7deef.js @@ -0,0 +1 @@ +rn_("AYACoFAAABFFoFAAABKVYXUBgwKxABS+AAGw0BSuAAFrbQELADswAAABAAAGAAEAHhQGACOAAqCAAAAUSGVvIwMDoGAAABPdsEAUuQABZW5wASMMAAAjgAKgQAAAENZtcAELAPMBFAAAAQB1APcAAACFoQAAABR5BIEDOzAAAAEAADsABAC4BQUATwcAAKAHMwCFEQAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/64c2c1e2630c.js b/pr/451/docs/search.index/64c2c1e2630c.js new file mode 100644 index 000000000..fd3851d0f --- /dev/null +++ b/pr/451/docs/search.index/64c2c1e2630c.js @@ -0,0 +1 @@ +rn_("AYACsAAT5gABsAAT6AABNDYBBgD0tRQAAAEACgABAPMCAnJ28/UPAAABABsE8wIBZTswAAABAAAFAAIAfxADADARAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/64ddaa20de73.js b/pr/451/docs/search.index/64ddaa20de73.js new file mode 100644 index 000000000..2e6af0109 --- /dev/null +++ b/pr/451/docs/search.index/64ddaa20de73.js @@ -0,0 +1 @@ +rn_("AQIA9LARAAABAAEAAQABCgD0whMAAAEAAQABAPOBAm10AQYA83IRAAABAAEAAYICsGATSAABsNAUaAABaXITgAKwEAzpAAFlaQEFAPQFFAAAAQBOAAEA8wQBY/IHEwAAAQABBQDz1xIAAAEAAQDzggJmdAEAAbAAFF8AAWT0+wsAAAEAAQABAPOBAmVpNwYAAImxMBSJAAGgYAAAEXGgUAAAEQWwkBMYAAGwQBEBAAG1EAcBOxQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/652e095d3b43.js b/pr/451/docs/search.index/652e095d3b43.js new file mode 100644 index 000000000..d8048c8ae --- /dev/null +++ b/pr/451/docs/search.index/652e095d3b43.js @@ -0,0 +1 @@ +rn_("AQ8A9CUUAAABAE8AAQABBAD0gxAAAAEAtwABAAECADowAAABAAAAAAAJABAAAADeEd8R+hL7EusT7BNAFEEUvBS9FPMAAW/ycwkAAAEAU4ADsQAUiwABaG9ww4CEoBAAABNdsIAToAABBQAG") \ No newline at end of file diff --git a/pr/451/docs/search.index/66753853a13a.js b/pr/451/docs/search.index/66753853a13a.js new file mode 100644 index 000000000..73b9c9ad1 --- /dev/null +++ b/pr/451/docs/search.index/66753853a13a.js @@ -0,0 +1 @@ +rn_("AYACoCAAAA/PoDAAABPaYXLzAAF0AX0HAAABBAGgQAAAFGVl8okUAAABAAEFADswAAABAAALAAUAWRAFANASAQABFAEAWBQAAHcUAADjAISgMAAAD82FAAI7MAAAAQAACwAFAKYFAgC8BgQAVwwBAIsRAADiEgAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6693e2a7afc5.js b/pr/451/docs/search.index/6693e2a7afc5.js deleted file mode 100644 index 72e8ca49d..000000000 --- a/pr/451/docs/search.index/6693e2a7afc5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9OgTAAABAFAAAQDzAwFj8usSAAABACOAArBwFKAAAWhvAYICsAASxwABsAARzwABZXNDAAOgoAAAFD6gIAAAE3VlaXTy2hIAAAEAAQIAOzAAAAEAAAYAAQBJEQYAAQIBoEAAABQfZfJNFAAAAQDzgAJpbwEIAPTfEwAAAQCLAAUAEwEDsJAUSAABsFATJAFnZ2xwOzAAAAEAABIACQBPEAIA0hEBABITAQA0EwEAaxMBANATAQAKFAEAGRQBAFoUAQABgAKwQBKcARagIAAAEQ1haQEDAPO7EgAAAQABACOBArAwE/AAAWNpAQAA9A0TAAABAFkBAQDzAwFzOzAAAAEAAA8AAwDNEQEAiRMHAIEUBQABAwD1yxEAAAEAJAEBAGIB84ACcnYBAAA7MAAAAQAABQACAJoSAQDyEgMA8wIBZDswAAABAAAJAAMAxxEDAJUSAwDJEgEAI4ACoJAAABQ9aW8TgAKw8BSQAAFsbwEFAPRZFAAADAAiABEAI4ACoGAAABNpZ3MjgAKwkBQ7AAFibPfHAACNsGAUcQABsHAT4gABsGATIgABsFATDwABtckXOjAAAAEAAAAAACAAEAAAAKsF9AgiCUwMTQzMDM0MzgyBDoIOgw6EDoYOhw7kD+UP5g/nD+gPtRC2EPEQFBFzEXQRdRF2EcURGRMxEzITUxMNFA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/66dcaa05b5bb.js b/pr/451/docs/search.index/66dcaa05b5bb.js deleted file mode 100644 index 341be0fc8..000000000 --- a/pr/451/docs/search.index/66dcaa05b5bb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABoCAAAAzYbQF6BQAAAQAA9PISAAABAAEAAQDzBAFk9JUSAAABAAEAAQAjhISwoBQ7AAGwQBLwAAGwcBPiAAEVgAAzgAOw0BQlAAFhZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/66fc142f300b.js b/pr/451/docs/search.index/66fc142f300b.js new file mode 100644 index 000000000..0cd879c0f --- /dev/null +++ b/pr/451/docs/search.index/66fc142f300b.js @@ -0,0 +1 @@ +rn_("AQACoCAAABHdoDAAABKoYmgByg8AAAETAbAAFIQAAXM7MAAAAQAABQABAJ0UBQABBwDzqRIAAAEAAQDzgANkcnQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/670f0a9091b9.js b/pr/451/docs/search.index/670f0a9091b9.js deleted file mode 100644 index bdbd50565..000000000 --- a/pr/451/docs/search.index/670f0a9091b9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9C8HAAABAAEAAQABhQKwcBP7AAGwMBNOAAFlcxMAA7BAD7wAAaBgAAARcWJlczswAAABAAASAAQAoQUBALsGCgA3EAEAjxADAAECAPOrBQAAOQoBAAEDAPW0DwAAnwABAAQAAQABCgGgQAAAFEpl8mwUAAABACMABKAwAAARAaCQAAATdrBgEp4AATBjaGn15xEAAJcBAQABAAEA84ACZHLnBAAAh7AQCOoDO7AgC/IBCqAwAAAP3BJBODswAAABAAASAAYAiwACAJIFAAC/BQMAjgcFANEHAwD8CwAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6718ecfbb765.js b/pr/451/docs/search.index/6718ecfbb765.js deleted file mode 100644 index b6acec73e..000000000 --- a/pr/451/docs/search.index/6718ecfbb765.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBsIATzgABdAEHCQAAAYECoBAAABMqoHAAABNyYWPzAQFy8o8RAAAgAPOAAmVpAQAA9AEMAAABAAEAAQDzgAJkcvMAAWX0jgAAAE8MRgcBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/67a942abe161.js b/pr/451/docs/search.index/67a942abe161.js deleted file mode 100644 index 9ee4cc0e5..000000000 --- a/pr/451/docs/search.index/67a942abe161.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQkA82MUAAABADsAAYACoEAAABNAsCAT/gABaGkBAQDz4Q8AAAEAAQATgAKwwBSdAAF0dQGBArCAFHMAAaBgAAAUm2RzNwMAg4agQAAAFEawYBQWAFACkA4BEgGwABRnAAFzOzAAAAEAAAUAAQCBFAUAAQDGoFAAABLGoAAAABB/sCARMQABoFAAABHdoQAAABR8oEAAABQgAAMCCzswAAABAAAVAAcApAUAACAMAADVDAAA8Q8NAFURAADcEgEAGxQBAAEBAbCwFG4AAWw7MAAAAQAADAADABkHCADzDAEALhABAAEBAaAwAAARDWjz9AgAAC4AowgBAgD0vBEAAAEAAQABABOBArAgEcAAAWFz84ICbm/3AQCAh6AgAAAFo6BAAAAMBxUBBw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/67bd888b1183.js b/pr/451/docs/search.index/67bd888b1183.js deleted file mode 100644 index 9af061301..000000000 --- a/pr/451/docs/search.index/67bd888b1183.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQ0A9cMTAAABAAEAegABAAEHAPOrEwAAbAABACOAAqEQAAAT/W5yAQcA9JsRAAABAAEAAQAzAASgIAAACRuw4BPyAAExZWlyOjAAAAEAAAAAAAkAEAAAAFcEWARCB/0IWgxbDFwMXQw9ET4RAQIA9O8QAAABAJEBAQABAwD0WhEAAAEAewEBAPMAAmJ1AQEQAAABAwD07RAAAAEAkQEBAPOEAmRzAQIA8/8IAAABAFgA84ACaXQBGADzqhQAAAEAAQAjgQKwwBPBAAFjbQENAbBAFEsAAWX1DxQAAAEAAQABAAEAE4ICsAARBQABa3MBBAD0lBAAAAEAAQABAAGBAqAgAAARALBAE1YAAWVp84ICZXQBBAGwYBKpAAFy9A8MAAABAO8DAQDzgANhZW8BCQD21BIAAAEA/wCJADUAAQATgAKwYBCuAAFkbgEGADswAAABAAAFAAIA2BADAG8TAQABAwGgcAAAExFn9E8HAAABAAYCAQABCQDzYxQAAAEAOwABgQKwgBRzAAGgYAAAFJtkcxcBAIiFsNAUnQABoEAAABRGsGAUFgBQApAKAQQA9K8TAAABAEsAAQBTgAOwkBSWAAFlaG8BAAGgQAAAEWdlAa4PAAAjgQKwUBESAAFrdHfsAwCOoZAAABSLsLATIAABoKAAABLTsVAUlAAB/cEPOzAAAAEAACEACACAAAAAhgADAGgFAQArBwcAJgkHADAMBwA5EAEAfhIAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/688c1e1d0f3f.js b/pr/451/docs/search.index/688c1e1d0f3f.js deleted file mode 100644 index d8ff702fb..000000000 --- a/pr/451/docs/search.index/688c1e1d0f3f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYkCsCAUVgABoDAAABRgaXATAAOgIAAAExGggAAAEyVlaXX1/wsAADkAAQABAAEAAYYCsJAUfQABoAAAABKdYmQBAgD0CAwAAAEAiAMBAAEAAPQqCQAAAQABAAEAAQAAOzAAAAEAAAYAAgBxEwAA2RMFACOBArBAFFEAAWVz84ADZXh5AQAA9CYJAAABAAEAAQAjgAKwIBMFAAFyeEcBAADGsMATnQABoBAAABFWsEAM0wABoDAAABDdAESAC/RmBQAAAQByBwEA1wAAAIWgMAAAEFKgMAAAE1wQARM6MAAAAQAAAAAACgAQAAAA7gjvCPAIIAkhCQ0MbRB0EC0RLhHhEg==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6902fbabc407.js b/pr/451/docs/search.index/6902fbabc407.js deleted file mode 100644 index b853d5ea1..000000000 --- a/pr/451/docs/search.index/6902fbabc407.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAAAUAAQB1FAUA8w0BbTswAAABAAAFAAEAMxQFACMDhLBAEqcAAaEAAAAUe7AAE2oAijAIAjswAAABAAAYAAYAGBEHAMsSAQBJEwQAJxQEAC4UAwCcFAAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6957822f864f.js b/pr/451/docs/search.index/6957822f864f.js new file mode 100644 index 000000000..9ae377e51 --- /dev/null +++ b/pr/451/docs/search.index/6957822f864f.js @@ -0,0 +1 @@ +rn_("AQkA834UAAABADwAAYACoEAAABNdsCAUGgABaGkBAQDz+Q8AAAEAAQATgAKwwBS5AAF0dQGBArCAFJAAAaBgAAAUt2RzNwMAgIagQAAAFGGwYBQxAFICkA4BAAGwUBENAAFj9F0HAAABAAEACALDgISgEAAADHegUAAAEqIMQAQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/69b1257472c5.js b/pr/451/docs/search.index/69b1257472c5.js new file mode 100644 index 000000000..86ddd5429 --- /dev/null +++ b/pr/451/docs/search.index/69b1257472c5.js @@ -0,0 +1 @@ +rn_("AQYA9I4RAAABAAEAAQABAgA7MAAAAQAACgAEAI8QBQDSEgEAMxMBAFkUAADzgAJmbgEBADswAAABAAAIAAIAog8GAF4TAQDzgAJkbgEBAPMuBwAAAQABAAGGAqAAAAATV6AAAAATWGRy8wEBdgGDEwAAQwADsIAS5wABsLAUiwABa2xyOzAAAAEAAAwAAwAlBwgABg0BAEgQAQABAgD19AcAAAEAAQABAAEA8wCEFEEAAYAAAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/69efc07ee8fe.js b/pr/451/docs/search.index/69efc07ee8fe.js deleted file mode 100644 index d3902e756..000000000 --- a/pr/451/docs/search.index/69efc07ee8fe.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHASvgABoLAAABP9ZW8jAQOwcBHlAAGAE2MAEPptbnM7MAAAAQAAGgAGAF4HAQDkCAUA9QwBADMRCQBxEQAAQxMFAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6b2a45acfbf1.js b/pr/451/docs/search.index/6b2a45acfbf1.js new file mode 100644 index 000000000..368fbfe11 --- /dev/null +++ b/pr/451/docs/search.index/6b2a45acfbf1.js @@ -0,0 +1 @@ +rn_("AQIAOzAAAAEAAAUAAQCSFAUA8w0BbTswAAABAAAFAAEAThQFAEMAhLAAFF8AAbBAEsMAAbAAE4cAiTgAAjswAAABAAAcAAcA+wsDADMRBwDnEgEAZhMEAEIUBABJFAMAuBQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6b3565993718.js b/pr/451/docs/search.index/6b3565993718.js deleted file mode 100644 index 6ebeb8c90..000000000 --- a/pr/451/docs/search.index/6b3565993718.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9PYSAAABAJkBAQATAgKwYBL4AAFmbfLPDAAAAQAjggKwQBMmAAFlbAEFAPToEwAAAQBQAAEA8wMBY/LrEgAAAQABgQKgAAAACR+gMAAAD7tzdBOAAqAQAAAHQm5404CEsHAUoAABkEAB") \ No newline at end of file diff --git a/pr/451/docs/search.index/6b7ac8f4866d.js b/pr/451/docs/search.index/6b7ac8f4866d.js new file mode 100644 index 000000000..df5f8cbd8 --- /dev/null +++ b/pr/451/docs/search.index/6b7ac8f4866d.js @@ -0,0 +1 @@ +rn_("AQEBsFAP1gABY/SjBAAAAQABAMcAAQABoIAAABS4ZgGYEwAA8wYBc/IEDQAAlwQzAAOgYAAAFJhjaXPy2xEAAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6b804baa315c.js b/pr/451/docs/search.index/6b804baa315c.js deleted file mode 100644 index 5a9186a25..000000000 --- a/pr/451/docs/search.index/6b804baa315c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUBsEASpwABZTswAAABAAAFAAIAyxIBAC4UAwABggKgkAAAFBugkAAAFBwyMwEAhKAgAAASg6AgAAAI67CAFJQAAbAwDfIAAQCRATswAAABAAAMAAYAlQQAAM0EAwBbCQAAmQ8DAO4TAAAAFAEA8wABcwEXEQAAAQUA9HcTAAABAAEAAQABAAD1aAwAAAEAAQABAAEA84ACZ2tzgISwsBPSAAERSAABBQGgQAAAEd5l8/IMAAAMBAEAE4ACoDAAAAwhZXIBgAOwUBQUAAGgEAAAC+mgcAAAE2ZnbnJHAQAAhaBgAAAT76BQAAAUR6CgAAAT4RUBBDswAAABAAARAAQA/QEBAJ8QCACbEQMApBICAAGFArBwE/sAAbAwE04AAWVzEwADsEAPvAABoGAAABFxYmVzOzAAAAEAABIABAChBQEAuwYKADcQAQCPEAMAAQMA9FMQAAABAAQAAQABCgGgQAAAFEpl8mwUAAABABOAArBgEp4AAWNp84ACZHLzAAJlbzswAAABAAAJAAIAjgcFANEHAwABAgGwMBE9AAFuOzAAAAEAAAkAAQDfCwkAAQAA9JQQAAABAAEAAQABAQA7MAAAAQAA7QACALwFAAD1B+wAYwADsBATCwABZWlzOjAAAAEAAAAAAA4AEAAAAP4IOQk6CTsJPAkIDAkMkQ+SD7sP0g/AEOUQcxN0E+OAhLAgB0wAARFABAGBhLAwC/UAAaBAAAAMvqCAAAARRLBADL8AAQAAHQEFAPXDEwAAAQABAHoAAQATgAKgAAAABu9hZwEDAPR8EgAAAQBtAQEA84ACaW8BAQA7MAAAAQAA7QACAGoHAADyCuwAAQgBsEAUSwABZfUPFAAAAQABAAEAAQDzgQJndAEBAaBwAAAR5Gk7MAAAAQAA/wACAGkHAADzCf4AAQUA87AQAAABAAEA84ACYW8TgAKgQAAADCBydQGAAqAAAAAHTqAQAAAI/WR4EwACsCAM6QABZXLyAhAAAAEAE4ACsLAUlgABcngBCgD0fhMAAAEAAQABABOAArAQEPkCOmFlAQQA9I8QAAABAAEAAQABBAA6MAAAAQAAAAAABwAQAAAAHhMfEy0TLhOzE7QTxhPHEwEDAPO4EgAAAQCsAJcBAICFsEAS4AA7sEAQ6wABgUgBx78DAI+ggAAAErqwYAzZAAGwQAxKAAGxMBRpAAE/qR87MAAAAQAASwAiAHkAAAB+AAAAtgQBAMIEAAB4BQEANAcBAEQHAABIBwEAMgkAAEYJBwBdCQEA9AsAAAsMAQASDAEAJgwHAEoMAQBmDAEAtQwBAM4PAQA8EAEAvBABAMkQAQDgEAAAAhECABIRAQAkEQQASBEAAJURAwDZEQEAlBIAAPYSAQD6EgEAKRMAAJAUAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6b91b3b9e417.js b/pr/451/docs/search.index/6b91b3b9e417.js deleted file mode 100644 index 5bd2f49f7..000000000 --- a/pr/451/docs/search.index/6b91b3b9e417.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHARwwABsCAP0gDuaHkBBQD08wwAAAEAOgMBAAEAAPRODAAAAQABAG4AAQIAOjAAAAEAAAAAAAkAEAAAAMcPyA+7EHcR1BLVEtQTXRSSFJMUAQMA8w4JAAABAAEA1wMAAIagQAAAEd4YQAc6MAAAAQAAAAAAFwAQAAAAXAddB+cHGwlPCVAJ8gywD7EP1Q9+EIUQhhCHEIgQxxDIEP4Q/xB5EtIT0xP+E/8T") \ No newline at end of file diff --git a/pr/451/docs/search.index/6b95550a6642.js b/pr/451/docs/search.index/6b95550a6642.js new file mode 100644 index 000000000..b2c094780 --- /dev/null +++ b/pr/451/docs/search.index/6b95550a6642.js @@ -0,0 +1 @@ +rn_("AQIA9QATAAABAPIAfgABABOAArAwEzIBSWlwAQYA87ATAAABAAEA8wICYnMBQBAAAPMBAWYBZgcAAAECAPRbEwAAAQCYAAEAM4ADsEATJwABaXV5") \ No newline at end of file diff --git a/pr/451/docs/search.index/6be7775146ac.js b/pr/451/docs/search.index/6be7775146ac.js new file mode 100644 index 000000000..e5ca5bc69 --- /dev/null +++ b/pr/451/docs/search.index/6be7775146ac.js @@ -0,0 +1 @@ +rn_("AQMBsEARYQABZQGVDgAAAQUAOzAAAAEAAAYAAQBfEAYAAQMAOzAAAAEAAAUAAQDXDAUA8wACcHM6MAAAAQAAAAAADQAQAAAAqQWqBcEGEglZDGYMBhEHEbYStxIOEw8TEBMREzMAA7BQD9AA02RwcjswAAABAAAPAAYAogQAAGgFAwC9DwAA4hEDALESAwDlEgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6c223b5d689a.js b/pr/451/docs/search.index/6c223b5d689a.js new file mode 100644 index 000000000..36f449874 --- /dev/null +++ b/pr/451/docs/search.index/6c223b5d689a.js @@ -0,0 +1 @@ +rn_("AYACsEATcQABoMAAABQvZW8BAgD01xEAAAEAAQABABMAA7AgEdsAAbCAFEAAAWFzdAHeDAAAAQQA8/4PAAABAAEAs4CEsEAThAABQQAG") \ No newline at end of file diff --git a/pr/451/docs/search.index/6c2c4dc5a026.js b/pr/451/docs/search.index/6c2c4dc5a026.js new file mode 100644 index 000000000..dd009328f --- /dev/null +++ b/pr/451/docs/search.index/6c2c4dc5a026.js @@ -0,0 +1 @@ +rn_("AQgA9PwTAAABAIsABQATAAOwkBRjAAGwUBNBAWZnbHA7MAAAAQAAEgAJAGkQAgDtEQEALhMBAFETAQCIEwEA7RMBACUUAQA0FAEAdRQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6cc5a584e86e.js b/pr/451/docs/search.index/6cc5a584e86e.js new file mode 100644 index 000000000..6d27d9819 --- /dev/null +++ b/pr/451/docs/search.index/6cc5a584e86e.js @@ -0,0 +1 @@ +rn_("AQgBoIAAABS4ZgGYEwAAE4ACoOAAABR5YnABAwGwABQxAFJz9ugPAAABAMQALAABAAADAYACsEASvAABoBAAABEYcHIBgQOgAAAAEYmwAA/PASugEAAAD8dlc3XzgANhb3IBAgD13BAAAAEAAQBfAgEAAQQAOzAAAAEAAAkAAQBOEQkAAQAA9L4RAAABAAEAAQABgAKgkAAAE/OwUBKkAAFid/OAAmR0o4CEsFAUDgABsJATMwABQCADVwMAAIagcAAAE+qgQAAAFF4wQQw7MAAAAQAAQgAhAMQEAQCIBQoAfQcAACYJAQAsCQAALQwBAFAMAgDmDAEA8wwAAJoOAAC4DwQAzw8AAPIPAQA5EAAAthAAAMQQAwDoEAEA+hAAACARAQCNEQAA4REAAOoRAQDvEQAA7xIAACkTAQDeEwEA5RMAAA4UAQAvFAEARxQAAIQUAQC1FAEAwBQBAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6d172713b950.js b/pr/451/docs/search.index/6d172713b950.js new file mode 100644 index 000000000..08cf09140 --- /dev/null +++ b/pr/451/docs/search.index/6d172713b950.js @@ -0,0 +1 @@ +rn_("AQIA9CUTAAABAJIAAQABAwD0WxMAAAEAmAABAPMCAmJ1OjAAAAEAAAAAABMAEAAAAEoHSwdMB9gP2Q+FEIYQphCnED0RPhHwEfERIxMkEzATMRNCE7YTtxM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6d3e4d35925e.js b/pr/451/docs/search.index/6d3e4d35925e.js new file mode 100644 index 000000000..9e8346fef --- /dev/null +++ b/pr/451/docs/search.index/6d3e4d35925e.js @@ -0,0 +1 @@ +rn_("AQYA9OsTAAABAFQAAQAjgAKhEAAAFKdjchOAAqAgAAAJC2h5AQEBoGAAABSYc/LbEQAAAQAjgAKwIAlxAAFldQGAArAgECMAAaCQAAAUR2FlAQcBsEAUZgABZfUqFAAAAQABAAEAAQABAAD0KRMAAAEAWgEBAPMEAXM7MAAAAQAADQACAKYTBwCdFAUARwAABIaggAAAE4axABSsAAGwQBLjAAGwgBS8AAGwUBMrAAEVABb0lg4AAAEAAQABAIMAhLBAFLIAAbAwExoAAbAwE7oAAcAAAzowAAABAAAAAAAJABAAAAC6BBEREhG0EbURwhHDEQkUChR6FAEDAbBAFHwAAXTy2xMAAAEAI4QCoEAAABQLYnMTgAKwwBSwAAF1eQGAAqAAAAAJK7AgEQMAAWdwAQEA9NcRAAABAAEAAQDzgAJkbLfoAACMoNAAABOSoFAAABFwoJAAABOusFATCQABoDAAAAxrt5gHOzAAAAEAAGgAFQADAgEACQIBAGgEAwDFBQAAwgYBAGEHAQB0BwAABwkBABQJAAAeCQAAUAkAAIQMQwDeDAAA7g8BABgQAADIEAEA5hABACsRAQCrEQQAvBIBADcTAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6d5bf0cc001d.js b/pr/451/docs/search.index/6d5bf0cc001d.js new file mode 100644 index 000000000..2c9607147 --- /dev/null +++ b/pr/451/docs/search.index/6d5bf0cc001d.js @@ -0,0 +1 @@ +rn_("AQUA9MsSAAABAKkAAQABCAD0ihMAAAEAhwABADMBA7BAE1kAAWFiZTowAAABAAAAAAAMABAAAAAxBzIHMwf0DPUMxw86EIgQiRCKEIsQoxJ4EwEAADswAAABAAAiAA4AxAQBAIgFCgAmCQEALQwBAOYMAQCaDgAAuA8EAM8PAADyDwEA6BABAPoQAACNEQAA6hEBAO8RAAABgQKwABF+AAGwABGAAAE0NiOAArCAFI4AAW9wAQABsEATVQABZfOJEQAAWgEBALcBAICFoGAAABEmEAEL") \ No newline at end of file diff --git a/pr/451/docs/search.index/6d6ee039d782.js b/pr/451/docs/search.index/6d6ee039d782.js deleted file mode 100644 index 3b7fc27db..000000000 --- a/pr/451/docs/search.index/6d6ee039d782.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQsBoEAAABQfZfJNFAAAAQABBQD06BEAAAEAAQABAAGDArAAENQAAbBQE2EAAWR3E4ACoKAAABPVZXYBAgD07xAAAAEAkQEBAAEAAPTaDwAAAQDyAAcBI4ICsFATOAABZXMBAgD0CRMAAAEAkQABAAEDAPQ+EwAAAQCYAAEA84UCYnUBAgD0WhEAAAEAewEBAPOAAmls8wADYnR1OjAAAAEAAAAAAA8AEAAAAGYH7wfwB/EH8gcBEMYQkhMWFGMUZBRmFHMUdBSbFJ8UAQwA8/gTAAABAAEAAYACsAAJDAABoCAAAAzwZG4BBgD0BxMAAAEAkQABAPMDAWL07RAAAAEAkQEBAJcGAACHsEAP2AABoHAAABKLsJATNgABOAkDOjAAAAEAAAAAACQAEAAAALcFuAW5BboFKAcpByEM5QzmDOcM6wzsDO0M0A/RDyIQIxAnECgQRRBGEEcQSBBJEEoQSxCOEJMQvhC/EHITlhO9E1kUZRSHFJgUAQMA9BwTAAABAFEBAQDzgAJjbXcBAIDFsYAUqAABQIEECA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6d8f629c95e4.js b/pr/451/docs/search.index/6d8f629c95e4.js deleted file mode 100644 index c704aee91..000000000 --- a/pr/451/docs/search.index/6d8f629c95e4.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsAARYwABsAARZQABNDYBCwA7MAAAAQAABgABAAIUBgAjgAKggAAAFC1lbyMCA6BgAAATwLBAFJ0AAWVucAERDAAA8wACYXYB0wYAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/6dc536e1c4d1.js b/pr/451/docs/search.index/6dc536e1c4d1.js new file mode 100644 index 000000000..6707d1158 --- /dev/null +++ b/pr/451/docs/search.index/6dc536e1c4d1.js @@ -0,0 +1 @@ +rn_("AQQA9n4UAAABABEAAQAmAAQAAQMA88ASAAABAAEAI4ECsSAUvAABY2sTgAKgMAAAFDpuclMAA6BAAAAMH2VvcvQoEQAAgAHAAQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e058b4cb634.js b/pr/451/docs/search.index/6e058b4cb634.js new file mode 100644 index 000000000..69fd54940 --- /dev/null +++ b/pr/451/docs/search.index/6e058b4cb634.js @@ -0,0 +1 @@ +rn_("AQIAOjAAAAEAAAAAAAgAEAAAABoMGwypD6oP6g/bEAARkBOREwEAAPQ3CQAAAQABAAEAAQUBsDATGgABaPMJFAAAAQBwABODArAwEM0AAWNwAQMA9OgPAAABAPAAAQABgQKgAAAAEYmgEAAAD8dldfOAAmFyAQAA9L4RAAABAAEAAQABgAKgkAAAE/OwUBKkAAFid/OAAmR0E4ACsJATMwABbnLzAAJldPTEEAAAAQABAAEAAQEA9XoMAAABAAEAAQABABOAArCwE+8AAWFvIwACsEAM/AABZXQ7MAAAAQAACQAEAL8EAQCCBQEAXAwBALARAwABAgA7MAAAAQAACQACAOMHBwAUEwEAAYACsJATfgABoKAAABPyZXYTgAKxgBTEAAFjbQGCArBAFBwAAaBQAAAR7GJmE4ACoEAAABRHZHMjgAKg0AAAFJhjbvOAhBEBQAEFAbBAE1kAAWUBoxIAAEOCA6EAAAAUpqDQAAAUeGttcxOAArBgEOQAAWFyAQUAOzAAAAEAAI0AAgD3EAAACBKMAAGCA7AgExYAAbAgEH0AAbBAEXkAAWtsc1OAA7AgFHwAAWt0dQGJhKCAAAAUt6BAAAAUYaBgAAAUg6CQAAAUuwKAChOCA6CgAAAT8bDQFFoAAWNpdQEBAPS6EQAAAQABAAEAQ4ADsXAUwgABoNAAABQwY29zI4ACsGAQ4gABaW8BAAD0JAwAAAEAUwABACOBAqDgAAAUTWV5AQ0AOjAAAAEAAAAAAAcAEAAAAFETUhPtE+4TJRQmFHUUdhQBgQKwQBNxAAGgwAAAFC9lbwECADowAAABAAAAAAAIABAAAADfD+APkhHwEvES8RN4FK4UrxTzAQFvOzAAAAEAAAkABABcCQEAnxADAOIQAQDvEwEA84ADbW5yAQAAOzAAAAEAAAYAAgCOEwAA9hMFAEOAA7BAFGwAAaAgAAAM+2VycyMAArAwE9IAAWNy8rwTAAABAAGBArCAExwAAaEAAAAUd2NmAQsA9HoTAAABAAEAAQABgAKgAAAAEJWgAAAAEJY0NvMCAXYBAg0AAPMAAmRsARcJAADX/jkAkrCgFBoAAbBwEd4AAbCAEvoAAaCwAAATNZ3dfzswAAABAAAXAAsAqwEBALsEAwB/BQIA+wYBAAUHAgA1DAAA9w8AAF8RAADOEQIAqRICAF4UAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e3afbd9ac87.js b/pr/451/docs/search.index/6e3afbd9ac87.js new file mode 100644 index 000000000..526dd7165 --- /dev/null +++ b/pr/451/docs/search.index/6e3afbd9ac87.js @@ -0,0 +1 @@ +rn_("AYACsHAR3gABsCAP6gDxaHkBBQD0Bg0AAAEAQQMBAAEAAPRgDAAAAQABAG8AAQIAOjAAAAEAAAAAAAkAEAAAAN8P4A/WEJIR8BLxEvETeBSuFK8UAQMA8xsJAAABAAEA1wMAAIagQAAAEfkYQAc6MAAAAQAAAAAAFwAQAAAAaAdpB/MHKAlcCV0JBQ3ID8kP7Q+YEJ8QoBChEKIQ4hDjEBkRGhGVEu8T8BMaFBsU") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e41df2f5abc.js b/pr/451/docs/search.index/6e41df2f5abc.js deleted file mode 100644 index 7081ad294..000000000 --- a/pr/451/docs/search.index/6e41df2f5abc.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoAAAABMwc/S2DwAA8wMBAAQAAQECsGAUIwABsEAUYQABZHQ7MAAAAQAADQAGAOISAwBOEwEAvhMBANUTAQBWFAEAfRQBAAEBA7BwEeUAAbCAEr4AAYATYwAQ+m1uczswAAABAAALAAIA5AgFAEMTBQCjgISwsBOsAAGxcBSgAAEIQIIBAIWgcAAAEoOwMBSUAAGgcAAAEPSxABQjAAGwABMkAWcGIQHybgUAAAEAZwQAAIdgj3BMNRagEAAABaOwMBMWAUqgQAAAERcRwQw7MAAAAQAABgACAHETAADZEwUA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e9cbfa350f1.js b/pr/451/docs/search.index/6e9cbfa350f1.js deleted file mode 100644 index f27ca878c..000000000 --- a/pr/451/docs/search.index/6e9cbfa350f1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQQBsDATQQABY/NrEQAAAQABAAEFADswAAABAAAIAAMA4RECAOYSBAAOFAAA8wADY2Vw87gMAAABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/6e9d4bfea4fb.js b/pr/451/docs/search.index/6e9d4bfea4fb.js new file mode 100644 index 000000000..3099aae81 --- /dev/null +++ b/pr/451/docs/search.index/6e9d4bfea4fb.js @@ -0,0 +1 @@ +rn_("AQIAOjAAAAEAAAAAAA4AEAAAAA0NDg0VEfkR+hH7EbUSvhK/EsMSxBIcEx0TVRNWEwEJAPN7EQAAAQABAAEKAPPNEgAAAQABAAEGAPU/EQAAAQABAAEAAQDzgAJkbnMAhLEgFD4AAQARA/InEwAAAQABDQD2ohMAAAEAqgBZABgAAQABgAKwoBO6AAGgEAAADDZjdQEBAPTMEwAAAQBKAAEA8wABZfJxCQAAAQD3AACAxbAwELcAAQACqAg=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6f920086d797.js b/pr/451/docs/search.index/6f920086d797.js deleted file mode 100644 index 4faccd03b..000000000 --- a/pr/451/docs/search.index/6f920086d797.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9SkQAAABAAEAXQIBAAEEAaAAAAAQxnP16wwAAAEAAQA6AwEAAQIA9ZARAAABAAEAAQABAAGAArBgEvoAAaAgAAAQ5W95AQABoMAAABQgafRSEwAACAAcABsAAQYA9IwUAAABAAEAAQABAgD04xAAAAEAmgABAAECAaBAAAATKXI7MAAAAQAABwACAOEQAQB4EQUA84ACYW9nAwAAhrCAEygAWrCgE58AAQwwBjswAAABAAAIAAMACgwAAKsRAwCQEgMA84ICZWlHAQAAhbBwE68AAaCAAAAUnKBAAAAP37BABPTtCAAA3QYBALADAYECoCAAABDfsFASoAABYW8BBAGgAAAAE8hzAd4QAABjBAOwUBFQAAFjZXP29QgAAAEAAQAsAAEAYgkBAAA7MAAAAQAAJAAIADYHBwApEAIAgRENALARAgCIEgEADxMBAGETAQD4EwIAAQUA8wITAAABAAEAAQEAOzAAAAEAAAUAAgDuCwMA2xEBAFMAA7AwFDsAAWVwcvTMDwAAAQDsAAEAAQADsEAUAAABoUAAABRwoFAAABHRYmNmAf0GAADjgISxQBSZAAECMBABAYSgsAAAE72wQBCdAAGwwBPsAAGgYAAAEXcUIAT25AYAAFEJAQA5AS0BFgEBAwA7MAAAAQAABQABAMASBQABAAGgoAAAFCxy9NYPAAABAPQAAQDzgQJncwEHAPVJEwAAAQABAAEAAQABgAKgQAAAEUiwUBEvAAFjafOAAmFvAQABoCAAABFocvMiDAAAewNtAUOAhKAAAAAUCaAwAAAUCYAFaAAdFQggIQEDArBAEwUAAaAwAAATXGV38nQQAAC6ABODAqBwAAATlmdvAQIA9eQSAAABAPEAgAABABOAArAwExYBSmlwAQYA85MTAAABAAEA8wUCYnMBJhAAAAECAPQEEAAAAQABAAEAI4ACoGAAABKEYmP3XwEAzbBgEMQAAaCQAAAShcBOchU6MAAAAQAAAAAAGAAQAAAAFgIXAnsEfAR9BAcHQQdyB3MHdAd1B3YHdwfpC2IM8QwhEDMQNBAAEYARfxSAFJQUlRQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/6fb57124e92e.js b/pr/451/docs/search.index/6fb57124e92e.js new file mode 100644 index 000000000..8a5afa4d5 --- /dev/null +++ b/pr/451/docs/search.index/6fb57124e92e.js @@ -0,0 +1 @@ +rn_("AQMBoAAAABNNc/TODwAA+AMBAAQAAQECsGAUPgABsEAUfAABZHQ7MAAAAQAADQAGAP4SAwBrEwEA2xMBAPITAQBxFAEAmRQBAAEBA7BwEgAAAbCAEtoAAYATgAAQ+G1uczswAAABAAALAAIA8QgFAGATBQCjgISwsBPJAAGxcBS8AAEIQIIBAIWgcAAAEp+wMBSwAAGgcAAAEQ+xABQ+AAGwABNBAWYGIQHyeAUAAAEAAQEAOzAAAAEAAAYAAQDCFAYA8wYBZfRvFAAAAQA0AAEAEwEDsEAUmwABsGAThwCJYXJ183sQAAB5AgEAdwQAAIegEAAABa2wMBMyAUmgQAAAETIRwQw7MAAAAQAABgACAI4TAAD2EwUA") \ No newline at end of file diff --git a/pr/451/docs/search.index/7032cf56e781.js b/pr/451/docs/search.index/7032cf56e781.js deleted file mode 100644 index 974a63f38..000000000 --- a/pr/451/docs/search.index/7032cf56e781.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBgBNjABD6czswAAABAAAFAAEAQxMFACOCAqCQAAAUR2lzBwEAhMWwUBMLAAGgoAAAE66wcBNzAAGg0AAAFFOAARgI") \ No newline at end of file diff --git a/pr/451/docs/search.index/7106b5f1eec5.js b/pr/451/docs/search.index/7106b5f1eec5.js deleted file mode 100644 index 5aaa47e91..000000000 --- a/pr/451/docs/search.index/7106b5f1eec5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYMCsQAUogABsNAUkgABa20BCwDz5BMAAAEAdwAzAoSgYAAAEnmhAAAAFF4EgAM7MAAAAQAANAACAEMHAACUBzMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/710e3a47aed6.js b/pr/451/docs/search.index/710e3a47aed6.js deleted file mode 100644 index 407f2b4e6..000000000 --- a/pr/451/docs/search.index/710e3a47aed6.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9F0TAAABAAEAAQDzBgFz8uITAAABAAEBAbBQEtwAAWX3BgkAAE8DAQAJBAEA2wMBAAEDAPSjEQAAAQABAAEAAQMA9J8RAAABAAEAAQDzgwJsc/OAAmN0U4ADoBAAAAy3YW9y") \ No newline at end of file diff --git a/pr/451/docs/search.index/7163b34c9425.js b/pr/451/docs/search.index/7163b34c9425.js deleted file mode 100644 index 6ca564156..000000000 --- a/pr/451/docs/search.index/7163b34c9425.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQgA9M4TAAABAFYAAQABAgGgYAAAFHxz8sARAAABAAGAArAgEAkAAaCQAAAULGFlAQcBsEAUSwABZfUPFAAAAQABAAEAAQABAwGwQBRhAAF08r4TAAABACOEAqBAAAAT7mJzE4ACsMAUlAABdXkBAQD0vBEAAAEAAQABABOAArAwEOgAAWRsN3QAAIuwQBOdAAGgUAAAEVWgkAAAE5GwUBLtAAGgMAAADFmzmAc7MAAAAQAAYgARAAUCAQBfBAMAuwUAALcGAQBVBwEA+ggBAAcJAAARCQAAQwkAAHIMQgDLDAAA1g8BAK4QAQDLEAEAEBEBAJARBACgEgEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/71b075553573.js b/pr/451/docs/search.index/71b075553573.js new file mode 100644 index 000000000..2e43866dd --- /dev/null +++ b/pr/451/docs/search.index/71b075553573.js @@ -0,0 +1 @@ +rn_("AQIBsEAUfAABdDswAAABAAANAAYA/hIDAGsTAQDbEwEA8hMBAHEUAQCZFAEAE4ACsDATMgFJaXABBQDzsBMAAAEAAQATgAKgMAAAFAtleVMBA7BwFBcAAWJlcwFAEAAAE4ADsDAUjgABsCAG+wABZWxz") \ No newline at end of file diff --git a/pr/451/docs/search.index/732444e4dd16.js b/pr/451/docs/search.index/732444e4dd16.js deleted file mode 100644 index eee2c4cad..000000000 --- a/pr/451/docs/search.index/732444e4dd16.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsRAUfwABsBAMQQABZW8BBQA7MAAAAQAADQABAIERDQABAgDz7g8AAAEAAQABAgD06g8AAAEAAQABAAEDADswAAABAAAIAAEAnxAIAIcHAACHsKATqQABsNAUQQABoFAAABQMjBQROzAAAAEAAAYAAwBKBwAAzxADAJ4SAQDzgQJsdPMAAWk7MAAAAQAAjQADAJMEAADcEAAA7RGLAAEFAPNjFAAAAQA7AAEAAaBAAAAQ4GXz+AgAAAEAkgkTgAKgMAAAD8ZkcvMAAWXz4QYAAAEAAQDzgAJodAEBADswAAABAACMAAIA3BAAAO0RiwATgAKgUAAAEAhpdAEIAPTfEwAAAQCLAAUAEwCEsPAUZQAzsJAUSAABsFATJAFnQIkAOzAAAAEAABIACQBPEAIA0hEBABITAQA0EwEAaxMBANATAQAKFAEAGRQBAFoUAQDXAQAAhrCAEZkAAaBAAAAJGwgwJjowAAABAAAAAAANABAAAACQAJEAVAdcCfIL/AzvEPAQgRKCEgkTChObE5wT") \ No newline at end of file diff --git a/pr/451/docs/search.index/736cd7e92e7f.js b/pr/451/docs/search.index/736cd7e92e7f.js deleted file mode 100644 index e777d9f89..000000000 --- a/pr/451/docs/search.index/736cd7e92e7f.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYkCsCAUVgABoDAAABRgaXATAAOgIAAAExGggAAAEyVlaXX1/wsAADkAAQABAAEAAYYCsJAUfQABoAAAABKdYmQBgAKwMBMFAAGgMAAAEN1ydtcAAACFoDAAABBSoDAAABNcEAETOjAAAAEAAAAAAAkAEAAAAO4I7wjwCCAJIQltEHQQLREuEeES") \ No newline at end of file diff --git a/pr/451/docs/search.index/738edcf2da0b.js b/pr/451/docs/search.index/738edcf2da0b.js deleted file mode 100644 index 5342e1141..000000000 --- a/pr/451/docs/search.index/738edcf2da0b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9OgTAAABAFAAAQDzAwFj8usSAAABACOAArBwFKAAAWhvAYICsAASxwABsAARzwABZXNDAAOgoAAAFD6gIAAAE3VlaXT12hIAAAEAiAABAPoAAYACsHASvgABoLAAABP9ZW8BAgA7MAAAAQAABgABAEkRBgABAgGgQAAAFB9l8k0UAAABAPOAAmlvAQgA9N8TAAABAIsABQATAQOwkBRIAAGwUBMkAWdnbHA7MAAAAQAAEgAJAE8QAgDSEQEAEhMBADQTAQBrEwEA0BMBAAoUAQAZFAEAWhQBAAGAArBAEpwBFqAgAAARDWFpAQMA87sSAAABAAEAI4ECsDAT8AABY2kBAAD0DRMAAAEAWQEBAPMDAXM7MAAAAQAADwADAM0RAQCJEwcAgRQFAAEDAPXLEQAAAQAkAQEAYgHzgAJydgEAADswAAABAAAFAAIAmhIBAPISAwDzAgFkOzAAAAEAAAkAAwDHEQMAlRIDAMkSAQAjgAKgkAAAFD1pbxOAArDwFJAAAWxvAQUA9FkUAAAMACIAEQAjgAKgYAAAE2lncyOAArCQFDsAAWJs9xcDAI+wcBHlAAGwYBRxAAGwcBPiAAGwYBMiAAGwUBMPAAG1+Rc7MAAAAQAAPAAYAKsFAABeBwEA5AgFAPQIAAAiCQAA/gsAAEwMAQDMDAIA9QwBAIEOAwCGDgEA5A8EALUQAQDxEAAAFBEAADMRCQBxEQAAcxEDAMURAAAZEwAAMRMBAEMTBQBTEwAADRQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/74220e663fcd.js b/pr/451/docs/search.index/74220e663fcd.js deleted file mode 100644 index 7ee76d2dc..000000000 --- a/pr/451/docs/search.index/74220e663fcd.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMBoEAAABFnZQGuDwAAAQIA8x0MAAABAAEAIwACsCAMCwABa3L25QYAAAEABArtAAEANALzAAJyePLiCAAAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/74b289fd1fbb.js b/pr/451/docs/search.index/74b289fd1fbb.js deleted file mode 100644 index 91e5b372b..000000000 --- a/pr/451/docs/search.index/74b289fd1fbb.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYQCsCARaQEhoJAAABQObXABgAKgMAAAEPWgUAAAE7FjcpcAAACFsCATfAABsMAUQQABoGAAABHFEEAOOzAAAAEAABIABgDEBAgAwxEBAN4SAQDOEwEAJRQBAKAUAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/758d161430bc.js b/pr/451/docs/search.index/758d161430bc.js new file mode 100644 index 000000000..4407f7996 --- /dev/null +++ b/pr/451/docs/search.index/758d161430bc.js @@ -0,0 +1 @@ +rn_("AQAAOzAAAAEAAAYAAgCOEwAA9hMFACOIArBAFGwAAWVzAQQBsDATXgABY/OGEQAAAQABAAEFADswAAABAAAIAAMA/BECAAITBAApFAAA8wEDY2Vw88sMAAABAAEAAQIA9hgJAABYABMHAQC3AAEA84ACbnI=") \ No newline at end of file diff --git a/pr/451/docs/search.index/768ee4759e5c.js b/pr/451/docs/search.index/768ee4759e5c.js new file mode 100644 index 000000000..87e359742 --- /dev/null +++ b/pr/451/docs/search.index/768ee4759e5c.js @@ -0,0 +1 @@ +rn_("AQIA9eYRAAABACUBAQBhAQEBAPciEQAAAQABANgCAQCLAAUAAQIA9LIQAAABAAEAAQATgAKg0AAAFHNjc/OAAmVvI4ACsAAHUgABbHTzAAJhcvKrDwAAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/776540b8ab1a.js b/pr/451/docs/search.index/776540b8ab1a.js new file mode 100644 index 000000000..e6031017c --- /dev/null +++ b/pr/451/docs/search.index/776540b8ab1a.js @@ -0,0 +1 @@ +rn_("AYQCsCARhAEioJAAABQpbXABgAKgMAAAERCgUAAAE85jchcBAACGsCATmQABoGAAABM3sMAUXAABoGAAABHgEEAPOzAAAAEAABIABgDNBAgA3hEBAPoSAQDrEwEAQBQBALwUAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/77798d930b56.js b/pr/451/docs/search.index/77798d930b56.js deleted file mode 100644 index d90c9033c..000000000 --- a/pr/451/docs/search.index/77798d930b56.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAA9F0TAAABAAEAAQATAgKwgBMxAAFzdvRfCQAAAQCCCgEA8wABYwFTDAAAAQICoLAAABQ9oLAAABQ+Y3PztRAAAAEAXgABgQKwUBHXAAGgwAAAFAxoafOCAmVp84ACZW8=") \ No newline at end of file diff --git a/pr/451/docs/search.index/77822d716af7.js b/pr/451/docs/search.index/77822d716af7.js deleted file mode 100644 index c3005db0a..000000000 --- a/pr/451/docs/search.index/77822d716af7.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9O8QAAABAJEBAQABAAD02g8AAAEA8gAHASOCArBQEzgAAWVzAQIA9AkTAAABAJEAAQABAwD0PhMAAAEAmAABAPOFAmJ1AQIA9FoRAAABAHsBAQDzgAJpbPMAA2J0dTowAAABAAAAAAAPABAAAABmB+8H8AfxB/IHARDGEJITFhRjFGQUZhRzFHQUmxSfFAEMAPP4EwAAAQABAAGAArAACQwAAaAgAAAM8GRuAQYA9AcTAAABAJEAAQDzAwFi9O0QAAABAJEBAQCXBgAAh7BAD9gAAaBwAAASi7CQEzYAATgJAzowAAABAAAAAAAkABAAAAC3BbgFuQW6BSgHKQchDOUM5gznDOsM7AztDNAP0Q8iECMQJxAoEEUQRhBHEEgQSRBKEEsQjhCTEL4QvxByE5YTvRNZFGUUhxSYFA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/779759506437.js b/pr/451/docs/search.index/779759506437.js new file mode 100644 index 000000000..ed3241b32 --- /dev/null +++ b/pr/451/docs/search.index/779759506437.js @@ -0,0 +1 @@ +rn_("AQYA9L4TAAABAAEAAQABDwD0qBQAAAEAAQABAAEAAbDwFLIAAWs7MAAAAQAACAADABkUAAAeFAYASBQAAAECAPR1EAAAAQCzAAEA9wAAAIWxABSbAAGJMADy2hIAAAEAE4ACsBAMUwABZW8BBQA7MAAAAQAADQABAJwRDQABAgDzBxAAAAEAAQABAgD0AxAAAAEAAQABAAEDADswAAABAAAIAAEAuRAIAAGAAqAAAAAMJrAwFGYAAXFyAYACoEAAABEQsMAUXAABZXMBAQA7MAAAAQAAEwABACUQEwAjgAKwkBPGAAFpbXc2AACKoFAAABQnsHAUBwABoCAAAAzwnFUROzAAAAEAAAsABABWBwAA6hADALoSAQAqFAQAAQIBsDARdwABcAGhEgAAs4CEoCAAABHhAigE") \ No newline at end of file diff --git a/pr/451/docs/search.index/77c18862fa4c.js b/pr/451/docs/search.index/77c18862fa4c.js new file mode 100644 index 000000000..094475f25 --- /dev/null +++ b/pr/451/docs/search.index/77c18862fa4c.js @@ -0,0 +1 @@ +rn_("AQABsIAT6wABdAEUCQAAAQkA880SAAABAAEA84ACY2cBgwKgkAAAFDagkAAAFDcyMwGAAqBQAAAUYqCAAAATg2Nl8wACc3TyQAcAAAEAAQABoLAAABPOYvNYEwAALgEBACOAAqAAAAATV2RyI4ACoFAAABQoY2XzgAJpeQEGAPU/EQAAAQABAAEAAQABAQGwQBRmAAFl9SoUAAABAAEAAQABAPcAAICFoBAAAAwPKWAA") \ No newline at end of file diff --git a/pr/451/docs/search.index/77cff9841c95.js b/pr/451/docs/search.index/77cff9841c95.js new file mode 100644 index 000000000..eede255ab --- /dev/null +++ b/pr/451/docs/search.index/77cff9841c95.js @@ -0,0 +1 @@ +rn_("AYEBVvUXN/iEayMAAqCAAAAUuGZo8voIAACeCgEFAPMeEwAAAQABAAEBADswAAABAAAFAAIA/wsDAPYRAQBTAAOwMBRWAAFlcHL05A8AAAEA7wABAAEAA7BAFBwAAaFAAAAUjaBQAAAR7GJjZgEIBwAA84ACbW4BAYSgsAAAE9qwQBC3AAGwwBQJAAGgYAAAEZIUIAT27wYAAGAJAQA6AS4BFwEBBQA7MAAAAQAABQABANwSBQABBwD1ZhMAAAEAAQABAAEAI4ACoFAAABFjYW/zAIUgSQI7MAAAAQAACAAEAE0HAAB+BwUABA0AAJsRAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/77e679070a1d.js b/pr/451/docs/search.index/77e679070a1d.js deleted file mode 100644 index 5e3d2714a..000000000 --- a/pr/451/docs/search.index/77e679070a1d.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9KMRAAABAAEAAQDzAAFl9KoQAAABAAEAAQABAwD0nxEAAAEAAQABAMcAAACFsKATAAABsCAMEgABsIAS3gABCAwG8/oGAAABAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/780398a1ffe5.js b/pr/451/docs/search.index/780398a1ffe5.js deleted file mode 100644 index 47571b9ec..000000000 --- a/pr/451/docs/search.index/780398a1ffe5.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AYACsHARXAABsCAUQQABYWQBBQD0LRMAAAEAmAABAPMAAWMBlBIAAEOAA7AwE4MAAbCgFCMAAWFzeQEGAPT7EAAAAQC+AAEAk4CEoAAAABFDsSAUiAABCSAB84ACZWk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7811abcc9a5f.js b/pr/451/docs/search.index/7811abcc9a5f.js new file mode 100644 index 000000000..5b987d9de --- /dev/null +++ b/pr/451/docs/search.index/7811abcc9a5f.js @@ -0,0 +1 @@ +rn_("AQAAOzAAAAEAAAYAAgCOEwAA9hMFACOIArBAFGwAAWVzAQQBsDATXgABY/OGEQAAAQABAAEFADswAAABAAAIAAMA/BECAAITBAApFAAA8wMDY2Vw88sMAAABAAEAAQABsGAUDgABZQFHFAAA8wIBc/YjCQAAAQABACAIPwAiAQECAaBQAAARhXIBJhEAAPOAA2ZtcA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7844f0d84b02.js b/pr/451/docs/search.index/7844f0d84b02.js deleted file mode 100644 index f113fc4aa..000000000 --- a/pr/451/docs/search.index/7844f0d84b02.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOzAAAAEAAAYAAgCbEQMApBICAAEKAbBAFEsAAWX1DxQAAAEAAQABAAEA8wACaXP0EgwAAAEAUwABACOBAqDgAAAUMmV5") \ No newline at end of file diff --git a/pr/451/docs/search.index/78878d5dabd9.js b/pr/451/docs/search.index/78878d5dabd9.js deleted file mode 100644 index 23d1e65f2..000000000 --- a/pr/451/docs/search.index/78878d5dabd9.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOjAAAAEAAAAAAAkAEAAAAMcPyA+7EHcR1BLVEtQTXRSSFJMUAYACsOATzgABsDAT5gABZHDDgISxQBSkAAGgkAAAE5ICgAk=") \ No newline at end of file diff --git a/pr/451/docs/search.index/78cce7048e1e.js b/pr/451/docs/search.index/78cce7048e1e.js deleted file mode 100644 index af034525c..000000000 --- a/pr/451/docs/search.index/78cce7048e1e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEA9pcFAAABABcNAQCoAAEAAQAAOzAAAAEAAAcAAwANAgEAVwwBAAQQAwABgwKwcBN8AAGwgBO1AAFjcwEFAPQuFAAAAQABAAEAAQUA9B4TAAABAJQAAQDzCAJjdfU8EAAAAQDFAAEAAQABAgD0oxEAAAEAAQABABMAAqAwAAARbmV09KoQAAABAAEAAQABAwD0nxEAAAEAAQABAAGAAqAAAAAQe6AAAAAQfDQ28wMBdgHvDAAAlwEAAIawoBMAAAGwIAwSAAGwgBLeAAEJDAY6MAAAAQAAAAAACQAQAAAA+gb7BvwGIwzfD0QRjRKOEo8SQxQBAgD0CRMAAAEAkQABAAEDAPQ+EwAAAQCYAAEA8wACYnU6MAAAAQAAAAAAFQAQAAAAPgc/B0AHwA/BD2sQbBCMEI0QIhEjEdUR1hEHEwgTFBMVEyUTUBNRE5kTmhMBAQD0kwUAAAEASgcBALcAAICFsQAUJQABsAAHSAABDAhEAQgAOzAAAAEAAAUAAQDAEgUA1w0AAIiwQBESAAGgIAAADPm5AAs7MAAAAQAADwAFAHsAAQDyBgcAVwcBAFsQAQAOEQEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/78f59f62da93.js b/pr/451/docs/search.index/78f59f62da93.js deleted file mode 100644 index b2a999e54..000000000 --- a/pr/451/docs/search.index/78f59f62da93.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQACoCAAABN1sDARzwABaXTy2hIAAAEAAQMAOzAAAAEAAAYAAQBJEQYAAQgA9N8TAAABAIsABQATAQOwkBRIAAGwUBMkAWdnbHA7MAAAAQAAEgAJAE8QAgDSEQEAEhMBADQTAQBrEwEA0BMBAAoUAQAZFAEAWhQBAAGAArAwEcsAAbAwEc0AAXJ2AQQBsAASmgABZDswAAABAAAFAAIAxxEDAMkSAQC3BQABh7BQEpwBFrBgEyIAAZQJAzowAAABAAAAAAALABAAAADMDM0MzgyGDocO8RBzEXQRdRF2ERkTUxMjgAKgEAAADMtubwEDAPNMEAAAAQABACOAAqAAAAAL/WRyYwCEoFAAABFwoDAAABCKFAEQ8owHAAABAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7906e11d0aff.js b/pr/451/docs/search.index/7906e11d0aff.js new file mode 100644 index 000000000..305c28d7f --- /dev/null +++ b/pr/451/docs/search.index/7906e11d0aff.js @@ -0,0 +1 @@ +rn_("AQUAOzAAAAEAAAcAAwDLEQIAfhMBABQUAgABBwD2ohMAAAEAqgBZABgAAQABBADzzhEAAAEAAQAjAAKgYAAAEv1hcvPvEAAAAQDJAQGAAqEQAAAUo7CAFJkAAWl1dwgAAIixUBTAAAGgAAAAEM+wcBPeAAGhMAAAFLQKQRvyWwkAAEoHAYECoFAAABEUoKAAABNQa3ABAwA7MAAAAQAABgECAOQMAQCdDgQB84ACbHTzgAJhZQ==") \ No newline at end of file diff --git a/pr/451/docs/search.index/799a0178fd53.js b/pr/451/docs/search.index/799a0178fd53.js deleted file mode 100644 index 56104a10f..000000000 --- a/pr/451/docs/search.index/799a0178fd53.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9G4QAAABAAEAAQABEwD1qBQAAAEAAQABAAEAE4ACoMAAABMvYW8BAgKgsAAAFD2gsAAAFD5jc/O1EAAAAQBeAAGBArBQEdcAAaDAAAAUDGhp84ACZWkjgAKwsBNvAAFpdAEFAbCAFEQAAXY7MAAAAQAABwACALYRAwClEwMAI4ACsHAR2wABYmR3AgAAhqCgAAATkqCQAAATWwXgAvUKBwAAAQABAN0IkgQ=") \ No newline at end of file diff --git a/pr/451/docs/search.index/79de9416e93c.js b/pr/451/docs/search.index/79de9416e93c.js deleted file mode 100644 index 57aa75990..000000000 --- a/pr/451/docs/search.index/79de9416e93c.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEBoEAAABRKZfJsFAAAAQABgAOgMAAAEoWw4BQlAAGwkBRuAAFhY22HAQCAhrAQERIAAbAQETEAAaCgAAAUWKDwAAAUezFJAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7bb203f6c904.js b/pr/451/docs/search.index/7bb203f6c904.js deleted file mode 100644 index fe919ea72..000000000 --- a/pr/451/docs/search.index/7bb203f6c904.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQEAOzAAAAEAAAYAAQBJEQYAAQAA9xIJAAABAFAHAQByAAEAFQEBggKgIAAAEQygMAAAEXJpdPOAA2FncwEBAaBAAAAUH2XyTRQAAAEAI4ACsFAT0gABZnPzgAJpbw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7be830c7f373.js b/pr/451/docs/search.index/7be830c7f373.js deleted file mode 100644 index 891d9d43f..000000000 --- a/pr/451/docs/search.index/7be830c7f373.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQYA9KETAAABAAEAAQABDwD0jBQAAAEAAQABAPOHAmhtAQAA9PISAAABAAEAAQDzBAFk9JUSAAABAAEAAQAjh4SwoBQ7AAGwQBLwAAGwcBPiAAEVgAABAAGgQAAAEH1lOzAAAAEAAAcAAgBgBwUA7QcBABOAArBQEDMAAWRyAQkA9cMTAAABAAEAegABALcCAICGsCASoAABcDLPVueBEUkB") \ No newline at end of file diff --git a/pr/451/docs/search.index/7cc498cd5f4e.js b/pr/451/docs/search.index/7cc498cd5f4e.js deleted file mode 100644 index 6d73ecf1e..000000000 --- a/pr/451/docs/search.index/7cc498cd5f4e.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOzAAAAEAABEABQCkBQAAIAwAANUMAADxDw0AVREAAAGEArDQFJQAAaCQAAATkWJoAYQCsAARYwABsAARZQABNDaXBAAAh6AgAAARwqAwAAARKaAwAAASjLBwD8oAActACDswAAABAAAQARQAtQEAAJoFAADrCAAA8QgAAA4JAgD7CwAAPAwBAGMMAQDYDAAABQ3sALIPAADHDwEAuxAAAHcRAADUEgEAtxMFANQTAABdFAAAdRQFAJIUAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7cdfc41c47d8.js b/pr/451/docs/search.index/7cdfc41c47d8.js new file mode 100644 index 000000000..660ef6675 --- /dev/null +++ b/pr/451/docs/search.index/7cdfc41c47d8.js @@ -0,0 +1 @@ +rn_("AQUA9MsSAAABAKkAAQABCAD0ihMAAAEAhwABADMBA7BAE1kAAWFiZfX0DAAAAQBFA2kC1QABAAA7MAAAAQAABgACAC0MAQC4DwQA84ACc3UjgQKwgBOEAAFydAGAArBwEwkAAaAQAAAM1G56AQYA9IUQAAABALcAAQDzAANiaXT1egUAAAEA5g5YAAEA") \ No newline at end of file diff --git a/pr/451/docs/search.index/7ce496ab7c6d.js b/pr/451/docs/search.index/7ce496ab7c6d.js new file mode 100644 index 000000000..9a7f53b0b --- /dev/null +++ b/pr/451/docs/search.index/7ce496ab7c6d.js @@ -0,0 +1 @@ +rn_("AYECsDAQPgABsBATvAABc3QBAwDzzRIAAAEAAQAjgAKwABHEAAFoaQEAADswAAABAAATAAEAhAcTACOAAqAwAAAM3WFl84ACcnTzAAJhbDswAAABAAAoAAMAmgATAK4BAAAlEBMA") \ No newline at end of file diff --git a/pr/451/docs/search.index/7d0b131779fc.js b/pr/451/docs/search.index/7d0b131779fc.js deleted file mode 100644 index 0368f3cd0..000000000 --- a/pr/451/docs/search.index/7d0b131779fc.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIA9JURAAABAAEAAQABCgD0pRMAAAEAAQABAPOBAm10AQYA81cRAAABAAEAAYICsGATKwABsNAUTQABaXITgAKwEAzWAAFlaQEFAPToEwAAAQBQAAEA8wQBY/LrEgAAAQABBQDzuxIAAAEAAQDzggJmdAEAAbAAFEQAAWT06gsAAAEAAQABAPOBAmVpNwYAAImxMBRsAAGgYAAAEVagUAAAEOqwkBL8AAGwQBDmAAG1EAcBIBQAAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7d5fb1732d0e.js b/pr/451/docs/search.index/7d5fb1732d0e.js new file mode 100644 index 000000000..86c9196cf --- /dev/null +++ b/pr/451/docs/search.index/7d5fb1732d0e.js @@ -0,0 +1 @@ +rn_("AQcA9qITAAABAKoAWQAYAAEA8wEBaQFbCQAAAQAAOzAAAAEAAAUAAQBeCQUApwAAgMVk3aog3nOwYBQHAAGgEAAACTIAAiCK") \ No newline at end of file diff --git a/pr/451/docs/search.index/7da5ade5b83b.js b/pr/451/docs/search.index/7da5ade5b83b.js deleted file mode 100644 index 2ed751913..000000000 --- a/pr/451/docs/search.index/7da5ade5b83b.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQMA9G0MAAABAAEAAQABAAGwUBQjAAFyAboEAABTAYSwQBKiAAGwQBRhAAGYAAQ6MAAAAQAAAAAAKAAQAAAAhASFBIYEjgX/CAAJJQlYCcEMsw8kECUQMhBiEMEQwhDDEAwRgxLiEuMS5BLlEhETIBMhE04TTxO+E78TwxPEE8UT1RPWEz8UQBRWFFcUfRR+FA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7dca73af94f0.js b/pr/451/docs/search.index/7dca73af94f0.js deleted file mode 100644 index 82406dc3c..000000000 --- a/pr/451/docs/search.index/7dca73af94f0.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQADoKAAABQ+oCAAABN1sDARzwABZWl08toSAAABAAEDADswAAABAAAGAAEASREGAAEIAPTfEwAAAQCLAAUAEwEDsJAUSAABsFATJAFnZ2xwOzAAAAEAABIACQBPEAIA0hEBABITAQA0EwEAaxMBANATAQAKFAEAGRQBAFoUAQABgAKwMBHLAAGwMBHNAAFydgECAbAAEpoAAWQ7MAAAAQAABQACAMcRAwDJEgEAI4ECoJAAABQ9aW8BBwD0WRQAAAwAIgARAHcLAAGIsFASnAEWsGATIgABlQkDOjAAAAEAAAAAAA4AEAAAAMwMzQzODIYOhw61ELYQ8RAUEXMRdBF1EXYRGRNTEyOAAqAQAAAMy25v") \ No newline at end of file diff --git a/pr/451/docs/search.index/7dfde7047aaf.js b/pr/451/docs/search.index/7dfde7047aaf.js deleted file mode 100644 index 0b38ce78e..000000000 --- a/pr/451/docs/search.index/7dfde7047aaf.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQABsKAStgABZTswAAABAAAJAAIA1wcHAPgSAQABAAGgIAAADNhtAXoFAAABgAKwoBK0AAGwQA++AAFsbwEAAPTyEgAAAQABAAEA8wQBZPSVEgAAAQABAAEAIwKEsKAUOwABsEAS8AABsHAT4gABFYAAAQ0UAAABAAGwgBQhAAFw8h0UAAABAEMAA6AgAAARRaBAAAASmWRlc/PgDwAASwMBAPOBAmVp1wIAAIawIBJ6AAGw0BQlAAEXAQL0mgQAAAEAAQDGAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7e508d623850.js b/pr/451/docs/search.index/7e508d623850.js deleted file mode 100644 index e21b3c17e..000000000 --- a/pr/451/docs/search.index/7e508d623850.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQAAOzAAAAEAAAYAAgBxEwAA2RMFACOIArBAFFEAAWVzAQQBsDATQQABY/NrEQAAAQABAAEFADswAAABAAAIAAMA4RECAOYSBAAOFAAA8wMDY2Vw87gMAAABAAEAAQABsGAT8gABZQEsFAAA8wIBc/YWCQAAAQABABIIPwAhAQECAaBQAAARanIBCxEAAPOAA2ZtcA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7e6d6e61a59f.js b/pr/451/docs/search.index/7e6d6e61a59f.js new file mode 100644 index 000000000..33e120b45 --- /dev/null +++ b/pr/451/docs/search.index/7e6d6e61a59f.js @@ -0,0 +1 @@ +rn_("AQgA9UIUAAABAAEAAQABAPMAAWY7MAAAAQAABQACAPIBAACaEAQAAQIAOzAAAAEAAAsAAgDUEwUAkhQFAPMGAW07MAAAAQAADQAEANAMAABtEQAA6RIFAE4UBQBjgAOwAAkhAAFlbXM=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7e7ee16d46d1.js b/pr/451/docs/search.index/7e7ee16d46d1.js deleted file mode 100644 index fcf15af48..000000000 --- a/pr/451/docs/search.index/7e7ee16d46d1.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIBoQAAABRlZPJUBwAACAIBBgGgMAAAEdR0AZITAAABAwGwUBLcAAFl9QYJAABPAwEACQQBAAEBAaEAAAAUWWTyqAUAADwCFwcAAIewoBLaAAGg4AAAFCywEAWpAAEegArzWQQAAAEAAQA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/7f9dc1f44330.js b/pr/451/docs/search.index/7f9dc1f44330.js deleted file mode 100644 index dd31b70ed..000000000 --- a/pr/451/docs/search.index/7f9dc1f44330.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQUA9K8SAAABAKgAAQABCAD0bRMAAAEAiAABADMAA7BAEzwAAWFiZTowAAABAAAAAAAMABAAAAAlByYHJwfhDOIMrw8gEG4QbxBwEHEQhxJbEw==") \ No newline at end of file diff --git a/pr/451/docs/search.index/7f9fb0e71342.js b/pr/451/docs/search.index/7f9fb0e71342.js deleted file mode 100644 index 3c8b4d1a1..000000000 --- a/pr/451/docs/search.index/7f9fb0e71342.js +++ /dev/null @@ -1 +0,0 @@ -rn_("AQIAOjAAAAEAAAAAAAgAEAAAAAgMCQyRD5IP0g/AEOUQcxN0EwEAAPQqCQAAAQABAAEAAQUBsDAS/gABaPPsEwAAAQByABODAqAwAAAQs2NwAQMA9NAPAAABAO0AAQABgQKgAAAAEW6gEAAAD69ldfOAAmFyAQAA9KMRAAABAAEAAQABgAKgkAAAE9awUBKIAAFid/OAAmR0E4ACsJATFwABbnLzAAJldPSqEAAAAQABAAEAAQEA9WgMAAABAAEAAQABABOAArCwE9IAAWFvIwACsEAM6QABZXQ7MAAAAQAACQAEALYEAQB4BQEASgwBAJURAwABAgA7MAAAAQAACQACANcHBwD4EgEAAYACsJATYQABoKAAABPVZXYTgAKxgBSoAAFjbQGCArBAFAAAAaBQAAAR0WJmE4ACoEAAABQsZHMjgAKg0AAAFHxjbvOAhBEBQAEFAbBAEzwAAWUBhxIAAEOCA6EAAAAUiqDQAAAUXWttcxOAArBgEMkAAWFyAQUAOzAAAAEAAIwAAgDcEAAA7RGLAAGCA7AgEvoAAbAgEGMAAbBAEV4AAWtsc1OAA7AgFGEAAWt0dQGJhKCAAAAUm6BAAAAURqBgAAAUZqCQAAAUnwKAChOCA6CgAAAT1LDQFD8AAWNpdQEBAPSfEQAAAQABAAEAQ4ADsXAUpgABoNAAABQVY29zI4ACsGAQxwABaW8BAAD0EgwAAAEAUwABACOBAqDgAAAUMmV5AQ0AOjAAAAEAAAAAAAcAEAAAADQTNRPQE9ETChQLFFoUWxQBgQKwQBNUAAGgwAAAFBRlbwECADowAAABAAAAAAAIABAAAADHD8gPdxHUEtUS1BNdFJIUkxTzAQFvOzAAAAEAAAkABABPCQEAhRADAMcQAQDSEwEA84ADbW5yAQAAOzAAAAEAAAYAAgBxEwAA2RMFAEOAA7BAFFEAAaAgAAAM6GVycyMAArAwE7UAAWNy8p8TAAABAAGBArCAEwAAAaEAAAAUXGNmAQsA9F0TAAABAAEAAQABgAKgAAAAEHugAAAAEHw0NvMCAXYB7wwAAPMAAmRsAQoJAADX/jkAkrCgE/4AAbBwEcMAAbCAEt4AAaCwAAATGZ3dfzswAAABAAAXAAsAqAEBALIEAwB1BQIA8AYBAPoGAgAjDAAA3w8AAEQRAACzEQIAjRICAEMUAAA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/desc/5c696247fc77.js b/pr/451/docs/search.index/desc/5c696247fc77.js new file mode 100644 index 000000000..e34ac4369 --- /dev/null +++ b/pr/451/docs/search.index/desc/5c696247fc77.js @@ -0,0 +1 @@ +rd_("AmData flows in both directions0CdThe CE codepoint, signalling that congestion was \xe2\x80\xa600AjContains the success valueBeThe PathId of this path.BmReturns the PathId of this path.AoGet the identity of this stream00CdThe path which can now be used for application data.AlThe path that was abandoned.B`Which path had its state droppedAmPath which has changed statusCfPath over which the observed address was reported, \xe2\x80\xa643210AlWhich stream is now readableAlWhich stream is now writableAnWhich stream has been finishedAmWhich stream has been stoppedAnType of the Long header packetClWhether a stream communicates data in both directions or \xe2\x80\xa60AhContains the error valueAoThe largest representable valueAlThe maximum path ID allowed.10ClConnects to a QUIC perf server and maintains a specified \xe2\x80\xa6BmData flows only from the stream\xe2\x80\x99s initiator0AnWhich directions data flows in0CaDirectionality for which streams have been openedCdDirectionality for which streams are newly availableCjExplicit congestion notification bits to set on the packet0CeThe Explicit Congestion Notification bits for the \xe2\x80\xa61CdReturns the most recently observed external address.B`The current best RTT estimation.BfThe number of I/O operations executed.0BcLength of QUIC packet being decodedAlLength of the packet payload0BmThe number of bytes the associated buffer hasBbSet a custom TokenLog0CaMinimum RTT registered so far for this estimator.ClConstruct with an approximate maximum memory usage and a \xe2\x80\xa6CjConstruct an endpoint with arbitrary configuration and \xe2\x80\xa6DiCreates a new QlogConfig that writes a qlog trace to the \xe2\x80\xa6CiCreates a new qlog factory that writes files into the \xe2\x80\xa6D`Create a default config with a particular reset_keyCmCreate a default config with a particular handshake token \xe2\x80\xa6CkCreate a default config with a particular cryptographic \xe2\x80\xa6AmConstruct cid from byte arrayoConstruct emptyAeCreate a new StreamIdBeCreates a new FourTuple.DjConstruct a state using the given config and current time \xe2\x80\xa60;9AeCreate a new endpointDcCreate a new instance of FixedLengthConnectionIdParserBdCreate a generator with a random key;:98DgBegin decoding a QUIC packet from bytes, returning any \xe2\x80\xa68BkConstruct an error with a code and a reasonCgInitialize Random CID generator with a fixed CID length98766BfQUIC transport protocol implementationCkGet SystemTime::now() or the mocked equivalentAhLook up the current time01ChCurrent best estimate of this connection\xe2\x80\x99s latency \xe2\x80\xa6000BcExperimental! Use at your own risk.0ChThe ECT(0) codepoint, indicating that an endpoint is \xe2\x80\xa600ChThe ECT(1) codepoint, indicating that an endpoint is \xe2\x80\xa600CaA complete set of keys for a certain packet space0CbA Long packet header, as used during the handshakeChNot attributed to any particular FrameType.CkAn open network transmission within a multipath-enabled \xe2\x80\xa6Ab(Multi)Path eventsB`An error occurred during readingCeWhether an endpoint was the initiator of a connection0nThe 0 path id.0CaThe address observed by the remote over this path0CjThe source address of the datagram(s) contained in the \xe2\x80\xa6mType of errorBlCurrent congestion window of the connection.0gPayloadAoReturns the argument unchanged.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000BaCalls U::from(self).000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dNextCoMethod for opening a sealed message data in-place.B`Open a single stream if possible1CgReturns the Path structure of an open pathBhPing the remote endpoint over this path.AhPing the remote endpointCkCheck whether the timer has expired, and register to be \xe2\x80\xa6BaReturns application-facing eventsBgRead data contiguously from the stream.AoRead from the given recv streamCcDecode TransportParameters from bufferBiReceive an unreliable, unordered datagramBdMethod for sealing a token in-place.0CiQueue an unreliable, unordered datagram for immediate \xe2\x80\xa6CbSends a Transmit on the given socket.CmNumber of address validation tokens sent to a client when \xe2\x80\xa60BmThe side of the connection (client or server)ChLook up whether we\xe2\x80\x99re the client or server of this \xe2\x80\xa6AlMethod for signing a message0CdAmount of data written to the caller-supplied buffer0hSpin bitAcStop accepting data0BoStop accepting data on the given receive streamCiTry to find and take a token that was stored with the \xe2\x80\xa60BgA chunk of data from the receive stream0CiThe RFC8312 congestion controller, as widely used for TCP0Borustls reports protocol errors using this type.0DbErrors encountered while decoding TransportParametersCdErrors that the nat traversal state might encounter.BeEvents of interest to the applicationCiEvent emitted when the client receives ADD_ADDRESS or \xe2\x80\xa6CjAttributed to a specific FrameType, never \xe2\x80\xa6A`Any other error.0CcThe peer abandoned transmitting data on this streamCnThe peer is unable to continue processing this connection, \xe2\x80\xa6CdThe peer abandoned transmitting data on this stream.1AeA Retry packet headerCdA short packet header, as used during the data phaseCmBest-effort extraction of the ALPN protocols from the TLS \xe2\x80\xa60BiConstruct a fresh Controller0AiThe contents of the chunkCiThe total amount of bytes which have been transferred \xe2\x80\xa610BnUnderlying error describing reason for failureBaClose the connection immediately.CnClose all of this endpoint\xe2\x80\x99s connections immediately and \xe2\x80\xa6AaCloses this path.AnClose a connection immediatelyBcFrame type that triggered the errorCcDistinguishes streams of the same initiator and \xe2\x80\xa60CjReturns whether the codepoint is a CE, signalling that \xe2\x80\xa60AgKey for encrypting data0BgParse a connection id from given bufferAhReturns all known paths.BlUpdate the timer to expire at iBbClose the send stream immediately.BeAbandon transmitting data on a streamCnRespond with a retry packet, requiring the client to retry \xe2\x80\xa60CiDrive future to completion in the backgroundCeShow connection stats the at the end of the benchmarkAmReturns connection statisticsBiReturns relevant stats from this EndpointCaReturns the PathStats for this path.CgAggregate connection statistics at the moment of close.3BcTitle to record in the qlog capture0CmWrite a buffer into this stream, returning how many bytes \xe2\x80\xa6AmSend data on the given streamCcEncode TransportParameters into bufferC`Future produced by Endpoint::acceptClPaths marked as backup will only be used if there are no \xe2\x80\xa60CeChunks returned from RecvStream::read().AmThe initiator of a connectionAdRun as a perf client1CoState of a Connection at the moment it was closed.CgAttempted to initiate NAT traversal on a closed, or \xe2\x80\xa6BcThe receiver lagged too far behind.CcFuture produced by Connection::open_biChOne or more new streams has been opened and might be \xe2\x80\xa6ChId representing different paths when using multipath \xe2\x80\xa60AbCoding result typeAlThe acceptor of a connectionAdRun as a perf server1mStream eventsAiAn integer less than 2^620CfGet the next incoming connection attempt from a clientClAttempt to accept this incoming connection (an error may \xe2\x80\xa6CcAccept a remotely initiated stream of a certain \xe2\x80\xa61BlAllows to configure the desired cipher suiteCiHelper to construct an endpoint for use with outgoing \xe2\x80\xa6CcWait for the connection to be closed for any reasonAfCoding related traits.BnAccess the configuration used by this endpointChTraits and implementations for the QUIC cryptography \xe2\x80\xa6BjCreate QUIC error code from TLS alert codeBoTLS configuration used for incoming connections210BkAn underlying crypto (e.g. TLS) layer errorCgDecode a plain header from given buffer, with given \xe2\x80\xa6DiDecode a Self from the provided buffer, if the buffer is \xe2\x80\xa6ChThe destination IP address which was encoded in this \xe2\x80\xa6D`Append the encoding of self to the provided buffer.CmNotify the peer that no more data will ever be written to \xe2\x80\xa6CnFinish a send stream, signalling that no more data will be \xe2\x80\xa6B`Process an incoming UDP datagramAfHeader protection keys0ClIgnore this incoming connection attempt, not sending any \xe2\x80\xa60C`Potentially store a token for later one-time use0CfPerform NSS-compatible TLS key logging to the file \xe2\x80\xa6AhThe offset in the stream0B`Packet deliveries were confirmed0AfPacket protection keys0lRandom valueBeThe reason the connection was closed.BcHuman-readable reason for the close0BcReason why this path was abandoned.11BhHuman-readable explanation of the reasonBeReason that the connection was closed2AjSwitch to a new UDP socketBgReject this incoming connection attempt0BoReturns the remote address of the network path.AgKey for decrypting data10AmTLS interface based on rustls0CnHelper to construct an endpoint for use with both incoming \xe2\x80\xa6BkOptional source IP address for the datagram00CgThe current local PathStatus of this path.B`The new status set by the remote0CfThe size of a single datagram in the associated bufferCkStatistics about datagrams and bytes received on this path.CjStatistics about UDP datagrams received on the connection.10CgStatistics about datagrams and bytes sent on this path.CeStatistics about UDP datagrams transmitted on the \xe2\x80\xa610AnMethod for verifying a message0CcNumber of ack-eliciting bytes that may be in flight0A`Send would blockCcNo more data is currently available on this stream.CjThe peer is not able to accept additional data, or the \xe2\x80\xa6BfA catch-all error for unlikely errors.0BlA key for signing with HMAC-based algorithms0AhAn Initial packet headerBnA pair of keys for bidirectional communication0BhA simple, standard congestion controller0CdFuture produced by Connection::open_uniCkAbstracts I/O and timer operations for runtime independenceBfA cryptographic session (commonly TLS)0CcThe peer is no longer accepting data on this streamCfFuture returned from SendStream::stopped.ClThe peer is no longer accepting data on this stream, and \xe2\x80\xa6CkThe peer is no longer accepting data on this stream. No \xe2\x80\xa6CgThe peer asked us to stop sending on an outgoing streamAaAccess to streamsCaThe stream is larger than the user-supplied limitCnAttributed to some frame type this implementation does not \xe2\x80\xa6l0-RTT packetCjReturns the length of a CID for connections created by \xe2\x80\xa60CdProvide the length of dst_cid in short header packetCcThe total number of clients which should be createdAlConnect to a remote endpointAeInitiate a connectionBbDecrypt the Initial packet payloadCgDecrypt the packet payload with the given packet numberBcDecrypt the given packet\xe2\x80\x99s header210ClConstructs a value with arbitrary fields, intended to be \xe2\x80\xa6DjConstruct an event that indicating that a Connection will \xe2\x80\xa6BkThe destination connection ID of the packetBkThe destination Connection ID of the packetAiDestination Connection ID00000CgEncrypt the packet payload with the given packet numberBcEncrypt the given packet\xe2\x80\x99s header10CbCheck if this stream has been opened during 0-RTT.0CmRetrieve implementation-specific metrics used to populate \xe2\x80\xa60CnThe maximum UDP payload size guaranteed to be supported by \xe2\x80\xa60CjUniform interface to send and receive UDP packets with \xe2\x80\xa6BbOne or more packets were just sent0BmInitiate a new outgoing bidirectional stream.CaReturns the path ID of the new path being opened.AdSource Connection ID0000CiCompletes when the peer stops the stream or reads the \xe2\x80\xa6CjCheck if this stream was stopped, get the reason if it wasCcThe total number of streams which should be createdAlProvide control over streamsCeThe length of the AEAD tag appended to packets on \xe2\x80\xa60CdUpgrade the handle to a full ConnectionB`Upgrades to a Path.AkSet the QUIC version to use0lQUIC versionB`The version that was unsupported11CeFuture produced by Connection::accept_biAfAn unreliable datagramBdDatagram support is disabled locally0A`A QUIC endpoint.BcThe main entry point to the libraryChA finished stream has been fully acknowledged or stoppedCkAn incoming connection for which the server has not yet \xe2\x80\xa60BoLong packet types with uniform header structureB`The largest encoded value length0Cmthe connection is being closed abruptly in the absence of \xe2\x80\xa60CeFuture returned by Connection::on_closedClFuture produced by crate::Connection::open_pathCmA currently open stream likely has data or errors waiting \xe2\x80\xa6CkMetadata for a single buffer filled with bytes received \xe2\x80\xa6BkResponse generated directly by the endpointCfIdentifier for a stream within a particular connection0CnCommunication with the peer has lapsed for longer than the \xe2\x80\xa6CkWe didn\xe2\x80\x99t receive any data from the remote within the \xe2\x80\xa61ChResponsible for limiting clients\xe2\x80\x99 ability to reuse \xe2\x80\xa60ClThe datagram is larger than the connection can currently \xe2\x80\xa60AbAn outgoing packet00CmStatistics about UDP datagrams transmitted or received on \xe2\x80\xa60CnA formerly write-blocked stream might be ready for a write \xe2\x80\xa6AkCongestion algorithm to useAhContents of the datagramBoMark the read data as consumed from the stream.BnStatistics about frames received on this path.CcStatistics about frames received on the connection.10CaStatistics about frames transmitted on this path.CfStatistics about frames transmitted on the connection.10DhConstructs cid by reading len bytes from a Buf0DkCreates a new qlog factory that writes files into QLOGDIR, \xe2\x80\xa60BfCreate a generator with a specific keyBjConstruct a VarInt infallibly0BeSucceeds iff x < 2^620AeWhat type we receivedAoWhat handshake type we received10CbWhether 0-RTT is/was possible during the handshakeCmSpecifies the time to wait after completing MTU discovery \xe2\x80\xa60CcReturns Self for use in down-casting to extract \xe2\x80\xa60EhReturns true if the Connection associated with this handle \xe2\x80\xa6CmDuration after an address validation token was issued for \xe2\x80\xa60CeThe local IP address which was used when the peer \xe2\x80\xa60BjThe local IP used for this path, if known.BnReturns the local address of the network path.20ClCompute the maximum size of datagrams that may be passed \xe2\x80\xa6BnInitiate a new outgoing unidirectional stream.BcGet the priority of the send streamAlGet the priority of a streamCfThe negotiated application protocol, if ALPN is in use0Aeqlog output directoryBbOptional response to transmit backCiOptional seed to be used internally for random number \xe2\x80\xa60AlSlow start threshold (bytes)0BeSucceeds iff x < 2^6200000DaSends a Transmit on the given socket without any \xe2\x80\xa6DkQuickly determine whether cid could have been generated by \xe2\x80\xa600BmA path was abandoned and is no longer usable.0CfFuture produced by Connection::accept_uniCdPaths marked with as available will be used when \xe2\x80\xa60CnAt least one new stream of a certain directionality may be \xe2\x80\xa6BkThe connection was successfully establishedAoAPI to control datagram trafficAlDecoding of QUIC primitives.CkA path was discarded and all remaining state for it has \xe2\x80\xa60BgInfallible encoding of QUIC primitives.CnIdentifies a network path by the combination of remote and \xe2\x80\xa60A`Handshake packetBcKeys used to protect packet headers0CiCatch-all error for problems while decoding transport \xe2\x80\xa6CeNat traversal attempt failed due to a multipath errorBdKeys used to protect packet payloads0BmErrors that might trigger a path being closed0AnApplication events about paths0BjStatistics related to a transmission path.0AeA read error occurredBmErrors that arise from reading from a stream.C`Errors triggered when reading from a recv streamCiAn object for asynchronously writing to an associated \xe2\x80\xa6BmAccept the next incoming bidirectional streamBbDuplicate the controller\xe2\x80\x99s state0BeThe number of UDP datagrams observed.AaControl datagrams1BeCreate new object from the given bits00BoWhich side of a connection initiated the stream0CmConvert into a 0-RTT or 0.5-RTT connection at the cost of \xe2\x80\xa6BoShorthand for self == Side::Client0B`Whether the connection is closedBoShorthand for self == Side::Server0CbWhether it is legal to respond with a retry packet0CdWhether to allow clients to migrate to new addresses0CdConstruct a timer that will expire at iBoLow-level protocol logic for the QUIC protoocolCjWait for the connection to be closed without keeping a \xe2\x80\xa6CjOpens an additional path if the multipath extension is \xe2\x80\xa6AaOpens a new path.BmPing the remote endpoint over a specific pathCiAttempts to read from the stream into the provided bufferCeReceive UDP datagrams, or register to be woken if \xe2\x80\xa6CkSend a UDP datagram, or register to be woken if sending \xe2\x80\xa6ClPrivate key used to send authenticated connection resets \xe2\x80\xa60BmGenerate the integrity tag for a retry packet0BgA stable identifier for this connectionCePrivate key used to authenticate data included in \xe2\x80\xa60BlThe position of a token in the packet bufferCgTransport configuration to use for incoming connections0CkWaits for all connections on the endpoint to be cleanly \xe2\x80\xa6BoWrite a buffer into this stream in its entiretyCiAbstract implementation of an async timer for runtime \xe2\x80\xa6BoNumber of UDP packets to send/receive at a timeCmConfiguration for the Bbr3 congestion controller0CkError indicating that a path has not been opened or has \xe2\x80\xa6BnThe path is already closed or was never opened1101BeIn-progress connection attempt futureAbA QUIC connection.CeProtocol state and logic for a single QUIC connectionCeCommon interface for different congestion controllers0CjNumber of frames transmitted or received of each frame \xe2\x80\xa60BoThe connection ID was not recognized by the \xe2\x80\xa60DdThe type used to refer to FrameTypes in closing and \xe2\x80\xa6DbA stream of PathEvents for all paths in a connection.AnThe QUIC-MULTIPATH path status0BeConfiguration for qlog trace logging.0BnA stream that can only be used to receive dataAaAccess to streamsChErrors that arise while waiting for a stream to be resetDhError for attempting to retry an Incoming which already \xe2\x80\xa60BkA stream that can only be used to send data3BmObject to get current SystemTime0AdA rustls TLS session0CkResponsible for storing validation tokens received from \xe2\x80\xa60AeA borrowed UDP socketBjErrors that arise from writing to a streamBoErrors triggered while writing to a send streamBoAccept the next incoming uni-directional streamCkWhether the implementation is permitted to set the spin \xe2\x80\xa60CbReturns the number of bytes read from this stream.0CiCloses a path and sends a PATH_ABANDON frame with the \xe2\x80\xa6CdLogic for controlling the rate at which data is sent0BfWhether to print connection statisticsBnClass of error as encoded in the specificationB`Application-specific reason code10CaThe error code to be sent with the abandon frame.BoThe error that was sent with the abandon frame.AoError code supplied by the peerBcType of frame that caused the close0AiExtract the integer value0CnWhether there is no longer any need to keep the connection \xe2\x80\xa6DkDetermine whether this is the last event a Connection will \xe2\x80\xa6CiLook up the local IP address and port used by this socketDgGet the local SocketAddr the underlying socket is bound toBfThe number of bytes lost on this path.BkThe number of bytes lost on the connection.10CjPad UDP datagrams carrying application data to current \xe2\x80\xa60AgReturns path statisticsCjPer-path statistics for every path the connection knew \xe2\x80\xa6CjThe final path stats, they are no longer available via \xe2\x80\xa620C`Attempt to write bytes from buf into the stream.CoReads the next segment of data as zero-copy Bytes.AoReads the next segment of data.CkRead an exact number of bytes contiguously from the stream.BnSets the PathStatus of this path.BoEpoch qlog event times are recorded relative to0ClError type for attempting to accept an IncomingBjErrors in the configuration of an endpoint0AeGeneric crypto errors0CnConfiguration for the Cubic congestion controller0CdA new path has established connection with the peer.0CdReasons why attempting to finish a stream might failCnMaximum duration of inactivity to accept before timing out \xe2\x80\xa60AnValue exceeds supported bounds0D`Constructs a QlogConfig for individual connections.0AfA noq runtime for smolB`Application events about streamsCbAccept this incoming connection using a custom \xe2\x80\xa6CeLargest UDP payload size the path currently supports.ClStorage size required for the largest packet that can be \xe2\x80\xa61BiDescription to record in the qlog capture0BjThe socket this datagram should be sent to00DcCreates a new FourTuple without a known local address.0BkStarting guess for maximum UDP payload sizeCkThe initial value to be used as the maximum UDP payload \xe2\x80\xa60ChUDP payload size that the network must be capable of \xe2\x80\xa6BjThe RTT used before an RTT sample is taken0BfThe initial round-trip-time (in msecs)CiWhether this is the event is the event indicating the \xe2\x80\xa6CeThe amount of concurrent streams which should be usedDdPackets are acked in batches, all with the same now \xe2\x80\xa60AePacing rate (bytes/s)0DeA stream of PathEvents for all paths in this connection.DfGets the local PathStatus for a known PathIdCkConvenience method to read all remaining data into a bufferAlProvide control over streamsBoThe sample size used for this key\xe2\x80\x99s algorithm01CiMaximum number of bytes to transmit to a peer without \xe2\x80\xa600BoThe server name specified by the client, if any0BmObject to get current SystemTime0BdSet a custom TokenStore0CaNumber of bytes to transmit from client to serverCnSpecifies the upper bound to the max UDP payload size that \xe2\x80\xa60ChReturns a weak reference to the inner connection struct.CdReturns a WeakPathHandle for this path.DgCreate a server config with the given crypto::ServerConfig0CeSets a prefix to the filename of the generated files.0DdWrites a single Bytes into this stream in its entirety.BbAn ADD_ADDRESS frame was received.BfConfiguration for outgoing connectionsCaClient-side configuration for the crypto protocol10CgThe stream has already been stopped, finished, or resetBmThe stream has already been finished or resetCmError indicating that a stream has not been opened or has \xe2\x80\xa6CjThe stream has not been opened or was already stopped, \xe2\x80\xa6CfThe stream has not been opened or has already been \xe2\x80\xa6CmThe stream has not been opened or was already finished or \xe2\x80\xa63CgErrors in the parameters being used to create a new \xe2\x80\xa60BkProtocol-level identifier for a connection.0CkWe couldn\xe2\x80\x99t decrypt a message. This is invariably fatal.0BjExplicit congestion notification codepoint00CnWe couldn\xe2\x80\x99t encrypt a message because it was larger than \xe2\x80\xa60BhParameters that are semantically invalidCkCannot close the last remaining open path via the local \xe2\x80\xa60Akn0\xe2\x80\x99s nat traversal eventsDhNull implementation of TokenLog, which never accepts tokens0ClReceived an observation of our external address from the \xe2\x80\xa60CiFuture produced by Connection::read_datagramBiThe remote changed the status of the path0BlRTT estimation for a particular network pathCnFuture produced by Connection::send_datagram_waitBiParameters governing incoming connectionsCaServer-side configuration for the crypto protocol10CmErrors that arise while monitoring for a send stream stop \xe2\x80\xa6AgA noq runtime for TokioC`Returns the lifetime of generated Connection IDs0BjWhether the connection is closed, and why.CjConnect to a remote endpoint using a custom configuration.AlConservative estimate of RTTBnGet the 0-RTT keys if available (clients only)0AgWhich types we expectedB`Which handshake type we expected10AcGenerates a new CID0ClThe number of segments to read when GRO is enabled. Used \xe2\x80\xa6DeProcess ConnectionEvents generated by the associated \xe2\x80\xa6EaProcess EndpointEvents emitted from related ConnectionsCgCreate the initial set of keys given the client\xe2\x80\x99s \xe2\x80\xa6000BhThe number of packets lost on this path.BmThe number of packets lost on the connection.10DcMaximum number of Incoming to allow to exist at a time0CjWhether datagrams might get fragmented into multiple parts0CmWhether transmitted datagrams might get fragmented by the \xe2\x80\xa6BdThe network path used for this path.CkReturns the path\xe2\x80\x99s network path represented as a 4-tuple.CkThe original destination CID when initiating the connectionCiThe original destination connection ID sent by the clientDhReturns the next time at which handle_timeout should be \xe2\x80\xa6D`Configures qlog capturing by setting a QlogFactory.0AgThe runtime type to useCkThe segment size if this transmission contains multiple \xe2\x80\xa600CkSend Quantum (bytes) used to control the size of packet \xe2\x80\xa60ChThe number of streams that may have unacknowledged data.CbSet the lifetime of CIDs created by this generator0BcSet the priority of the send streamAlSet the priority of a streamCnInitialize a QUIC-compatible TLS client configuration with \xe2\x80\xa6000AmSend data on the given streamCkWe received a fatal alert. This means the peer is unhappy.0BhBloom filter-based TokenLog0CmThe connection could not be created because not enough of \xe2\x80\xa6000CaEvent resulting from processing a single datagramBlEvents sent from a Connection to an EndpointAoStatistics on Endpoint activityBnThe stream finished before all bytes were readBlAuthentication data for (rustls) TLS session0Ccreceived an invalid Retry Token in a client Initial0ChIterator over ALPN protocol names from a TLS ClientHelloAlThe packet header is invalidBkThe local application closed the connection0DaThe datagram may result in starting a new ConnectionD`Configuration for the NewReno congestion controller0BjDecodes a QUIC packet\xe2\x80\x99s invariant headerCgErrors triggered when opening a recv stream for readingCaDefault implementation of TimeSource0CgError indicating that the provided buffer was too smallChFor clients, if the peer accepted the 0-RTT data packetsAbAck Frequency modeAfExports for benchmarksBoSupply a custom connection ID generator factory0DgCreate a UdpSender that can register a single task for \xe2\x80\xa6CaNumber of bytes to transmit from server to clientAmGet the Incoming0CnThe max_ack_delay we will request the peer to use0CgDisable packet encryption/decryption (for debugging \xe2\x80\xa6CkThe known MTU for the current network path has been updated0BbCryptographic identity of the peerBgGet the peer\xe2\x80\x99s identity, if availableDhFor the rustls TlsSession, the Any type is \xe2\x80\xa610AkReturns packets to transmitDjConfigures qlog capturing through the QLOGDIR environment \xe2\x80\xa60AoReceive an application datagramDfTransmit data as an unreliable, unordered application \xe2\x80\xa6CmWhether to implement fair queuing for send streams having \xe2\x80\xa60BfLength of sign\xe2\x80\x99s output0BnStart a client session with this configurationBnStart a server session with this configuration10BdA REMOVE_ADDRESS frame was received.CgAbstract implementation of a UDP socket for runtime \xe2\x80\xa6BgErrors triggered when abandoning a path0AgThe connection was lost00000ChGlobal configuration for the endpoint, affecting all \xe2\x80\xa60Cithe endpoint encountered an internal error and cannot \xe2\x80\xa60FbBasic adapter to let Incoming be await-ed like a ConnectingBhEncounter a frame ID that was not valid.CeThe peer sent us a TLS message with invalid contents.0Amno viable network path exists0DhNull implementation of TokenStore, which does not store \xe2\x80\xa60CiThe peer deviated from the standard TLS protocol. The \xe2\x80\xa60BmErrors that arise from reading from a stream.C`Errors from RecvStream::read_to_endCaIndicates whether a frame needs to be transmittedCmThe peer violated the QUIC specification as understood by \xe2\x80\xa60CiTransport-level errors occur when a peer violates the \xe2\x80\xa6C`Tokio-compatible UDP socket with some useful \xe2\x80\xa6DiWeak handle for a Path that does not keep the connection \xe2\x80\xa6BnCreate a client endpoint and client connectionAgGet a session referenceDiReturns a QlogConfig for a connection, if logging should \xe2\x80\xa60AiProcess timer expirationsBjParameters negotiated during the handshake0CfGet data negotiated during the handshake, if available0AiInitial congestion windowCiDefault limit on the amount of outstanding data in bytes.001000BnConverts this stream into an unordered stream.DdReturns true until the connection is fully established.CeWhether the connection is in the process of being \xe2\x80\xa61BfVerify the integrity of a retry packet0CnSpecifies the minimum MTU change to stop the MTU discovery \xe2\x80\xa60BdCompute keys for the next key update0AhOne packet was just lost0AhOne packet was just sent0BkConfigures qlog capturing into a directory.0AlRead bytes of handshake data0BeWhether to use the unordered read APIClMaximum number of bytes the peer may transmit across all \xe2\x80\xa600CkCompletes when the stream has been reset by the peer or \xe2\x80\xa60CiCheck whether this stream has been reset by the peer, \xe2\x80\xa6AjThe peer\xe2\x80\x99s UDP addressesAhThe peer\xe2\x80\x99s UDP addressBgThe peer\xe2\x80\x99s UDP address for this path.1CeSaturating integer addition. Computes self + rhs, \xe2\x80\xa6000ChSaturating integer subtraction. Computes self - rhs, \xe2\x80\xa60CkMaximum reordering in time space before time based loss \xe2\x80\xa60ChReason given by the transport for closing the connection0BfReasons why a connection might be lost0CiThe datagram is redirected to its ConnectionBlEvents sent from an Endpoint to a ConnectionAfConnection statistics.0BiStatistics for the currently open streamsAcPlain packet headerBkEnables writing qlog traces to a directory.0AkThe remote closed the path.CfError for when a validation token may have been reused0C`Parameters governing the core QUIC state machine0CbThe peer doesn\xe2\x80\x99t implement any supported version0CdFuture that completes when a connection is fully \xe2\x80\xa6CbThis was a 0-RTT stream and the server rejected it000CjAutomatically select an appropriate runtime from those \xe2\x80\xa6CkWhether to accept QUIC packets containing any value for \xe2\x80\xa60CdMaximum number of incoming packets that may fail \xe2\x80\xa60CnThe interface index of the interface on which the datagram \xe2\x80\xa6AjSwitch to a new UDP socketCiCreates a server endpoint which runs on the given runtimeD`Sets the PathStatus for a known PathIdCfSee proto::TransportConfig::send_window()BoSee TransportConfig::send_window()BmReturns whether a frame should be transmittedD`Convert t into the socket type used by this runtimeCdWrites handshake bytes into the given buffer and \xe2\x80\xa60ClStream produced by Path::observed_external_addrCiReason given by an application for closing the connection0CbThe peer\xe2\x80\x99s QUIC stack aborted the connection \xe2\x80\xa60DjInternal identifier for a Connection currently associated \xe2\x80\xa6CdOne or more application datagrams have been receivedBjDecrypted payload of a QUIC Initial packet0CaThe endpoint can no longer create new connections0Cnreceived a STREAM frame or a RESET_STREAM frame containing \xe2\x80\xa60ClSpecific failure cases from keys_match or a \xe2\x80\xa60A`key update error0CaCurrent limits do not allow us to open more paths0ClThe peer doesn\xe2\x80\x99t support a protocol version/feature we \xe2\x80\xa60BjA QUIC-compatible TLS client configuration0BjA QUIC-compatible TLS server configuration0EfTokenStore implementation that stores up to N tokens per \xe2\x80\xa60CgAn endpoint (local or remote) tried to add too many \xe2\x80\xa6CaPath could not be validated and will be abandonedCmWe didn\xe2\x80\x99t receive a path response in time after opening \xe2\x80\xa61BcA Version Negotiation packet headerCiRecord that the token was used and, ideally, return a \xe2\x80\xa60CjCurrent state of the congestion control algorithm, for \xe2\x80\xa6CcCurrent state of this connection\xe2\x80\x99s congestion \xe2\x80\xa6BaUpdate traffic keys spontaneously0CnThe maximum amount of segments which can be transmitted if \xe2\x80\xa6CnMaximum duration of inactivity to accept before timing out \xe2\x80\xa60Cgn0\xe2\x80\x99s (https://n0.computer) NAT Traversal protocol \xe2\x80\xa6CeGet the number of connections that are currently openBmNumber of connections that are currently openDeOpens a new path if no path exists yet for network_path.CmOpens a new path only if no path on the same network path \xe2\x80\xa6CiMaximum reordering in packet number space before FACK \xe2\x80\xa60B`Reads the next segments of data.CfGet the size of the socket receive bufferAlReceive buffer size in bytesCcGet the size of the socket send bufferAiSend buffer size in bytesBiSet a custom TransportConfig000ChConfiguration for sending and handling validation tokens0CnCreate a server config with the given certificate chain to \xe2\x80\xa60DfWrites a slice of Bytes into this stream in its entirety.Cfthe application or application protocol caused the \xe2\x80\xa60AnThe peer closed the connectionBoThe path was closed locally by the application.1B`Constructs controllers on demand0CnCommon congestion controller metrics used both for logging \xe2\x80\xa60ClThe trait for encrypting tokens that power retry packets \xe2\x80\xa60BcThe given server name was malformed0CbHints when the caller identifies a network change.0AcPacket decode errorBdReason for why a path was abandoned.BmErrors that can arise when sending a datagram0CcThe peer does not support receiving datagram frames0BdCongestion events on the connection.0AiCongestion window (bytes)0ClCompute the maximum size of datagrams that may be passed \xe2\x80\xa6BoBytes available in the outgoing datagram bufferClReplace the server configuration, affecting new incoming \xe2\x80\xa60CmWaits for all connections on the endpoint to be ready for \xe2\x80\xa6DgWrites Bytes from a slice of buffers into this stream, \xe2\x80\xa6Cmthe endpoint has reached the confidentiality or integrity \xe2\x80\xa60CkParameters for controlling the peer\xe2\x80\x99s acknowledgement \xe2\x80\xa60DjThe max_fragment_size value supplied in configuration was \xe2\x80\xa60Bmthe server refused to accept a new connection0BjParse connection id in short header packetCjOne or more application datagrams have been sent after \xe2\x80\xa6Ckreceived more data than permitted in advertised data limits0AoThe TLS handshake was confirmedBjThe connection\xe2\x80\x99s handshake data is readyCeAttempted an ordered read following an unordered readAnWe saw an invalid certificate.0BcParameters governing MTU discovery.0BnNot enough addresses to complete the operationCkdetected an error with protocol compliance that was not \xe2\x80\xa60Cjreceived a frame for a stream identifier that exceeded \xe2\x80\xa60Cnreceived a frame for a stream that was not in a state that \xe2\x80\xa60AnError from setting path status0AjTransport-level error code0ChThe local endpoint does not support the QUIC version \xe2\x80\xa6CkError indicating that the specified QUIC version is not \xe2\x80\xa61C`Packet uses a QUIC version that is not supported1CiMaximum quantity of out-of-order crypto layer data to \xe2\x80\xa60C`Create a VarInt without ensuring it\xe2\x80\x99s in range0ChCumulative number of Quic handshakes ignored on this \xe2\x80\xa6EgReturns true if the two WeakConnectionHandle point at the \xe2\x80\xa6CiMinimum interval between outgoing stateless reset packets0CjConstruct with an approximate maximum memory usage and \xe2\x80\xa60ChCumulative number of Quic handshakes refused on this \xe2\x80\xa6AnReturns the remote path statusDfTransmit data as an unreliable, unordered application \xe2\x80\xa6CnUpdates the set of ALPN protocols configured in the client \xe2\x80\xa6CnUpdates the set of ALPN protocols configured in the server \xe2\x80\xa610CiSee proto::TransportConfig::receive_window()CbSee TransportConfig::receive_window()B`Override supported QUIC versions0CiOverride the instant relative to which all events are \xe2\x80\xa60CcA stream of NAT traversal updates for a connection.BkNo remote CIDs available to open a new path0CkTransport parameters used to negotiate connection-level \xe2\x80\xa6CgA stream that can be used to receive data out-of-order.CmThe certificate verifier doesn\xe2\x80\x99t support the given type \xe2\x80\xa60CfThe operation is not allowed for this endpoint\xe2\x80\x99s \xe2\x80\xa6CiCumulative number of Quic handshakes accepted by this \xe2\x80\xa6CjSpecifies the amount of time that MTU discovery should \xe2\x80\xa60CiIf the 0-RTT-encrypted data has been accepted by the peer0BgWait for the handshake to be confirmed.CgInform the connection if a path may recover after a \xe2\x80\xa60CgPeriod of inactivity before sending a keep-alive packet0CfThe number of PLPMTUD probe packets lost on this path.0BlPackets were deemed lost or marked congested0CgCumulative number of Quic handshakes sent from this \xe2\x80\xa6CnThe number of remotely initiated open streams of a certain \xe2\x80\xa6AhBench fns for SendBufferCfThe number of PLPMTUD probe packets sent on this path.0Bireceived a frame that was badly formatted0CkThis function doesn\xe2\x80\x99t work until the TLS handshake is \xe2\x80\xa60CkWe received a TLS message that isn\xe2\x80\x99t valid right now. \xe2\x80\xa60CkThe remote address for the path is not supported by the \xe2\x80\xa6CiThe remote SocketAddr supplied was malformed10ChThe initial cipher suite (AES-128-GCM-SHA256) is not \xe2\x80\xa60CnWatches the external address reported by the peer for this \xe2\x80\xa6BdPaths can only be opened client-side0DmError returned when constructing a VarInt from a value >= \xe2\x80\xa60CeA handle to some connection internals, use with care.DkSpecifies the ACK frequency config (see AckFrequencyConfig \xe2\x80\xa60CjThe number of times a black hole was detected in the path.0CgMaximum number of received bytes to buffer for each \xe2\x80\xa60AeIs multipath enabled?ClMaximum number of datagrams that might be described by a \xe2\x80\xa60CkMaximum UDP payload size accepted from peers (excluding \xe2\x80\xa60AmMax UDP payload size in bytesDkSpecifies the MTU discovery config (see MtuDiscoveryConfig \xe2\x80\xa60AmReturn endpoint-facing eventsCkThe preferred IPv4 address that will be communicated to \xe2\x80\xa60CkThe preferred IPv6 address that will be communicated to \xe2\x80\xa60ChThe reordering threshold we will request the peer to use0ClDuration after a retry token was issued for which it\xe2\x80\x99s \xe2\x80\xa60BmSets the max idle timeout for a specific pathDfResize the receive buffer of socket to bytesDcResize the send buffer of socket to bytesBfThe peer\xe2\x80\x99s QUIC transport parametersBdQUIC connection transport parameters1CaGenerates connection IDs for incoming connections0CdAn incoming connection did not support any known \xe2\x80\xa60BjNo default client configuration was set up0BiPath abandoned due to unstable interfaces0ClConfiguration for sending and handling validation tokens \xe2\x80\xa60CmMaximum number of packets that may be sent using a single \xe2\x80\xa60CiNotify connections that the local network address has \xe2\x80\xa6ChHandle a change in the local address, i.e. an active \xe2\x80\xa6CiCounter for the number of bytes currently used in the \xe2\x80\xa6CkReduction in congestion window when a new loss event is \xe2\x80\xa60DdMaximum number of datagrams that a Transmit may encode.0CfA stream of NAT traversal updates for this connection.CjGet the address observed by the remote over the given pathCiMaximum number of bytes the peer may transmit without \xe2\x80\xa600Chreceived more data in CRYPTO frames than can be buffered0B`The extension was not negotiatedCbWe failed to figure out what time it currently is.0CbWe failed to acquire random bytes from the system.0CnError indicating that this operation requires multipath to \xe2\x80\xa6BnThe extension was not negotiated with the peerAkMultipath is not negotiated210CnError when the multipath extension was not negotiated, but \xe2\x80\xa6BnHeader of an Initial packet, before decryptionCgDerive keying material from this connection\xe2\x80\x99s TLS \xe2\x80\xa6EdFill output with output.len() bytes of keying material \xe2\x80\xa60BnPathological case: many segments, get from endCfGet segments in the old way, using a loop of get callsClCurrent number of remotely initiated streams that may be \xe2\x80\xa6ClTrack changes on our external address as reported by the \xe2\x80\xa60CmCreate a client configuration that trusts specified trust \xe2\x80\xa60BkThe peer didn\xe2\x80\x99t give us any certificates.0BkThe peer sent an oversized record/fragment.0CkThe ack-eliciting threshold we will request the peer to use0BiWhether there are any pending retransmitsCdWhether the Multipath for QUIC extension is enabled.CbThe peer\xe2\x80\x99s ACK-frequency parameters have changed0C`Sets the keep_alive_interval for a specific pathBoSet a custom ValidationTokenConfig0BmPath abandoned at the application\xe2\x80\x99s request0CjGet the current value of max_udp_payload_size0ChConfigure how to populate the destination CID of the \xe2\x80\xa60CjConstruct an endpoint with arbitrary configuration and \xe2\x80\xa6CfWhether the socket address that is initiating this \xe2\x80\xa60CmModify the number of open paths allowed when multipath is \xe2\x80\xa6Cmthe number of connection IDs provided by the peer exceeds \xe2\x80\xa60CbError returned by Session::export_keying_material.0CiA provided certificate revocation list (CRL) was invalid.0CmPath abandoned due to no available connection IDs for the \xe2\x80\xa60Clreceived transport parameters that were badly formatted, \xe2\x80\xa60CiRegisters one address at which this endpoint might be \xe2\x80\xa6ClAdd addresses the local endpoint considers are reachable \xe2\x80\xa6ClMaximum number of outgoing application datagram bytes to \xe2\x80\xa60CiSet the client configuration used by connectBnSets the max_idle_timeout for a specific path.BfThe QUIC protocol version implemented.CfThe path became unusable after a local network change.BoBytes available in the outgoing datagram bufferDhMaximum number of received bytes to buffer for all Incoming0DaVariant of max_concurrent_bidi_streams affecting \xe2\x80\xa60CmSets whether the server is allowed to migrate once during \xe2\x80\xa60ClModify the number of remotely initiated streams that may \xe2\x80\xa6BmSpurious congestion events on the connection.0CnCreate a client configuration that trusts the platform\xe2\x80\x99s \xe2\x80\xa60CkGenerates 8-byte connection IDs that can be efficiently \xe2\x80\xa6CkAn error occurred while handling Encrypted Client Hello \xe2\x80\xa60CkPath abandoned due to resource limitations in the transport0CiGenerates purely random connection IDs of a specified \xe2\x80\xa6ChWhether to use \xe2\x80\x9cGeneric Segmentation Offload\xe2\x80\x9d to \xe2\x80\xa60CmMaximum number of incoming bidirectional streams that may \xe2\x80\xa60ClMaximum number of incoming application datagram bytes to \xe2\x80\xa60BcInitiates a new nat traversal round0BdPackets were incorrectly deemed lostCaequivalent to BBRHandleSpuriousLossDetection: \xe2\x80\xa610CnRemoves one or more addresses from the set of addresses at \xe2\x80\xa6CgRemoves an address the endpoing no longer considers \xe2\x80\xa6C`Sets the keep_alive_interval for a specific pathDeA ConnectionIdParser implementation that assumes the \xe2\x80\xa6CjWe received a TLS handshake message that isn\xe2\x80\x99t valid \xe2\x80\xa60CiHow to construct new congestion::Controllers0BlSets a default per-path maximum idle timeout0CkConfigures an outbound rate limit (in bytes per second) \xe2\x80\xa60CkThe key exchange group negotiated with the peer. Useful \xe2\x80\xa60CbWhether to send observed address reports to peers.0CiModify the number of remotely initiated bidirectional \xe2\x80\xa6BiEnables the Multipath Extension for QUIC.0CjModify the number of remotely initiated unidirectional \xe2\x80\xa6CeNumber of consecutive PTOs after which network is \xe2\x80\xa60BkSets a default per-path keep alive interval0CjWhether to receive observed address reports from other \xe2\x80\xa60BmGet the current local nat traversal addresses0CkGet the currently advertised nat traversal addresses by \xe2\x80\xa60CkSets the maximum number of nat traversal addresses this \xe2\x80\xa60AcNat traversal draft") \ No newline at end of file diff --git a/pr/451/docs/search.index/desc/791dbc1ca79a.js b/pr/451/docs/search.index/desc/791dbc1ca79a.js deleted file mode 100644 index da822e76c..000000000 --- a/pr/451/docs/search.index/desc/791dbc1ca79a.js +++ /dev/null @@ -1 +0,0 @@ -rd_("AmData flows in both directions0CdThe CE codepoint, signalling that congestion was \xe2\x80\xa600AjContains the success valueBeThe PathId of this path.BmReturns the PathId of this path.AoGet the identity of this stream00CdThe path which can now be used for application data.AhWith path was abandoned.B`Which path had its state droppedAmPath which has changed statusCfPath over which the observed address was reported, \xe2\x80\xa643210AlWhich stream is now readableAlWhich stream is now writableAnWhich stream has been finishedAmWhich stream has been stoppedAnType of the Long header packetClWhether a stream communicates data in both directions or \xe2\x80\xa60AhContains the error valueAoThe largest representable valueAlThe maximum path ID allowed.10ClConnects to a QUIC perf server and maintains a specified \xe2\x80\xa6BmData flows only from the stream\xe2\x80\x99s initiator0AnWhich directions data flows in0CaDirectionality for which streams have been openedCdDirectionality for which streams are newly availableCjExplicit congestion notification bits to set on the packet0CeThe Explicit Congestion Notification bits for the \xe2\x80\xa61CdReturns the most recently observed external address.B`The current best RTT estimation.BfThe number of I/O operations executed.0BcLength of QUIC packet being decodedAlLength of the packet payload0BmThe number of bytes the associated buffer hasBbSet a custom TokenLog0CaMinimum RTT registered so far for this estimator.ClConstruct with an approximate maximum memory usage and a \xe2\x80\xa6CjConstruct an endpoint with arbitrary configuration and \xe2\x80\xa6DiCreates a new QlogConfig that writes a qlog trace to the \xe2\x80\xa6CiCreates a new qlog factory that writes files into the \xe2\x80\xa6D`Create a default config with a particular reset_keyCmCreate a default config with a particular handshake token \xe2\x80\xa6CkCreate a default config with a particular cryptographic \xe2\x80\xa6AmConstruct cid from byte arrayoConstruct emptyAeCreate a new StreamIdDjConstruct a state using the given config and current time \xe2\x80\xa60:8AeCreate a new endpointDcCreate a new instance of FixedLengthConnectionIdParserBdCreate a generator with a random key:987DgBegin decoding a QUIC packet from bytes, returning any \xe2\x80\xa67BkConstruct an error with a code and a reasonCgInitialize Random CID generator with a fixed CID length87BeCreates a new FourTuple.77BfQUIC transport protocol implementationCkGet SystemTime::now() or the mocked equivalentAhLook up the current time01ChCurrent best estimate of this connection\xe2\x80\x99s latency \xe2\x80\xa6000BcExperimental! Use at your own risk.0ChThe ECT(0) codepoint, indicating that an endpoint is \xe2\x80\xa600ChThe ECT(1) codepoint, indicating that an endpoint is \xe2\x80\xa600CaA complete set of keys for a certain packet space0CbA Long packet header, as used during the handshakeChNot attributed to any particular FrameType.CkAn open network transmission within a multipath-enabled \xe2\x80\xa6Ab(Multi)Path eventsB`An error occurred during readingCeWhether an endpoint was the initiator of a connection0nThe 0 path id.0CaThe address observed by the remote over this path0CjThe source address of the datagram(s) contained in the \xe2\x80\xa6mType of errorBlCurrent congestion window of the connection.0gPayloadAoReturns the argument unchanged.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000BaCalls U::from(self).00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dNextCoMethod for opening a sealed message data in-place.B`Open a single stream if possible1CgReturns the Path structure of an open pathBhPing the remote endpoint over this path.AhPing the remote endpointCkCheck whether the timer has expired, and register to be \xe2\x80\xa6BaReturns application-facing eventsBgRead data contiguously from the stream.AoRead from the given recv streamCcDecode TransportParameters from bufferBiReceive an unreliable, unordered datagramBdMethod for sealing a token in-place.0CiQueue an unreliable, unordered datagram for immediate \xe2\x80\xa6CbSends a Transmit on the given socket.CmNumber of address validation tokens sent to a client when \xe2\x80\xa60BmThe side of the connection (client or server)ChLook up whether we\xe2\x80\x99re the client or server of this \xe2\x80\xa6AlMethod for signing a message0CdAmount of data written to the caller-supplied buffer0hSpin bitAcStop accepting data0BoStop accepting data on the given receive streamCiTry to find and take a token that was stored with the \xe2\x80\xa60BgA chunk of data from the receive stream0CiThe RFC8312 congestion controller, as widely used for TCP0Borustls reports protocol errors using this type.0DbErrors encountered while decoding TransportParametersCdErrors that the nat traversal state might encounter.BeEvents of interest to the applicationCiEvent emitted when the client receives ADD_ADDRESS or \xe2\x80\xa6CjAttributed to a specific FrameType, never \xe2\x80\xa6A`Any other error.0CcThe peer abandoned transmitting data on this streamCnThe peer is unable to continue processing this connection, \xe2\x80\xa6CdThe peer abandoned transmitting data on this stream.1AeA Retry packet headerCdA short packet header, as used during the data phaseCmBest-effort extraction of the ALPN protocols from the TLS \xe2\x80\xa60BiConstruct a fresh Controller0AiThe contents of the chunkCiThe total amount of bytes which have been transferred \xe2\x80\xa610BnUnderlying error describing reason for failureBaClose the connection immediately.CnClose all of this endpoint\xe2\x80\x99s connections immediately and \xe2\x80\xa6AaCloses this path.AnClose a connection immediatelyBcFrame type that triggered the errorCcDistinguishes streams of the same initiator and \xe2\x80\xa60CjReturns whether the codepoint is a CE, signalling that \xe2\x80\xa60AgKey for encrypting data0BgParse a connection id from given bufferAhReturns all known paths.BlUpdate the timer to expire at iBbClose the send stream immediately.BeAbandon transmitting data on a streamCnRespond with a retry packet, requiring the client to retry \xe2\x80\xa60CiDrive future to completion in the backgroundCeShow connection stats the at the end of the benchmarkAmReturns connection statisticsBiReturns relevant stats from this EndpointCaReturns the PathStats for this path.CgAggregate connection statistics at the moment of close.3BcTitle to record in the qlog capture0CmWrite a buffer into this stream, returning how many bytes \xe2\x80\xa6AmSend data on the given streamCcEncode TransportParameters into bufferC`Future produced by Endpoint::acceptClPaths marked as backup will only be used if there are no \xe2\x80\xa60CeChunks returned from RecvStream::read().AmThe initiator of a connectionAdRun as a perf client1CoState of a Connection at the moment it was closed.CgAttempted to initiate NAT traversal on a closed, or \xe2\x80\xa6BcThe receiver lagged too far behind.CcFuture produced by Connection::open_biChOne or more new streams has been opened and might be \xe2\x80\xa6ChId representing different paths when using multipath \xe2\x80\xa60AbCoding result typeAlThe acceptor of a connectionAdRun as a perf server1mStream eventsAiAn integer less than 2^620CfGet the next incoming connection attempt from a clientClAttempt to accept this incoming connection (an error may \xe2\x80\xa6CcAccept a remotely initiated stream of a certain \xe2\x80\xa61BlAllows to configure the desired cipher suiteCiHelper to construct an endpoint for use with outgoing \xe2\x80\xa6CcWait for the connection to be closed for any reasonAfCoding related traits.BnAccess the configuration used by this endpointChTraits and implementations for the QUIC cryptography \xe2\x80\xa6BjCreate QUIC error code from TLS alert codeBoTLS configuration used for incoming connections210BkAn underlying crypto (e.g. TLS) layer errorCgDecode a plain header from given buffer, with given \xe2\x80\xa6DiDecode a Self from the provided buffer, if the buffer is \xe2\x80\xa6ChThe destination IP address which was encoded in this \xe2\x80\xa6D`Append the encoding of self to the provided buffer.CmNotify the peer that no more data will ever be written to \xe2\x80\xa6CnFinish a send stream, signalling that no more data will be \xe2\x80\xa6B`Process an incoming UDP datagramAfHeader protection keys0ClIgnore this incoming connection attempt, not sending any \xe2\x80\xa60C`Potentially store a token for later one-time use0CfPerform NSS-compatible TLS key logging to the file \xe2\x80\xa6AhThe offset in the stream0B`Packet deliveries were confirmed0AfPacket protection keys0lRandom valueBeThe reason the connection was closed.BcHuman-readable reason for the close0BcReason why this path was abandoned.11BhHuman-readable explanation of the reasonBeReason that the connection was closed2AjSwitch to a new UDP socketBgReject this incoming connection attempt0AgKey for decrypting dataBoReturns the remote address of the network path.1AmTLS interface based on rustls0CnHelper to construct an endpoint for use with both incoming \xe2\x80\xa6BkOptional source IP address for the datagram00CgThe current local PathStatus of this path.B`The new status set by the remote0CfThe size of a single datagram in the associated bufferCkStatistics about datagrams and bytes received on this path.CjStatistics about UDP datagrams received on the connection.10CgStatistics about datagrams and bytes sent on this path.CeStatistics about UDP datagrams transmitted on the \xe2\x80\xa610AnMethod for verifying a message0CcNumber of ack-eliciting bytes that may be in flight0A`Send would blockCcNo more data is currently available on this stream.CjThe peer is not able to accept additional data, or the \xe2\x80\xa6BfA catch-all error for unlikely errors.0BlA key for signing with HMAC-based algorithms0AhAn Initial packet headerBnA pair of keys for bidirectional communication0BhA simple, standard congestion controller0CdFuture produced by Connection::open_uniCkAbstracts I/O and timer operations for runtime independenceBfA cryptographic session (commonly TLS)0CcThe peer is no longer accepting data on this streamCfFuture returned from SendStream::stopped.ClThe peer is no longer accepting data on this stream, and \xe2\x80\xa6CkThe peer is no longer accepting data on this stream. No \xe2\x80\xa6CgThe peer asked us to stop sending on an outgoing streamAaAccess to streamsCaThe stream is larger than the user-supplied limitCnAttributed to some frame type this implementation does not \xe2\x80\xa6l0-RTT packetCjReturns the length of a CID for connections created by \xe2\x80\xa60CdProvide the length of dst_cid in short header packetCcThe total number of clients which should be createdAlConnect to a remote endpointAeInitiate a connectionBbDecrypt the Initial packet payloadCgDecrypt the packet payload with the given packet numberBcDecrypt the given packet\xe2\x80\x99s header210ClConstructs a value with arbitrary fields, intended to be \xe2\x80\xa6DjConstruct an event that indicating that a Connection will \xe2\x80\xa6BkThe destination connection ID of the packetBkThe destination Connection ID of the packetAiDestination Connection ID00000CgEncrypt the packet payload with the given packet numberBcEncrypt the given packet\xe2\x80\x99s header10CbCheck if this stream has been opened during 0-RTT.0CmRetrieve implementation-specific metrics used to populate \xe2\x80\xa60CnThe maximum UDP payload size guaranteed to be supported by \xe2\x80\xa60CjUniform interface to send and receive UDP packets with \xe2\x80\xa6BbOne or more packets were just sent0BmInitiate a new outgoing bidirectional stream.CaReturns the path ID of the new path being opened.AdSource Connection ID0000CiCompletes when the peer stops the stream or reads the \xe2\x80\xa6CjCheck if this stream was stopped, get the reason if it wasCcThe total number of streams which should be createdAlProvide control over streamsCeThe length of the AEAD tag appended to packets on \xe2\x80\xa60CdUpgrade the handle to a full ConnectionB`Upgrades to a Path.AkSet the QUIC version to use0lQUIC versionB`The version that was unsupported11CeFuture produced by Connection::accept_biAfAn unreliable datagramBdDatagram support is disabled locally0A`A QUIC endpoint.BcThe main entry point to the libraryChA finished stream has been fully acknowledged or stoppedCkAn incoming connection for which the server has not yet \xe2\x80\xa60BoLong packet types with uniform header structureB`The largest encoded value length0Cmthe connection is being closed abruptly in the absence of \xe2\x80\xa60CeFuture returned by Connection::on_closedClFuture produced by crate::Connection::open_pathCmA currently open stream likely has data or errors waiting \xe2\x80\xa6CkMetadata for a single buffer filled with bytes received \xe2\x80\xa6BkResponse generated directly by the endpointCfIdentifier for a stream within a particular connection0CnCommunication with the peer has lapsed for longer than the \xe2\x80\xa6CkWe didn\xe2\x80\x99t receive any data from the remote within the \xe2\x80\xa61ChResponsible for limiting clients\xe2\x80\x99 ability to reuse \xe2\x80\xa60ClThe datagram is larger than the connection can currently \xe2\x80\xa60AbAn outgoing packet00CmStatistics about UDP datagrams transmitted or received on \xe2\x80\xa60CnA formerly write-blocked stream might be ready for a write \xe2\x80\xa6AkCongestion algorithm to useAhContents of the datagramBoMark the read data as consumed from the stream.BnStatistics about frames received on this path.CcStatistics about frames received on the connection.10CaStatistics about frames transmitted on this path.CfStatistics about frames transmitted on the connection.10DhConstructs cid by reading len bytes from a Buf0DkCreates a new qlog factory that writes files into QLOGDIR, \xe2\x80\xa60BfCreate a generator with a specific keyBjConstruct a VarInt infallibly0BeSucceeds iff x < 2^620AeWhat type we receivedAoWhat handshake type we received10CbWhether 0-RTT is/was possible during the handshakeCmSpecifies the time to wait after completing MTU discovery \xe2\x80\xa60CcReturns Self for use in down-casting to extract \xe2\x80\xa60EhReturns true if the Connection associated with this handle \xe2\x80\xa6CmDuration after an address validation token was issued for \xe2\x80\xa60CeThe local IP address which was used when the peer \xe2\x80\xa60BjThe local IP used for this path, if known.1BnReturns the local address of the network path.ClCompute the maximum size of datagrams that may be passed \xe2\x80\xa6BnInitiate a new outgoing unidirectional stream.BcGet the priority of the send streamAlGet the priority of a streamCfThe negotiated application protocol, if ALPN is in use0Aeqlog output directoryBbOptional response to transmit backCiOptional seed to be used internally for random number \xe2\x80\xa60AlSlow start threshold (bytes)0BeSucceeds iff x < 2^6200000DaSends a Transmit on the given socket without any \xe2\x80\xa6DkQuickly determine whether cid could have been generated by \xe2\x80\xa600BmA path was abandoned and is no longer usable.0CfFuture produced by Connection::accept_uniCdPaths marked with as available will be used when \xe2\x80\xa60CnAt least one new stream of a certain directionality may be \xe2\x80\xa6BkThe connection was successfully establishedAoAPI to control datagram trafficAlDecoding of QUIC primitives.CkA path was discarded and all remaining state for it has \xe2\x80\xa60BgInfallible encoding of QUIC primitives.CnIdentifies a network path by the combination of remote and \xe2\x80\xa6A`Handshake packetBcKeys used to protect packet headers0CiCatch-all error for problems while decoding transport \xe2\x80\xa6CeNat traversal attempt failed due to a multipath errorBdKeys used to protect packet payloads0BmErrors that might trigger a path being closed0AnApplication events about paths0BjStatistics related to a transmission path.0AeA read error occurredBmErrors that arise from reading from a stream.C`Errors triggered when reading from a recv streamCiAn object for asynchronously writing to an associated \xe2\x80\xa6BmAccept the next incoming bidirectional streamBbDuplicate the controller\xe2\x80\x99s state0BeThe number of UDP datagrams observed.AaControl datagrams1BeCreate new object from the given bits00BoWhich side of a connection initiated the stream0CmConvert into a 0-RTT or 0.5-RTT connection at the cost of \xe2\x80\xa6BoShorthand for self == Side::Client0B`Whether the connection is closedBoShorthand for self == Side::Server0CbWhether it is legal to respond with a retry packet0CdWhether to allow clients to migrate to new addresses0CdConstruct a timer that will expire at iBoLow-level protocol logic for the QUIC protoocolCjWait for the connection to be closed without keeping a \xe2\x80\xa6CjOpens an additional path if the multipath extension is \xe2\x80\xa6AaOpens a new path.BmPing the remote endpoint over a specific pathCiAttempts to read from the stream into the provided bufferCeReceive UDP datagrams, or register to be woken if \xe2\x80\xa6CkSend a UDP datagram, or register to be woken if sending \xe2\x80\xa6ClPrivate key used to send authenticated connection resets \xe2\x80\xa60BmGenerate the integrity tag for a retry packet0BgA stable identifier for this connectionCePrivate key used to authenticate data included in \xe2\x80\xa60BlThe position of a token in the packet bufferCgTransport configuration to use for incoming connections0CjWait for all connections on the endpoint to be cleanly \xe2\x80\xa6BoWrite a buffer into this stream in its entiretyCiAbstract implementation of an async timer for runtime \xe2\x80\xa6BoNumber of UDP packets to send/receive at a timeCmConfiguration for the Bbr3 congestion controller0CkError indicating that a path has not been opened or has \xe2\x80\xa6BnThe path is already closed or was never opened1101BeIn-progress connection attempt futureAbA QUIC connection.CeProtocol state and logic for a single QUIC connectionCeCommon interface for different congestion controllers0CjNumber of frames transmitted or received of each frame \xe2\x80\xa60BoThe connection ID was not recognized by the \xe2\x80\xa60DdThe type used to refer to FrameTypes in closing and \xe2\x80\xa6DbA stream of PathEvents for all paths in a connection.AnThe QUIC-MULTIPATH path status0BeConfiguration for qlog trace logging.0BnA stream that can only be used to receive dataAaAccess to streamsChErrors that arise while waiting for a stream to be resetDhError for attempting to retry an Incoming which already \xe2\x80\xa60BkA stream that can only be used to send data3BmObject to get current SystemTime0AdA rustls TLS session0CkResponsible for storing validation tokens received from \xe2\x80\xa60AeA borrowed UDP socketBjErrors that arise from writing to a streamBoErrors triggered while writing to a send streamBoAccept the next incoming uni-directional streamCkWhether the implementation is permitted to set the spin \xe2\x80\xa60CbReturns the number of bytes read from this stream.0CiCloses a path and sends a PATH_ABANDON frame with the \xe2\x80\xa6CdLogic for controlling the rate at which data is sent0BfWhether to print connection statisticsBnClass of error as encoded in the specificationB`Application-specific reason code10CaThe error code to be sent with the abandon frame.BoThe error that was sent with the abandon frame.AoError code supplied by the peerBcType of frame that caused the close0AiExtract the integer value0CnWhether there is no longer any need to keep the connection \xe2\x80\xa6DkDetermine whether this is the last event a Connection will \xe2\x80\xa6CiLook up the local IP address and port used by this socketDgGet the local SocketAddr the underlying socket is bound toBfThe number of bytes lost on this path.BkThe number of bytes lost on the connection.10CjPad UDP datagrams carrying application data to current \xe2\x80\xa60AgReturns path statisticsCjPer-path statistics for every path the connection knew \xe2\x80\xa6CjThe final path stats, they are no longer available via \xe2\x80\xa620C`Attempt to write bytes from buf into the stream.CoReads the next segment of data as zero-copy Bytes.AoReads the next segment of data.CkRead an exact number of bytes contiguously from the stream.BnSets the PathStatus of this path.BoEpoch qlog event times are recorded relative to0ClError type for attempting to accept an IncomingBjErrors in the configuration of an endpoint0AeGeneric crypto errors0CnConfiguration for the Cubic congestion controller0CdA new path has established connection with the peer.0CdReasons why attempting to finish a stream might failCnMaximum duration of inactivity to accept before timing out \xe2\x80\xa60AnValue exceeds supported bounds0D`Constructs a QlogConfig for individual connections.0AfA noq runtime for smolB`Application events about streamsCbAccept this incoming connection using a custom \xe2\x80\xa6CeLargest UDP payload size the path currently supports.ClStorage size required for the largest packet that can be \xe2\x80\xa61BiDescription to record in the qlog capture0BjThe socket this datagram should be sent to00DcCreates a new FourTuple without a known local address.BkStarting guess for maximum UDP payload sizeCkThe initial value to be used as the maximum UDP payload \xe2\x80\xa60ChUDP payload size that the network must be capable of \xe2\x80\xa6BjThe RTT used before an RTT sample is taken0BfThe initial round-trip-time (in msecs)CeThe amount of concurrent streams which should be usedDdPackets are acked in batches, all with the same now \xe2\x80\xa60AePacing rate (bytes/s)0DeA stream of PathEvents for all paths in this connection.DfGets the local PathStatus for a known PathIdCkConvenience method to read all remaining data into a bufferAlProvide control over streamsBoThe sample size used for this key\xe2\x80\x99s algorithm01CiMaximum number of bytes to transmit to a peer without \xe2\x80\xa600BoThe server name specified by the client, if any0BmObject to get current SystemTime0BdSet a custom TokenStore0CaNumber of bytes to transmit from client to serverCnSpecifies the upper bound to the max UDP payload size that \xe2\x80\xa60ChReturns a weak reference to the inner connection struct.CdReturns a WeakPathHandle for this path.DgCreate a server config with the given crypto::ServerConfig0CeSets a prefix to the filename of the generated files.0DdWrites a single Bytes into this stream in its entirety.BbAn ADD_ADDRESS frame was received.BfConfiguration for outgoing connectionsCaClient-side configuration for the crypto protocol10CgThe stream has already been stopped, finished, or resetBmThe stream has already been finished or resetCmError indicating that a stream has not been opened or has \xe2\x80\xa6CjThe stream has not been opened or was already stopped, \xe2\x80\xa6CfThe stream has not been opened or has already been \xe2\x80\xa6CmThe stream has not been opened or was already finished or \xe2\x80\xa63CgErrors in the parameters being used to create a new \xe2\x80\xa60BkProtocol-level identifier for a connection.0CkWe couldn\xe2\x80\x99t decrypt a message. This is invariably fatal.0BjExplicit congestion notification codepoint00CnWe couldn\xe2\x80\x99t encrypt a message because it was larger than \xe2\x80\xa60BhParameters that are semantically invalidCkCannot close the last remaining open path via the local \xe2\x80\xa60Akn0\xe2\x80\x99s nat traversal eventsDhNull implementation of TokenLog, which never accepts tokens0ClReceived an observation of our external address from the \xe2\x80\xa60CiFuture produced by Connection::read_datagramBiThe remote changed the status of the path0BlRTT estimation for a particular network pathCnFuture produced by Connection::send_datagram_waitBiParameters governing incoming connectionsCaServer-side configuration for the crypto protocol10CmErrors that arise while monitoring for a send stream stop \xe2\x80\xa6AgA noq runtime for TokioC`Returns the lifetime of generated Connection IDs0BjWhether the connection is closed, and why.CjConnect to a remote endpoint using a custom configuration.AlConservative estimate of RTTBnGet the 0-RTT keys if available (clients only)0AgWhich types we expectedB`Which handshake type we expected10AcGenerates a new CID0ClThe number of segments to read when GRO is enabled. Used \xe2\x80\xa6DeProcess ConnectionEvents generated by the associated \xe2\x80\xa6EaProcess EndpointEvents emitted from related ConnectionsCgCreate the initial set of keys given the client\xe2\x80\x99s \xe2\x80\xa6000BhThe number of packets lost on this path.BmThe number of packets lost on the connection.10DcMaximum number of Incoming to allow to exist at a time0CjWhether datagrams might get fragmented into multiple parts0CmWhether transmitted datagrams might get fragmented by the \xe2\x80\xa6CkReturns the path\xe2\x80\x99s network path represented as a 4-tuple.CkThe original destination CID when initiating the connectionCiThe original destination connection ID sent by the clientDhReturns the next time at which handle_timeout should be \xe2\x80\xa6D`Configures qlog capturing by setting a QlogFactory.0AgThe runtime type to useCkThe segment size if this transmission contains multiple \xe2\x80\xa600CkSend Quantum (bytes) used to control the size of packet \xe2\x80\xa60ChThe number of streams that may have unacknowledged data.CbSet the lifetime of CIDs created by this generator0BcSet the priority of the send streamAlSet the priority of a streamCnInitialize a QUIC-compatible TLS client configuration with \xe2\x80\xa6000AmSend data on the given streamCkWe received a fatal alert. This means the peer is unhappy.0BhBloom filter-based TokenLog0CmThe connection could not be created because not enough of \xe2\x80\xa6000CaEvent resulting from processing a single datagramBlEvents sent from a Connection to an EndpointAoStatistics on Endpoint activityBnThe stream finished before all bytes were readBlAuthentication data for (rustls) TLS session0Ccreceived an invalid Retry Token in a client Initial0ChIterator over ALPN protocol names from a TLS ClientHelloAlThe packet header is invalidBkThe local application closed the connection0DaThe datagram may result in starting a new ConnectionD`Configuration for the NewReno congestion controller0BjDecodes a QUIC packet\xe2\x80\x99s invariant headerCgErrors triggered when opening a recv stream for readingCaDefault implementation of TimeSource0CgError indicating that the provided buffer was too smallChFor clients, if the peer accepted the 0-RTT data packetsAbAck Frequency modeAfExports for benchmarksBoSupply a custom connection ID generator factory0DgCreate a UdpSender that can register a single task for \xe2\x80\xa6CaNumber of bytes to transmit from server to clientAmGet the Incoming0CnThe max_ack_delay we will request the peer to use0CgDisable packet encryption/decryption (for debugging \xe2\x80\xa6CkThe known MTU for the current network path has been updated0BbCryptographic identity of the peerBgGet the peer\xe2\x80\x99s identity, if availableDhFor the rustls TlsSession, the Any type is \xe2\x80\xa610AkReturns packets to transmitDjConfigures qlog capturing through the QLOGDIR environment \xe2\x80\xa60AoReceive an application datagramDfTransmit data as an unreliable, unordered application \xe2\x80\xa6CmWhether to implement fair queuing for send streams having \xe2\x80\xa60BfLength of sign\xe2\x80\x99s output0BnStart a client session with this configurationBnStart a server session with this configuration10BdA REMOVE_ADDRESS frame was received.CgAbstract implementation of a UDP socket for runtime \xe2\x80\xa6BgErrors triggered when abandoning a path0AgThe connection was lost00000ChGlobal configuration for the endpoint, affecting all \xe2\x80\xa60Cithe endpoint encountered an internal error and cannot \xe2\x80\xa60FbBasic adapter to let Incoming be await-ed like a ConnectingBhEncounter a frame ID that was not valid.CeThe peer sent us a TLS message with invalid contents.0Amno viable network path exists0DhNull implementation of TokenStore, which does not store \xe2\x80\xa60CiThe peer deviated from the standard TLS protocol. The \xe2\x80\xa60BmErrors that arise from reading from a stream.C`Errors from RecvStream::read_to_endCaIndicates whether a frame needs to be transmittedCmThe peer violated the QUIC specification as understood by \xe2\x80\xa60CiTransport-level errors occur when a peer violates the \xe2\x80\xa6C`Tokio-compatible UDP socket with some useful \xe2\x80\xa6DiWeak handle for a Path that does not keep the connection \xe2\x80\xa6BnCreate a client endpoint and client connectionAgGet a session referenceDiReturns a QlogConfig for a connection, if logging should \xe2\x80\xa60AiProcess timer expirationsBjParameters negotiated during the handshake0CfGet data negotiated during the handshake, if available0AiInitial congestion windowCiDefault limit on the amount of outstanding data in bytes.001000BnConverts this stream into an unordered stream.DdReturns true until the connection is fully established.CeWhether the connection is in the process of being \xe2\x80\xa61BfVerify the integrity of a retry packet0CnSpecifies the minimum MTU change to stop the MTU discovery \xe2\x80\xa60BdCompute keys for the next key update0AhOne packet was just lost0AhOne packet was just sent0BkConfigures qlog capturing into a directory.0AlRead bytes of handshake data0BeWhether to use the unordered read APIClMaximum number of bytes the peer may transmit across all \xe2\x80\xa600CkCompletes when the stream has been reset by the peer or \xe2\x80\xa60CiCheck whether this stream has been reset by the peer, \xe2\x80\xa6AjThe peer\xe2\x80\x99s UDP addressesAhThe peer\xe2\x80\x99s UDP addressBgThe peer\xe2\x80\x99s UDP address for this path.1CeSaturating integer addition. Computes self + rhs, \xe2\x80\xa6000ChSaturating integer subtraction. Computes self - rhs, \xe2\x80\xa60CkMaximum reordering in time space before time based loss \xe2\x80\xa60ChReason given by the transport for closing the connection0BfReasons why a connection might be lost0CiThe datagram is redirected to its ConnectionBlEvents sent from an Endpoint to a ConnectionAfConnection statistics.0BiStatistics for the currently open streamsAcPlain packet headerBkEnables writing qlog traces to a directory.0AkThe remote closed the path.CfError for when a validation token may have been reused0C`Parameters governing the core QUIC state machine0CbThe peer doesn\xe2\x80\x99t implement any supported version0CdFuture that completes when a connection is fully \xe2\x80\xa6CbThis was a 0-RTT stream and the server rejected it000CjAutomatically select an appropriate runtime from those \xe2\x80\xa6CkWhether to accept QUIC packets containing any value for \xe2\x80\xa60CdMaximum number of incoming packets that may fail \xe2\x80\xa60CnThe interface index of the interface on which the datagram \xe2\x80\xa6AjSwitch to a new UDP socketCiCreates a server endpoint which runs on the given runtimeD`Sets the PathStatus for a known PathIdCfSee proto::TransportConfig::send_window()BoSee TransportConfig::send_window()BmReturns whether a frame should be transmittedD`Convert t into the socket type used by this runtimeCdWrites handshake bytes into the given buffer and \xe2\x80\xa60ClStream produced by Path::observed_external_addrCiReason given by an application for closing the connection0CbThe peer\xe2\x80\x99s QUIC stack aborted the connection \xe2\x80\xa60DjInternal identifier for a Connection currently associated \xe2\x80\xa6CdOne or more application datagrams have been receivedBjDecrypted payload of a QUIC Initial packet0CaThe endpoint can no longer create new connections0Cnreceived a STREAM frame or a RESET_STREAM frame containing \xe2\x80\xa60ClSpecific failure cases from keys_match or a \xe2\x80\xa60A`key update error0CaCurrent limits do not allow us to open more paths0ClThe peer doesn\xe2\x80\x99t support a protocol version/feature we \xe2\x80\xa60BjA QUIC-compatible TLS client configuration0BjA QUIC-compatible TLS server configuration0EfTokenStore implementation that stores up to N tokens per \xe2\x80\xa60CgAn endpoint (local or remote) tried to add too many \xe2\x80\xa6CaPath could not be validated and will be abandonedCmWe didn\xe2\x80\x99t receive a path response in time after opening \xe2\x80\xa61BcA Version Negotiation packet headerCiRecord that the token was used and, ideally, return a \xe2\x80\xa60CjCurrent state of the congestion control algorithm, for \xe2\x80\xa6CcCurrent state of this connection\xe2\x80\x99s congestion \xe2\x80\xa6BaUpdate traffic keys spontaneously0CnThe maximum amount of segments which can be transmitted if \xe2\x80\xa6CnMaximum duration of inactivity to accept before timing out \xe2\x80\xa60Cgn0\xe2\x80\x99s (https://n0.computer) NAT Traversal protocol \xe2\x80\xa6CeGet the number of connections that are currently openBmNumber of connections that are currently openCiOpens a new path if no path exists yet for the remote \xe2\x80\xa6CmOpens a new path only if no path on the same network path \xe2\x80\xa6CiMaximum reordering in packet number space before FACK \xe2\x80\xa60B`Reads the next segments of data.CfGet the size of the socket receive bufferAlReceive buffer size in bytesCcGet the size of the socket send bufferAiSend buffer size in bytesBiSet a custom TransportConfig000ChConfiguration for sending and handling validation tokens0CnCreate a server config with the given certificate chain to \xe2\x80\xa60DfWrites a slice of Bytes into this stream in its entirety.Cfthe application or application protocol caused the \xe2\x80\xa60AnThe peer closed the connectionBoThe path was closed locally by the application.1B`Constructs controllers on demand0CnCommon congestion controller metrics used both for logging \xe2\x80\xa60ClThe trait for encrypting tokens that power retry packets \xe2\x80\xa60BcThe given server name was malformed0CbHints when the caller identifies a network change.0AcPacket decode errorBdReason for why a path was abandoned.BmErrors that can arise when sending a datagram0CcThe peer does not support receiving datagram frames0BdCongestion events on the connection.0AiCongestion window (bytes)0ClCompute the maximum size of datagrams that may be passed \xe2\x80\xa6BoBytes available in the outgoing datagram bufferClReplace the server configuration, affecting new incoming \xe2\x80\xa60DgWrites Bytes from a slice of buffers into this stream, \xe2\x80\xa6Cmthe endpoint has reached the confidentiality or integrity \xe2\x80\xa60CkParameters for controlling the peer\xe2\x80\x99s acknowledgement \xe2\x80\xa60DjThe max_fragment_size value supplied in configuration was \xe2\x80\xa60Bmthe server refused to accept a new connection0BjParse connection id in short header packetCjOne or more application datagrams have been sent after \xe2\x80\xa6Ckreceived more data than permitted in advertised data limits0AoThe TLS handshake was confirmedBjThe connection\xe2\x80\x99s handshake data is readyCeAttempted an ordered read following an unordered readAnWe saw an invalid certificate.0BcParameters governing MTU discovery.0BnNot enough addresses to complete the operationCkdetected an error with protocol compliance that was not \xe2\x80\xa60Cjreceived a frame for a stream identifier that exceeded \xe2\x80\xa60Cnreceived a frame for a stream that was not in a state that \xe2\x80\xa60AnError from setting path status0AjTransport-level error code0ChThe local endpoint does not support the QUIC version \xe2\x80\xa6CkError indicating that the specified QUIC version is not \xe2\x80\xa61C`Packet uses a QUIC version that is not supported1CiMaximum quantity of out-of-order crypto layer data to \xe2\x80\xa60C`Create a VarInt without ensuring it\xe2\x80\x99s in range0ChCumulative number of Quic handshakes ignored on this \xe2\x80\xa6EgReturns true if the two WeakConnectionHandle point at the \xe2\x80\xa6CiMinimum interval between outgoing stateless reset packets0CjConstruct with an approximate maximum memory usage and \xe2\x80\xa60ChCumulative number of Quic handshakes refused on this \xe2\x80\xa6AnReturns the remote path statusDfTransmit data as an unreliable, unordered application \xe2\x80\xa6CnUpdates the set of ALPN protocols configured in the client \xe2\x80\xa6CnUpdates the set of ALPN protocols configured in the server \xe2\x80\xa610CiSee proto::TransportConfig::receive_window()CbSee TransportConfig::receive_window()B`Override supported QUIC versions0CiOverride the instant relative to which all events are \xe2\x80\xa60CcA stream of NAT traversal updates for a connection.BkNo remote CIDs available to open a new path0CkTransport parameters used to negotiate connection-level \xe2\x80\xa6CgA stream that can be used to receive data out-of-order.CmThe certificate verifier doesn\xe2\x80\x99t support the given type \xe2\x80\xa60CfThe operation is not allowed for this endpoint\xe2\x80\x99s \xe2\x80\xa6CiCumulative number of Quic handshakes accepted by this \xe2\x80\xa6CjSpecifies the amount of time that MTU discovery should \xe2\x80\xa60CiIf the 0-RTT-encrypted data has been accepted by the peer0BgWait for the handshake to be confirmed.CgInform the connection if a path may recover after a \xe2\x80\xa60CgPeriod of inactivity before sending a keep-alive packet0CfThe number of PLPMTUD probe packets lost on this path.0BlPackets were deemed lost or marked congested0CgCumulative number of Quic handshakes sent from this \xe2\x80\xa6CnThe number of remotely initiated open streams of a certain \xe2\x80\xa6AhBench fns for SendBufferCfThe number of PLPMTUD probe packets sent on this path.0Bireceived a frame that was badly formatted0CkThis function doesn\xe2\x80\x99t work until the TLS handshake is \xe2\x80\xa60CkWe received a TLS message that isn\xe2\x80\x99t valid right now. \xe2\x80\xa60CkThe remote address for the path is not supported by the \xe2\x80\xa6CiThe remote SocketAddr supplied was malformed10ChThe initial cipher suite (AES-128-GCM-SHA256) is not \xe2\x80\xa60CnWatches the external address reported by the peer for this \xe2\x80\xa6BdPaths can only be opened client-side0DmError returned when constructing a VarInt from a value >= \xe2\x80\xa60CeA handle to some connection internals, use with care.DkSpecifies the ACK frequency config (see AckFrequencyConfig \xe2\x80\xa60CjThe number of times a black hole was detected in the path.0CgMaximum number of received bytes to buffer for each \xe2\x80\xa60AeIs multipath enabled?ClMaximum number of datagrams that might be described by a \xe2\x80\xa60CkMaximum UDP payload size accepted from peers (excluding \xe2\x80\xa60AmMax UDP payload size in bytesDkSpecifies the MTU discovery config (see MtuDiscoveryConfig \xe2\x80\xa60AmReturn endpoint-facing eventsCkThe preferred IPv4 address that will be communicated to \xe2\x80\xa60CkThe preferred IPv6 address that will be communicated to \xe2\x80\xa60ChThe reordering threshold we will request the peer to use0ClDuration after a retry token was issued for which it\xe2\x80\x99s \xe2\x80\xa60BmSets the max idle timeout for a specific pathDfResize the receive buffer of socket to bytesDcResize the send buffer of socket to bytesBfThe peer\xe2\x80\x99s QUIC transport parametersBdQUIC connection transport parameters1CaGenerates connection IDs for incoming connections0CdAn incoming connection did not support any known \xe2\x80\xa60BjNo default client configuration was set up0BiPath abandoned due to unstable interfaces0ClConfiguration for sending and handling validation tokens \xe2\x80\xa60CmMaximum number of packets that may be sent using a single \xe2\x80\xa60CiNotify connections that the local network address has \xe2\x80\xa6ChHandle a change in the local address, i.e. an active \xe2\x80\xa6CiCounter for the number of bytes currently used in the \xe2\x80\xa6CgReturns whether we think the other address probably \xe2\x80\xa6CkReduction in congestion window when a new loss event is \xe2\x80\xa60DdMaximum number of datagrams that a Transmit may encode.0CfA stream of NAT traversal updates for this connection.CjGet the address observed by the remote over the given pathCiMaximum number of bytes the peer may transmit without \xe2\x80\xa600Chreceived more data in CRYPTO frames than can be buffered0B`The extension was not negotiatedCbWe failed to figure out what time it currently is.0CbWe failed to acquire random bytes from the system.0CnError indicating that this operation requires multipath to \xe2\x80\xa6BnThe extension was not negotiated with the peerAkMultipath is not negotiated210CnError when the multipath extension was not negotiated, but \xe2\x80\xa6CjThe path was opened in a NAT traversal round which was \xe2\x80\xa6BnHeader of an Initial packet, before decryptionCgDerive keying material from this connection\xe2\x80\x99s TLS \xe2\x80\xa6EdFill output with output.len() bytes of keying material \xe2\x80\xa60BnPathological case: many segments, get from endCfGet segments in the old way, using a loop of get callsClCurrent number of remotely initiated streams that may be \xe2\x80\xa6ClTrack changes on our external address as reported by the \xe2\x80\xa60CmCreate a client configuration that trusts specified trust \xe2\x80\xa60BkThe peer didn\xe2\x80\x99t give us any certificates.0BkThe peer sent an oversized record/fragment.0CkThe ack-eliciting threshold we will request the peer to use0BiWhether there are any pending retransmitsCdWhether the Multipath for QUIC extension is enabled.CbThe peer\xe2\x80\x99s ACK-frequency parameters have changed0C`Sets the keep_alive_interval for a specific pathBoSet a custom ValidationTokenConfig0BmPath abandoned at the application\xe2\x80\x99s request0CjGet the current value of max_udp_payload_size0ChConfigure how to populate the destination CID of the \xe2\x80\xa60CjConstruct an endpoint with arbitrary configuration and \xe2\x80\xa6CfWhether the socket address that is initiating this \xe2\x80\xa60CmModify the number of open paths allowed when multipath is \xe2\x80\xa6Cmthe number of connection IDs provided by the peer exceeds \xe2\x80\xa60CbError returned by Session::export_keying_material.0CiA provided certificate revocation list (CRL) was invalid.0CmPath abandoned due to no available connection IDs for the \xe2\x80\xa60Clreceived transport parameters that were badly formatted, \xe2\x80\xa60CiRegisters one address at which this endpoint might be \xe2\x80\xa6ClAdd addresses the local endpoint considers are reachable \xe2\x80\xa6ClMaximum number of outgoing application datagram bytes to \xe2\x80\xa60CiSet the client configuration used by connectBnSets the max_idle_timeout for a specific path.BfThe QUIC protocol version implemented.CfThe path became unusable after a local network change.BoBytes available in the outgoing datagram bufferDhMaximum number of received bytes to buffer for all Incoming0DaVariant of max_concurrent_bidi_streams affecting \xe2\x80\xa60ClModify the number of remotely initiated streams that may \xe2\x80\xa6BmSpurious congestion events on the connection.0CnCreate a client configuration that trusts the platform\xe2\x80\x99s \xe2\x80\xa60CkGenerates 8-byte connection IDs that can be efficiently \xe2\x80\xa6CkAn error occurred while handling Encrypted Client Hello \xe2\x80\xa60CkPath abandoned due to resource limitations in the transport0CiGenerates purely random connection IDs of a specified \xe2\x80\xa6ChWhether to use \xe2\x80\x9cGeneric Segmentation Offload\xe2\x80\x9d to \xe2\x80\xa60CmMaximum number of incoming bidirectional streams that may \xe2\x80\xa60BhUpdates this tuple\xe2\x80\x99s local address iffClMaximum number of incoming application datagram bytes to \xe2\x80\xa60BcInitiates a new nat traversal round0BdPackets were incorrectly deemed lostCaequivalent to BBRHandleSpuriousLossDetection: \xe2\x80\xa610CnRemoves one or more addresses from the set of addresses at \xe2\x80\xa6CgRemoves an address the endpoing no longer considers \xe2\x80\xa6C`Sets the keep_alive_interval for a specific pathDeA ConnectionIdParser implementation that assumes the \xe2\x80\xa6CjWe received a TLS handshake message that isn\xe2\x80\x99t valid \xe2\x80\xa60CiHow to construct new congestion::Controllers0BlSets a default per-path maximum idle timeout0CkConfigures an outbound rate limit (in bytes per second) \xe2\x80\xa60CkThe key exchange group negotiated with the peer. Useful \xe2\x80\xa60CbWhether to send observed address reports to peers.0CiModify the number of remotely initiated bidirectional \xe2\x80\xa6BiEnables the Multipath Extension for QUIC.0CjModify the number of remotely initiated unidirectional \xe2\x80\xa6CeNumber of consecutive PTOs after which network is \xe2\x80\xa60BkSets a default per-path keep alive interval0CjWhether to receive observed address reports from other \xe2\x80\xa60BmGet the current local nat traversal addresses0CkGet the currently advertised nat traversal addresses by \xe2\x80\xa60CkSets the maximum number of nat traversal addresses this \xe2\x80\xa60AcNat traversal draft") \ No newline at end of file diff --git a/pr/451/docs/search.index/entry/8a78b0eadc5e.js b/pr/451/docs/search.index/entry/8a78b0eadc5e.js deleted file mode 100644 index d6f75a4cb..000000000 --- a/pr/451/docs/search.index/entry/8a78b0eadc5e.js +++ /dev/null @@ -1 +0,0 @@ -rd_("Af[473,15,474,0,132,0,0]Ak[3155,15,3156,3156,133,0,0]Ag[473,15,474,0,4182,0,0]Al[3155,15,3156,1817,4183,0,0]Al[2017,15,2018,2018,4184,0,0]Al[3155,15,1720,1720,1460,0,0]Ao[1219,13,1220,1220,4023,3093,0]Am[473,13,474,3719,4870,3093,0]Am[473,13,474,4244,1448,3093,0]Al[473,13,474,1117,516,3093,0]Am[473,13,474,1117,4464,3093,0]Am[473,13,474,4131,4457,3093,0]Am[473,13,474,4131,3103,3093,0]Am[473,13,474,4131,3305,3093,0]Am[473,13,474,4131,4456,3093,0]Am[473,13,474,4136,3323,3093,0]Am[473,13,474,4136,4222,3093,0]Aj[473,13,474,0,1470,3093,0]Aj[473,13,474,0,5040,3093,0]Aj[473,13,474,0,1175,3093,0]Aj[473,13,474,0,1453,3093,0]Aj[473,13,474,0,3298,3093,0]Aj[473,13,474,0,3098,3093,0]Aj[473,13,474,0,4925,3093,0]Aj[473,13,474,0,3271,3093,0]Aj[473,13,474,0,2330,3093,0]Aj[473,13,474,0,3284,3093,0]Aj[473,13,474,0,3100,3093,0]Aj[473,13,474,0,4168,3093,0]Aj[473,13,474,0,4556,3093,0]Aj[473,13,474,0,3094,3093,0]Aj[473,13,474,0,4423,3093,0]Aj[473,13,474,0,4040,3093,0]Aj[473,13,474,0,4026,3093,0]Aj[473,13,474,0,3081,3093,0]Aj[473,13,474,0,4553,3093,0]Aj[473,13,474,0,4759,3093,0]Aj[473,13,474,0,4174,3093,0]Aj[473,13,474,0,4177,3093,0]Aj[473,13,474,0,4182,3093,0]Aj[473,13,474,0,4930,3093,0]Ai[473,13,474,0,522,3093,0]Ai[473,13,474,0,132,3093,0]Aj[473,13,474,0,2317,3093,0]Ak[473,13,1724,0,5196,3093,0]Ak[473,13,1806,0,1192,3093,0]Ao[3155,13,3156,1844,1471,3093,0]Ao[3155,13,3156,1844,5041,3093,0]Ao[3155,13,3156,3120,1176,3093,0]Ao[3155,13,3156,3129,4871,3093,0]Ao[3155,13,3156,1393,1454,3093,0]Ao[3155,13,3156,1393,3299,3093,0]Ao[3155,13,3156,1393,3099,3093,0]Ao[3155,13,3156,1393,4869,3093,0]Ao[3155,13,3156,1393,4926,3093,0]Ao[3155,13,3156,1393,3274,3093,0]Ao[3155,13,3156,1408,2331,3093,0]Ao[3155,13,3156,1408,3285,3093,0]Ao[3155,13,3156,1408,3101,3093,0]Ao[3155,13,3156,1143,3104,3093,0]Ao[3155,13,3156,1143,4347,3093,0]Ao[3155,13,3156,1148,3324,3093,0]Ao[3155,13,3156,1148,4039,3093,0]Ao[3155,13,3156,2032,4058,3093,0]Ao[3155,13,3156,2032,4458,3093,0]Ao[3155,13,3156,2032,4172,3093,0]Ao[3155,13,3156,3720,4557,3093,0]Ao[3155,13,3156,3720,3095,3093,0]Ao[3155,13,3156,3720,4424,3093,0]Ao[3155,13,3156,3257,4041,3093,0]Ao[3155,13,3156,1722,4027,3093,0]Ao[3155,13,3156,1383,3082,3093,0]Ao[3155,13,3156,1383,4439,3093,0]Ao[3155,13,3156,1383,3291,3093,0]Ao[3155,13,3156,1383,3313,3093,0]Ao[3155,13,3156,1383,4179,3093,0]Ao[3155,13,3156,1383,4555,3093,0]Ao[3155,13,3156,1383,4761,3093,0]Ao[3155,13,3156,2341,4765,3093,0]Ao[3155,13,3156,2341,4175,3093,0]Ao[3155,13,3156,1787,2303,3093,0]Ao[3155,13,3156,1787,4866,3093,0]Ao[3155,13,3156,1817,4178,3093,0]Ao[3155,13,3156,1817,4183,3093,0]Al[3155,13,3156,0,4462,3093,0]Al[3155,13,3156,0,4931,3093,0]An[3155,13,3156,3156,523,3093,0]An[3155,13,3156,3156,133,3093,0]Ao[3155,13,3156,3156,2318,3093,0]Ao[3155,13,3156,3156,3080,3093,0]Ao[3155,13,1720,1720,4353,3093,0]Ao[3155,13,5082,5082,4978,3093,0]Ao[3155,13,5082,5082,1194,3093,0]Ao[3155,13,1725,1725,5197,3093,0]Al[3155,13,1807,0,1193,3093,0]Ao[2017,13,2018,2018,4184,3093,0]Ai[473,13,474,1117,516,0,0]Aj[473,13,474,1117,4464,0,0]Aj[473,13,474,4131,3303,0,0]Aj[473,13,474,4131,4979,0,0]Aj[473,13,474,4136,3309,0,0]Ah[473,14,3096,0,4035,0,0]Ah[473,14,3096,0,3055,0,0]Ah[473,14,3096,0,3074,0,0]Ah[473,14,3096,0,4207,0,0]Ah[473,14,3096,0,4198,0,0]Ai[3155,14,3097,0,4036,0,0]Ai[3155,14,3097,0,3056,0,0]Ai[3155,14,3097,0,3075,0,0]Ai[3155,14,3097,0,4208,0,0]Ai[3155,14,3097,0,4199,0,0]Ai[3155,14,4057,0,2313,0,0]Ai[3155,14,4057,0,2333,0,0]Ai[3155,14,4057,0,2297,0,0]Ai[3155,14,4057,0,1889,0,0]Ae[1219,7,1220,0,0,0,0]Ah[3155,14,4568,0,512,0,0]Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]Ae[2394,5,2395,0,0,0,0]Af[473,6,474,3156,0,0,0]Ae[3155,6,3156,0,0,0,0]Al[3155,15,1720,1720,1460,0,0]Ag[473,19,474,0,1470,0,0]Ag[473,19,474,0,1453,0,0]Al[3155,19,3156,1844,1471,0,0]Al[3155,19,3156,1393,1454,0,0]Ae[1219,5,1220,0,0,0,0]Ae[1118,5,1714,0,0,0,0]Ae[1118,5,1812,0,0,0,0]Af[473,15,474,0,132,0,0]Ak[3155,15,3156,3156,133,0,0]Ai[473,13,474,0,2330,126,0]Ai[473,13,474,0,3284,126,0]Ai[473,13,474,0,4560,126,0]An[3155,13,3156,1408,2331,126,0]An[3155,13,3156,1408,3285,126,0]An[3155,13,3156,1408,4561,126,0]Ai[473,13,474,0,1470,143,0]Ai[473,13,474,0,1453,143,0]Ai[473,13,474,0,4040,143,0]Ai[473,13,474,0,4177,143,0]Ah[473,13,474,0,522,143,0]Ah[473,13,474,0,132,143,0]Ai[473,13,474,0,2317,143,0]An[3155,13,3156,1844,1471,143,0]An[3155,13,3156,3129,4871,143,0]An[3155,13,3156,1393,1454,143,0]An[3155,13,3156,1143,3104,143,0]An[3155,13,3156,1143,4347,143,0]An[3155,13,3156,1148,3324,143,0]An[3155,13,3156,3257,4041,143,0]An[3155,13,3156,2341,4765,143,0]An[3155,13,3156,1787,4866,143,0]An[3155,13,3156,1817,4178,143,0]Am[3155,13,3156,3156,523,143,0]Am[3155,13,3156,3156,133,143,0]An[3155,13,3156,3156,2318,143,0]Ag[473,13,474,0,2317,0,0]Al[3155,13,3156,3156,2318,0,0]Ai[3155,14,4057,0,1450,0,0]Ai[3155,14,4057,0,3064,0,0]Ag[473,14,474,0,2327,0,0]Al[3155,14,3156,3156,2328,0,0]Al[2017,14,2018,2018,2315,0,0]Al[2017,14,2018,2018,2329,0,0]An[1219,13,1220,1220,140,1182,0]Ao[1219,13,1220,1220,4051,1182,0]Ao[1219,13,1220,1220,4023,1182,0]Ao[1219,13,1407,1407,4459,1182,0]Ao[4064,13,4065,4065,5134,1182,0]Am[473,13,474,4244,5036,1182,0]Am[473,13,474,3719,3276,1182,0]Am[473,13,474,3719,3278,1182,0]Am[473,13,474,3719,1440,1182,0]Am[473,13,474,3719,5043,1182,0]D`[473,13,474,3719,4870,1182,0,\"impl-Debug-for-SendDatagramError\"]Db[473,13,474,3719,4870,1861,0,\"impl-Display-for-SendDatagramError\"]Am[473,13,474,2340,2295,1182,0]Am[473,13,474,2340,4320,1182,0]Ce[473,13,474,4244,1448,1182,0,\"impl-Debug-for-Lagged\"]Cg[473,13,474,4244,1448,1861,0,\"impl-Display-for-Lagged\"]Am[473,13,474,4244,3297,1182,0]Am[473,13,474,4244,4971,1182,0]Am[473,13,474,2372,2299,1182,0]Ci[473,13,474,2372,3306,1182,0,\"impl-Debug-for-RetryError\"]Ck[473,13,474,2372,3306,1861,0,\"impl-Display-for-RetryError\"]Am[473,13,474,2372,4438,1182,0]Al[473,13,474,1117,516,1182,0]Am[473,13,474,1117,4464,1182,0]Am[473,13,474,4131,3303,1182,0]Am[473,13,474,4131,4979,1182,0]Cm[473,13,474,4131,4457,1182,0,\"impl-Debug-for-ReadToEndError\"]Co[473,13,474,4131,4457,1861,0,\"impl-Display-for-ReadToEndError\"]Cj[473,13,474,4131,3103,1861,0,\"impl-Display-for-ReadError\"]Ch[473,13,474,4131,3103,1182,0,\"impl-Debug-for-ReadError\"]Ci[473,13,474,4131,3305,1182,0,\"impl-Debug-for-ResetError\"]Ck[473,13,474,4131,3305,1861,0,\"impl-Display-for-ResetError\"]Co[473,13,474,4131,4456,1861,0,\"impl-Display-for-ReadExactError\"]Cm[473,13,474,4131,4456,1182,0,\"impl-Debug-for-ReadExactError\"]Am[473,13,474,1421,4223,1182,0]Am[473,13,474,1160,4054,1182,0]Am[473,13,474,4136,3309,1182,0]Ci[473,13,474,4136,3323,1182,0,\"impl-Debug-for-WriteError\"]Ck[473,13,474,4136,3323,1861,0,\"impl-Display-for-WriteError\"]Cm[473,13,474,4136,4222,1861,0,\"impl-Display-for-StoppedError\"]Ck[473,13,474,4136,4222,1182,0,\"impl-Debug-for-StoppedError\"]Am[473,13,474,4136,1891,1182,0]Cd[473,13,474,0,1470,1861,0,\"impl-Display-for-VarInt\"]Cb[473,13,474,0,1470,1182,0,\"impl-Debug-for-VarInt\"]Db[473,13,474,0,5040,1861,0,\"impl-Display-for-VarIntBoundsExceeded\"]D`[473,13,474,0,5040,1182,0,\"impl-Debug-for-VarIntBoundsExceeded\"]Aj[473,13,474,0,1175,1182,0]Cb[473,13,474,0,1453,1182,0,\"impl-Debug-for-PathId\"]Cd[473,13,474,0,1453,1861,0,\"impl-Display-for-PathId\"]Aj[473,13,474,0,3298,1182,0]Aj[473,13,474,0,3098,1182,0]D`[473,13,474,0,4925,1861,0,\"impl-Display-for-SetPathStatusError\"]Cn[473,13,474,0,4925,1182,0,\"impl-Debug-for-SetPathStatusError\"]Ch[473,13,474,0,3271,1861,0,\"impl-Display-for-ClosedPath\"]Cf[473,13,474,0,3271,1182,0,\"impl-Debug-for-ClosedPath\"]Aj[473,13,474,0,2330,1182,0]Aj[473,13,474,0,3284,1182,0]Aj[473,13,474,0,3100,1182,0]Aj[473,13,474,0,4560,1182,0]Cj[473,13,474,0,4168,1861,0,\"impl-Display-for-ClosedStream\"]Ch[473,13,474,0,4168,1182,0,\"impl-Debug-for-ClosedStream\"]Cm[473,13,474,0,4556,1861,0,\"impl-Display-for-ConnectionError\"]Ck[473,13,474,0,4556,1182,0,\"impl-Debug-for-ConnectionError\"]Cg[473,13,474,0,3094,1861,0,\"impl-Display-for-PathError\"]Ce[473,13,474,0,3094,1182,0,\"impl-Debug-for-PathError\"]Cl[473,13,474,0,4423,1861,0,\"impl-Display-for-ClosePathError\"]Cj[473,13,474,0,4423,1182,0,\"impl-Debug-for-ClosePathError\"]Aj[473,13,474,0,4570,1182,0]Aj[473,13,474,0,4579,1182,0]Aj[473,13,474,0,4895,1182,0]Aj[473,13,474,0,4910,1182,0]Aj[473,13,474,0,4040,1182,0]Aj[473,13,474,0,4433,1182,0]Aj[473,13,474,0,4216,1182,0]Aj[473,13,474,0,5097,1182,0]Aj[473,13,474,0,4162,1182,0]Ci[473,13,474,0,4026,1861,0,\"impl-Display-for-ConfigError\"]Cg[473,13,474,0,4026,1182,0,\"impl-Debug-for-ConfigError\"]Cg[473,13,474,0,3081,1861,0,\"impl-Display-for-FrameType\"]Ce[473,13,474,0,3081,1182,0,\"impl-Debug-for-FrameType\"]Cm[473,13,474,0,4553,1861,0,\"impl-Display-for-ConnectionClose\"]Ck[473,13,474,0,4553,1182,0,\"impl-Debug-for-ConnectionClose\"]Cn[473,13,474,0,4759,1861,0,\"impl-Display-for-ApplicationClose\"]Cl[473,13,474,0,4759,1182,0,\"impl-Debug-for-ApplicationClose\"]Cj[473,13,474,0,4174,1861,0,\"impl-Display-for-ConnectError\"]Ch[473,13,474,0,4174,1182,0,\"impl-Debug-for-ConnectError\"]Cj[473,13,474,0,4177,1861,0,\"impl-Display-for-ConnectionId\"]Ch[473,13,474,0,4177,1182,0,\"impl-Debug-for-ConnectionId\"]Aj[473,13,474,0,4182,1182,0]Cn[473,13,474,0,4930,1182,0,\"impl-Debug-for-TransportErrorCode\"]D`[473,13,474,0,4930,1861,0,\"impl-Display-for-TransportErrorCode\"]Aj[473,13,474,0,3287,1182,0]Aj[473,13,474,0,4793,1182,0]Ai[473,13,474,0,522,1182,0]C`[473,13,474,0,132,1861,0,\"impl-Display-for-Dir\"]Bn[473,13,474,0,132,1182,0,\"impl-Debug-for-Dir\"]Cd[473,13,474,0,2317,1182,0,\"impl-Debug-for-StreamId\"]Cf[473,13,474,0,2317,1861,0,\"impl-Display-for-StreamId\"]Aj[473,13,474,0,2327,1182,0]Aj[473,13,3714,0,497,1182,0]Ak[473,13,3714,0,3267,1182,0]Ak[473,13,3714,0,1179,1182,0]Ak[473,13,3714,0,4031,1182,0]Ak[473,13,3714,0,1874,1182,0]Ak[473,13,3714,0,4339,1182,0]Ak[473,13,1724,0,5196,1182,0]Ak[473,13,1724,0,4029,1182,0]Ak[473,13,1724,0,4934,1182,0]Cd[473,13,1806,0,1192,1861,0,\"impl-Display-for-Error\"]Cb[473,13,1806,0,1192,1182,0,\"impl-Debug-for-Error\"]Dc[473,13,1806,0,5034,1861,0,\"impl-Display-for-NoInitialCipherSuite\"]Da[473,13,1806,0,5034,1182,0,\"impl-Debug-for-NoInitialCipherSuite\"]Ao[3155,13,3156,3720,3279,1182,0]Ao[3155,13,3156,2341,2296,1182,0]Ci[3155,13,3156,1844,1471,1861,0,\"impl-Display-for-VarInt\"]Cg[3155,13,3156,1844,1471,1182,0,\"impl-Debug-for-VarInt\"]Dg[3155,13,3156,1844,5041,1861,0,\"impl-Display-for-VarIntBoundsExceeded\"]De[3155,13,3156,1844,5041,1182,0,\"impl-Debug-for-VarIntBoundsExceeded\"]Ao[3155,13,3156,3120,1176,1182,0]Db[3155,13,3156,3129,4871,1182,0,\"impl-Debug-for-SendDatagramError\"]Dd[3155,13,3156,3129,4871,1861,0,\"impl-Display-for-SendDatagramError\"]Ci[3155,13,3156,1393,1454,1861,0,\"impl-Display-for-PathId\"]Cg[3155,13,3156,1393,1454,1182,0,\"impl-Debug-for-PathId\"]Ao[3155,13,3156,1393,4212,1182,0]Ao[3155,13,3156,1393,3299,1182,0]Ao[3155,13,3156,1393,3099,1182,0]Ao[3155,13,3156,1393,4869,1182,0]Dc[3155,13,3156,1393,4926,1182,0,\"impl-Debug-for-SetPathStatusError\"]De[3155,13,3156,1393,4926,1861,0,\"impl-Display-for-SetPathStatusError\"]Cm[3155,13,3156,1393,3274,1861,0,\"impl-Display-for-ClosedPath\"]Ck[3155,13,3156,1393,3274,1182,0,\"impl-Debug-for-ClosedPath\"]Ao[3155,13,3156,1408,2331,1182,0]Ao[3155,13,3156,1408,3285,1182,0]Ao[3155,13,3156,1408,3101,1182,0]Ao[3155,13,3156,1408,4561,1182,0]Cl[3155,13,3156,1143,3104,1861,0,\"impl-Display-for-ReadError\"]Cj[3155,13,3156,1143,3104,1182,0,\"impl-Debug-for-ReadError\"]Cn[3155,13,3156,1143,4347,1182,0,\"impl-Debug-for-ReadableError\"]D`[3155,13,3156,1143,4347,1861,0,\"impl-Display-for-ReadableError\"]Ck[3155,13,3156,1148,3324,1182,0,\"impl-Debug-for-WriteError\"]Cm[3155,13,3156,1148,3324,1861,0,\"impl-Display-for-WriteError\"]Cl[3155,13,3156,1148,4039,1182,0,\"impl-Debug-for-FinishError\"]Cn[3155,13,3156,1148,4039,1861,0,\"impl-Display-for-FinishError\"]Ao[3155,13,3156,2032,4058,1182,0]Ao[3155,13,3156,2032,4458,1182,0]Co[3155,13,3156,2032,4172,1861,0,\"impl-Display-for-ClosedStream\"]Cm[3155,13,3156,2032,4172,1182,0,\"impl-Debug-for-ClosedStream\"]Db[3155,13,3156,3720,4557,1861,0,\"impl-Display-for-ConnectionError\"]D`[3155,13,3156,3720,4557,1182,0,\"impl-Debug-for-ConnectionError\"]Cl[3155,13,3156,3720,3095,1861,0,\"impl-Display-for-PathError\"]Cj[3155,13,3156,3720,3095,1182,0,\"impl-Debug-for-PathError\"]Co[3155,13,3156,3720,4424,1182,0,\"impl-Debug-for-ClosePathError\"]Da[3155,13,3156,3720,4424,1861,0,\"impl-Display-for-ClosePathError\"]Dg[3155,13,3156,3720,5129,1182,0,\"impl-Debug-for-MultipathNotNegotiated\"]Di[3155,13,3156,3720,5129,1861,0,\"impl-Display-for-MultipathNotNegotiated\"]Ao[3155,13,3156,3720,1197,1182,0]Ao[3155,13,3156,1139,4571,1182,0]Ao[3155,13,3156,3257,4580,1182,0]Ao[3155,13,3156,3257,4896,1182,0]Ao[3155,13,3156,3257,4911,1182,0]Ao[3155,13,3156,3257,4041,1182,0]Ao[3155,13,3156,1722,4434,1182,0]Ao[3155,13,3156,1722,4218,1182,0]Ao[3155,13,3156,1722,5098,1182,0]Ao[3155,13,3156,1722,4164,1182,0]Cn[3155,13,3156,1722,4027,1861,0,\"impl-Display-for-ConfigError\"]Cl[3155,13,3156,1722,4027,1182,0,\"impl-Debug-for-ConfigError\"]Cl[3155,13,3156,1383,3082,1861,0,\"impl-Display-for-FrameType\"]Cj[3155,13,3156,1383,3082,1182,0,\"impl-Debug-for-FrameType\"]Co[3155,13,3156,1383,4439,1182,0,\"impl-Debug-for-InvalidFrameId\"]Da[3155,13,3156,1383,4439,1861,0,\"impl-Display-for-InvalidFrameId\"]Ck[3155,13,3156,1383,3291,1182,0,\"impl-Debug-for-MaybeFrame\"]Cm[3155,13,3156,1383,3291,1861,0,\"impl-Display-for-MaybeFrame\"]Cm[3155,13,3156,1383,3313,1861,0,\"impl-Display-for-StreamInfo\"]Ck[3155,13,3156,1383,3313,1182,0,\"impl-Debug-for-StreamInfo\"]Co[3155,13,3156,1383,4179,1861,0,\"impl-Display-for-DatagramInfo\"]Cm[3155,13,3156,1383,4179,1182,0,\"impl-Debug-for-DatagramInfo\"]D`[3155,13,3156,1383,4555,1182,0,\"impl-Debug-for-ConnectionClose\"]Db[3155,13,3156,1383,4555,1861,0,\"impl-Display-for-ConnectionClose\"]Da[3155,13,3156,1383,4761,1182,0,\"impl-Debug-for-ApplicationClose\"]Dc[3155,13,3156,1383,4761,1861,0,\"impl-Display-for-ApplicationClose\"]Ci[3155,13,3156,1383,2289,1182,0,\"impl-Debug-for-Datagram\"]Ck[3155,13,3156,1383,2289,1861,0,\"impl-Display-for-Datagram\"]Ao[3155,13,3156,2341,4765,1182,0]Ao[3155,13,3156,2341,2300,1182,0]Cm[3155,13,3156,2341,4175,1182,0,\"impl-Debug-for-ConnectError\"]Co[3155,13,3156,2341,4175,1861,0,\"impl-Display-for-ConnectError\"]Ao[3155,13,3156,2341,4021,1182,0]Ck[3155,13,3156,2341,3307,1182,0,\"impl-Debug-for-RetryError\"]Cm[3155,13,3156,2341,3307,1861,0,\"impl-Display-for-RetryError\"]Ao[3155,13,3156,1787,4342,1182,0]Ao[3155,13,3156,1787,4569,1182,0]Ao[3155,13,3156,1787,5133,1182,0]Ao[3155,13,3156,1787,2303,1182,0]Db[3155,13,3156,1787,4866,1182,0,\"impl-Debug-for-PacketDecodeError\"]Dd[3155,13,3156,1787,4866,1861,0,\"impl-Display-for-PacketDecodeError\"]Ao[3155,13,3156,1817,4559,1182,0]Ao[3155,13,3156,1817,4319,1182,0]Co[3155,13,3156,1817,4178,1861,0,\"impl-Display-for-ConnectionId\"]Cm[3155,13,3156,1817,4178,1182,0,\"impl-Debug-for-ConnectionId\"]Ao[3155,13,3156,1817,4183,1182,0]Ce[3155,13,3156,0,4462,1861,0,\"impl-Display-for-Error\"]Cc[3155,13,3156,0,4462,1182,0,\"impl-Debug-for-Error\"]Cb[3155,13,3156,0,4931,1182,0,\"impl-Debug-for-Code\"]Cd[3155,13,3156,0,4931,1861,0,\"impl-Display-for-Code\"]Ao[3155,13,3156,4360,3288,1182,0]Ao[3155,13,3156,4360,5233,1182,0]Ao[3155,13,3156,4966,4794,1182,0]An[3155,13,3156,3156,523,1182,0]Ce[3155,13,3156,3156,133,1861,0,\"impl-Display-for-Dir\"]Cc[3155,13,3156,3156,133,1182,0,\"impl-Debug-for-Dir\"]Ci[3155,13,3156,3156,2318,1182,0,\"impl-Debug-for-StreamId\"]Ck[3155,13,3156,3156,2318,1861,0,\"impl-Display-for-StreamId\"]Ao[3155,13,3156,3156,2328,1182,0]Cl[3155,13,3156,3156,3080,1861,0,\"impl-Display-for-FourTuple\"]Cj[3155,13,3156,3156,3080,1182,0,\"impl-Debug-for-FourTuple\"]D`[3155,13,1720,1720,4353,1861,0,\"impl-Display-for-UnexpectedEnd\"]Cn[3155,13,1720,1720,4353,1182,0,\"impl-Debug-for-UnexpectedEnd\"]Ao[3155,13,5082,5082,4978,1182,0]Ch[3155,13,5082,5082,1194,1861,0,\"impl-Display-for-Error\"]Cf[3155,13,5082,5082,1194,1182,0,\"impl-Debug-for-Error\"]Ao[3155,13,1725,1725,5197,1182,0]Ao[3155,13,1725,1725,4030,1182,0]Ao[3155,13,1725,1725,4937,1182,0]Ce[3155,13,1807,0,1193,1861,0,\"impl-Display-for-Error\"]Cc[3155,13,1807,0,1193,1182,0,\"impl-Debug-for-Error\"]Dg[3155,13,1807,1807,5035,1861,0,\"impl-Display-for-NoInitialCipherSuite\"]De[3155,13,1807,1807,5035,1182,0,\"impl-Debug-for-NoInitialCipherSuite\"]Am[3155,13,3715,531,498,1182,0]An[3155,13,3715,531,3268,1182,0]Ao[3155,13,3715,1379,1180,1182,0]Ao[3155,13,3715,1379,4032,1182,0]Ao[3155,13,3715,2394,1875,1182,0]Ao[3155,13,3715,2394,4340,1182,0]Cf[3155,13,4825,4825,1195,1182,0,\"impl-Debug-for-Error\"]Ch[3155,13,4825,4825,1195,1861,0,\"impl-Display-for-Error\"]Ao[3155,13,4825,4825,1198,1182,0]An[2017,13,2018,428,4463,1182,0]Ao[2017,13,2018,2018,2315,1182,0]Ao[2017,13,2018,2018,2329,1182,0]Ao[2017,13,2018,2018,4184,1182,0]Aj[473,13,474,4244,5036,0,0]Al[3155,13,3156,1393,4212,0,0]Ag[473,14,474,0,2330,0,0]Al[3155,14,3156,1408,2331,0,0]Al[3155,13,3156,1787,4342,0,0]Al[3155,14,3156,1787,5133,0,0]Ah[3155,14,4568,0,512,0,0]Al[2017,14,2018,2018,2315,0,0]Ag[473,13,474,0,5097,0,0]Al[3155,13,3156,1722,5098,0,0]8Al[1219,13,1407,1407,4459,0,0]Al[4064,13,4065,4065,5134,0,0]Ag[473,13,474,0,4311,0,0]Aj[473,13,474,2340,2295,0,0]Ag[473,13,474,0,3300,0,0]Ag[473,13,474,0,4570,0,0]Ag[473,13,474,0,4433,0,0]Ag[473,13,474,0,4216,0,0]Ag[473,13,474,0,4162,0,0]Ag[473,13,474,0,4177,0,0]Ag[473,13,474,0,4793,0,0]Ag[473,13,474,0,2317,0,0]Ah[473,13,3714,0,1179,0,0]Ah[473,13,3714,0,1874,0,0]Al[3155,13,3156,4589,4312,0,0]Al[3155,13,3156,1139,3301,0,0]Al[3155,13,3156,2341,2296,0,0]Al[3155,13,3156,1787,5260,0,0]Al[3155,13,3156,4360,5228,0,0]Al[3155,13,3156,1139,4571,0,0]Al[3155,13,3156,1722,4434,0,0]Al[3155,13,3156,1722,4218,0,0]Al[3155,13,3156,1722,4164,0,0]Al[3155,13,3156,1787,4342,0,0]Al[3155,13,3156,1817,4178,0,0]Ai[3155,13,3156,0,4462,0,0]Al[3155,13,3156,4360,5233,0,0]Al[3155,13,3156,4966,4794,0,0]Al[3155,13,3156,3156,2318,0,0]Al[3155,13,3156,3156,3080,0,0]Al[3155,13,3715,1379,1180,0,0]Al[3155,13,3715,2394,1875,0,0]Ak[2017,13,2018,428,4463,0,0]Al[1118,13,4278,4278,5182,0,0]Al[1118,13,4278,4278,5183,0,0]Aa[473,3,0,0,0,0,0]Ah[473,13,474,0,522,139,0]Am[3155,13,3156,3156,523,139,0]Ag[473,12,474,0,3316,0,0]Aj[473,13,474,2024,1886,0,0]0Am[473,13,474,1421,4223,1886,0]Aj[473,13,474,0,4351,3316,0]Al[3155,12,3156,1722,3317,0,0]Ao[3155,13,3156,1722,4352,3317,0]Aj[473,13,474,3719,3278,0,0]Ag[473,14,474,0,3100,0,0]Al[3155,13,3156,3720,3279,0,0]Al[3155,14,3156,1408,3101,0,0]Ae[1118,7,1714,0,0,0,0]Ae[1118,7,1812,0,0,0,0]Ac[473,4,474,0,0,0,0]Af[473,5,3714,531,0,0,0]Ag[3155,5,3715,531,0,0,0]Al[1118,15,1119,1119,4970,0,0]Ag[473,15,474,0,4182,0,0]Al[3155,15,3156,1817,4183,0,0]Al[2017,15,2018,2018,4184,0,0]210Ag[473,5,1724,1725,0,0,0]Ae[3155,5,1725,0,0,0,0]Al[3155,15,3156,1787,4569,0,0]Al[3155,15,3156,1383,3291,0,0]Af[473,5,474,1117,0,0,0]Al[3155,15,3156,3720,1197,0,0]Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]Aj[473,15,474,4131,4457,0,0]Af[473,6,474,3156,0,0,0]Ae[3155,6,3156,0,0,0,0]Ag[473,19,474,0,1453,0,0]Al[3155,19,3156,1393,1454,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]Ah[473,14,3096,0,4198,0,0]Ai[3155,14,3097,0,4199,0,0]Al[2017,14,2018,2018,2315,0,0]Aa[532,3,0,0,0,0,0]Ai[3155,14,3156,0,4462,0,0]Ag[473,14,474,0,3100,0,0]Al[3155,14,3156,1408,3101,0,0]Al[3155,14,3156,1383,2289,0,0]Al[473,13,474,3719,2310,500,0]Al[473,13,474,2372,2299,500,0]Ak[473,13,474,1117,516,500,0]Al[473,13,474,1117,4464,500,0]Al[473,13,474,4131,3303,500,0]Al[473,13,474,4136,3309,500,0]An[3155,13,3156,1143,1436,500,0]Am[1219,13,1220,1220,140,507,0]An[1219,13,1220,1220,4051,507,0]An[1219,13,1220,1220,4023,507,0]An[1219,13,1407,1407,4059,507,0]An[1219,13,1407,1407,1212,507,0]An[1219,13,1407,1407,4459,507,0]Ak[532,13,533,533,4024,507,0]An[4064,13,4065,4065,5134,507,0]Al[473,13,474,3719,4584,507,0]Al[473,13,474,3719,2310,507,0]Ai[473,13,474,0,4311,507,0]Al[473,13,474,4244,5036,507,0]Al[473,13,474,1117,2311,507,0]Al[473,13,474,1117,4757,507,0]Al[473,13,474,3719,3276,507,0]Al[473,13,474,3719,3278,507,0]Al[473,13,474,3719,1877,507,0]Al[473,13,474,3719,1449,507,0]Al[473,13,474,3719,3059,507,0]Al[473,13,474,3719,2283,507,0]Al[473,13,474,3719,4206,507,0]Al[473,13,474,3719,4213,507,0]Al[473,13,474,3719,1440,507,0]Al[473,13,474,3719,5043,507,0]Al[473,13,474,3719,4870,507,0]0Al[473,13,474,2340,2295,507,0]Al[473,13,474,2340,4320,507,0]Al[473,13,474,2340,1427,507,0]Al[473,13,474,4244,1448,507,0]Al[473,13,474,4244,3297,507,0]Al[473,13,474,4244,4971,507,0]Al[473,13,474,2372,2299,507,0]Al[473,13,474,2372,3306,507,0]Al[473,13,474,2372,4438,507,0]Ak[473,13,474,1117,516,507,0]Al[473,13,474,1117,4464,507,0]Al[473,13,474,4131,3303,507,0]Al[473,13,474,4131,4979,507,0]Al[473,13,474,4131,4457,507,0]0Dk[473,13,474,4131,3103,507,0,\"impl-From%3CConnectionError%3E-for-ReadError\"]Df[473,13,474,4131,3103,507,0,\"impl-From%3CResetError%3E-for-ReadError\"]Al[473,13,474,4131,3103,507,0]Al[473,13,474,4131,3305,507,0]0Al[473,13,474,4131,4456,507,0]0Al[473,13,474,1421,4223,507,0]Al[473,13,474,1160,4054,507,0]Al[473,13,474,4136,3309,507,0]Dl[473,13,474,4136,3323,507,0,\"impl-From%3CConnectionError%3E-for-WriteError\"]Di[473,13,474,4136,3323,507,0,\"impl-From%3CClosedStream%3E-for-WriteError\"]Al[473,13,474,4136,3323,507,0]Di[473,13,474,4136,3323,507,0,\"impl-From%3CStoppedError%3E-for-WriteError\"]Al[473,13,474,4136,4222,507,0]0Al[473,13,474,4136,1891,507,0]Ai[473,13,474,0,3300,507,0]Ai[473,13,474,0,4351,507,0]Ai[473,13,474,0,4767,507,0]Ai[473,13,474,0,4576,507,0]Ai[473,13,474,0,4196,507,0]Ai[473,13,474,0,4447,507,0]Ci[473,13,474,0,1470,507,0,\"impl-From%3Cu32%3E-for-VarInt\"]Cn[473,13,474,0,1470,507,0,\"impl-From%3CStreamId%3E-for-VarInt\"]Ai[473,13,474,0,1470,507,0]Dh[473,13,474,0,1470,507,0,\"impl-From%3CTransportErrorCode%3E-for-VarInt\"]Ci[473,13,474,0,1470,507,0,\"impl-From%3Cu16%3E-for-VarInt\"]Ch[473,13,474,0,1470,507,0,\"impl-From%3Cu8%3E-for-VarInt\"]Ai[473,13,474,0,5040,507,0]Ai[473,13,474,0,1175,507,0]Ai[473,13,474,0,1453,507,0]0Ai[473,13,474,0,3298,507,0]Ai[473,13,474,0,3098,507,0]Ai[473,13,474,0,4925,507,0]Ai[473,13,474,0,3271,507,0]Ai[473,13,474,0,2330,507,0]Ai[473,13,474,0,3284,507,0]Ai[473,13,474,0,3100,507,0]Ai[473,13,474,0,4560,507,0]Ai[473,13,474,0,4168,507,0]Ai[473,13,474,0,4556,507,0]Dj[473,13,474,0,4556,507,0,\"impl-From%3CCloseReason%3E-for-ConnectionError\"]Dd[473,13,474,0,4556,507,0,\"impl-From%3CClose%3E-for-ConnectionError\"]Dm[473,13,474,0,4556,507,0,\"impl-From%3CTransportError%3E-for-ConnectionError\"]Ai[473,13,474,0,3094,507,0]Ai[473,13,474,0,4423,507,0]Ai[473,13,474,0,4570,507,0]Ai[473,13,474,0,4579,507,0]Ai[473,13,474,0,4895,507,0]Ai[473,13,474,0,4910,507,0]Ai[473,13,474,0,4040,507,0]0Ai[473,13,474,0,4433,507,0]Ai[473,13,474,0,4216,507,0]Ai[473,13,474,0,5097,507,0]Ai[473,13,474,0,4162,507,0]Ai[473,13,474,0,4026,507,0]Dj[473,13,474,0,4026,507,0,\"impl-From%3CTryFromIntError%3E-for-ConfigError\"]Do[473,13,474,0,4026,507,0,\"impl-From%3CVarIntBoundsExceeded%3E-for-ConfigError\"]Ai[473,13,474,0,3081,507,0]Ai[473,13,474,0,4553,507,0]0Ai[473,13,474,0,4759,507,0]Ai[473,13,474,0,4174,507,0]0Ai[473,13,474,0,4177,507,0]Ai[473,13,474,0,4182,507,0]Ai[473,13,474,0,4930,507,0]0Ai[473,13,474,0,3287,507,0]Ai[473,13,474,0,4793,507,0]Ah[473,13,474,0,522,507,0]Ah[473,13,474,0,132,507,0]Ai[473,13,474,0,2317,507,0]0Ai[473,13,474,0,2327,507,0]Ai[473,13,3714,0,497,507,0]Aj[473,13,3714,0,3267,507,0]Aj[473,13,3714,0,1179,507,0]Aj[473,13,3714,0,4031,507,0]Aj[473,13,3714,0,1874,507,0]Aj[473,13,3714,0,4339,507,0]Aj[473,13,3714,0,4857,507,0]Aj[473,13,1724,0,1869,507,0]Ai[473,13,1724,0,510,507,0]Aj[473,13,1724,0,5196,507,0]Aj[473,13,1724,0,4029,507,0]Aj[473,13,1724,0,4934,507,0]Aj[473,13,1806,0,3318,507,0]Aj[473,13,1806,0,4322,507,0]D`[473,13,1806,0,1192,507,0,\"impl-From%3COtherError%3E-for-Error\"]Dd[473,13,1806,0,1192,507,0,\"impl-From%3CPeerMisbehaved%3E-for-Error\"]Df[473,13,1806,0,1192,507,0,\"impl-From%3CInconsistentKeys%3E-for-Error\"]Do[473,13,1806,0,1192,507,0,\"impl-From%3CUnsupportedOperationError%3E-for-Error\"]Df[473,13,1806,0,1192,507,0,\"impl-From%3CCertificateError%3E-for-Error\"]Aj[473,13,1806,0,1192,507,0]Do[473,13,1806,0,1192,507,0,\"impl-From%3CEncryptedClientHelloError%3E-for-Error\"]Df[473,13,1806,0,1192,507,0,\"impl-From%3CPeerIncompatible%3E-for-Error\"]Dm[473,13,1806,0,1192,507,0,\"impl-From%3CCertRevocationListError%3E-for-Error\"]Dd[473,13,1806,0,1192,507,0,\"impl-From%3CInvalidMessage%3E-for-Error\"]De[473,13,1806,0,1192,507,0,\"impl-From%3CSystemTimeError%3E-for-Error\"]De[473,13,1806,0,1192,507,0,\"impl-From%3CGetRandomFailed%3E-for-Error\"]Aj[473,13,1806,0,4789,507,0]Aj[473,13,1806,0,5034,507,0]Aj[473,13,1806,0,4791,507,0]Am[2394,13,2395,2395,131,507,0]An[2394,13,2395,2395,2286,507,0]An[3155,13,3156,4589,4312,507,0]An[3155,13,3156,3129,3072,507,0]An[3155,13,3156,1143,1436,507,0]An[3155,13,3156,2032,1895,507,0]An[3155,13,3156,2032,3304,507,0]An[3155,13,3156,2032,3310,507,0]An[3155,13,3156,3720,3279,507,0]An[3155,13,3156,1139,3301,507,0]An[3155,13,3156,1722,4352,507,0]An[3155,13,3156,2341,2296,507,0]An[3155,13,3156,2341,4318,507,0]An[3155,13,3156,2341,4768,507,0]An[3155,13,3156,2341,4330,507,0]An[3155,13,3156,1787,5260,507,0]An[3155,13,3156,4360,5228,507,0]An[3155,13,3156,1420,4577,507,0]An[3155,13,3156,1420,4197,507,0]An[3155,13,3156,1420,4448,507,0]Dc[3155,13,3156,1844,1471,507,0,\"impl-From%3CStreamId%3E-for-VarInt\"]Cm[3155,13,3156,1844,1471,507,0,\"impl-From%3Cu8%3E-for-VarInt\"]Cn[3155,13,3156,1844,1471,507,0,\"impl-From%3Cu16%3E-for-VarInt\"]An[3155,13,3156,1844,1471,507,0]Cn[3155,13,3156,1844,1471,507,0,\"impl-From%3Cu32%3E-for-VarInt\"]Co[3155,13,3156,1844,1471,507,0,\"impl-From%3CCode%3E-for-VarInt\"]An[3155,13,3156,1844,5041,507,0]An[3155,13,3156,3120,1176,507,0]An[3155,13,3156,3129,4871,507,0]An[3155,13,3156,1393,1454,507,0]0An[3155,13,3156,1393,4212,507,0]An[3155,13,3156,1393,3299,507,0]An[3155,13,3156,1393,3099,507,0]An[3155,13,3156,1393,4869,507,0]An[3155,13,3156,1393,4926,507,0]An[3155,13,3156,1393,3274,507,0]An[3155,13,3156,1408,2331,507,0]An[3155,13,3156,1408,3285,507,0]An[3155,13,3156,1408,3101,507,0]An[3155,13,3156,1408,4561,507,0]An[3155,13,3156,1143,3104,507,0]An[3155,13,3156,1143,4347,507,0]An[3155,13,3156,1148,3324,507,0]An[3155,13,3156,1148,4039,507,0]An[3155,13,3156,2032,4058,507,0]An[3155,13,3156,2032,4458,507,0]An[3155,13,3156,2032,4172,507,0]An[3155,13,3156,3720,4557,507,0]0An[3155,13,3156,3720,3095,507,0]An[3155,13,3156,3720,4424,507,0]An[3155,13,3156,3720,5129,507,0]An[3155,13,3156,3720,1197,507,0]0An[3155,13,3156,1139,4571,507,0]An[3155,13,3156,3257,4580,507,0]An[3155,13,3156,3257,4896,507,0]An[3155,13,3156,3257,4911,507,0]An[3155,13,3156,3257,4041,507,0]0An[3155,13,3156,1722,4434,507,0]An[3155,13,3156,1722,4218,507,0]An[3155,13,3156,1722,5098,507,0]An[3155,13,3156,1722,4164,507,0]An[3155,13,3156,1722,4027,507,0]Ed[3155,13,3156,1722,4027,507,0,\"impl-From%3CVarIntBoundsExceeded%3E-for-ConfigError\"]Do[3155,13,3156,1722,4027,507,0,\"impl-From%3CTryFromIntError%3E-for-ConfigError\"]An[3155,13,3156,1383,3082,507,0]An[3155,13,3156,1383,4439,507,0]An[3155,13,3156,1383,3291,507,0]An[3155,13,3156,1383,3313,507,0]An[3155,13,3156,1383,4179,507,0]An[3155,13,3156,1383,4555,507,0]0An[3155,13,3156,1383,4761,507,0]An[3155,13,3156,1383,2289,507,0]An[3155,13,3156,2341,4765,507,0]An[3155,13,3156,2341,2300,507,0]An[3155,13,3156,2341,4175,507,0]0An[3155,13,3156,2341,4021,507,0]An[3155,13,3156,2341,3307,507,0]An[3155,13,3156,1787,4342,507,0]An[3155,13,3156,1787,4569,507,0]An[3155,13,3156,1787,5133,507,0]An[3155,13,3156,1787,2303,507,0]An[3155,13,3156,1787,4866,507,0]0An[3155,13,3156,1817,4559,507,0]An[3155,13,3156,1817,4319,507,0]An[3155,13,3156,1817,4178,507,0]An[3155,13,3156,1817,4183,507,0]Ak[3155,13,3156,0,4462,507,0]0Ak[3155,13,3156,0,4931,507,0]0An[3155,13,3156,4360,3288,507,0]An[3155,13,3156,4360,5233,507,0]An[3155,13,3156,4966,4794,507,0]Am[3155,13,3156,3156,523,507,0]Am[3155,13,3156,3156,133,507,0]An[3155,13,3156,3156,2318,507,0]0An[3155,13,3156,3156,2328,507,0]An[3155,13,3156,3156,3080,507,0]An[3155,13,1720,1720,4353,507,0]An[3155,13,5082,5082,4978,507,0]An[3155,13,5082,5082,1194,507,0]0An[3155,13,1725,1725,1870,507,0]Am[3155,13,1725,1725,511,507,0]An[3155,13,1725,1725,5197,507,0]An[3155,13,1725,1725,4030,507,0]An[3155,13,1725,1725,4937,507,0]An[3155,13,1807,1807,3319,507,0]An[3155,13,1807,1807,4323,507,0]Dn[3155,13,1807,0,1193,507,0,\"impl-From%3CCertRevocationListError%3E-for-Error\"]Dg[3155,13,1807,0,1193,507,0,\"impl-From%3CCertificateError%3E-for-Error\"]Dg[3155,13,1807,0,1193,507,0,\"impl-From%3CInconsistentKeys%3E-for-Error\"]Da[3155,13,1807,0,1193,507,0,\"impl-From%3COtherError%3E-for-Error\"]Df[3155,13,1807,0,1193,507,0,\"impl-From%3CGetRandomFailed%3E-for-Error\"]E`[3155,13,1807,0,1193,507,0,\"impl-From%3CEncryptedClientHelloError%3E-for-Error\"]E`[3155,13,1807,0,1193,507,0,\"impl-From%3CUnsupportedOperationError%3E-for-Error\"]Dg[3155,13,1807,0,1193,507,0,\"impl-From%3CPeerIncompatible%3E-for-Error\"]De[3155,13,1807,0,1193,507,0,\"impl-From%3CInvalidMessage%3E-for-Error\"]Df[3155,13,1807,0,1193,507,0,\"impl-From%3CSystemTimeError%3E-for-Error\"]De[3155,13,1807,0,1193,507,0,\"impl-From%3CPeerMisbehaved%3E-for-Error\"]Ak[3155,13,1807,0,1193,507,0]An[3155,13,1807,1807,4790,507,0]An[3155,13,1807,1807,5035,507,0]An[3155,13,1807,1807,4792,507,0]Al[3155,13,3715,531,498,507,0]Am[3155,13,3715,531,3268,507,0]An[3155,13,3715,1379,1180,507,0]An[3155,13,3715,1379,4032,507,0]An[3155,13,3715,2394,1875,507,0]An[3155,13,3715,2394,4340,507,0]An[3155,13,3715,3715,4858,507,0]An[3155,13,4825,4825,1195,507,0]An[3155,13,4825,4825,1198,507,0]An[2017,13,2018,2018,3322,507,0]0Am[2017,13,2018,428,4463,507,0]An[2017,13,2018,2018,2315,507,0]An[2017,13,2018,2018,2329,507,0]An[2017,13,2018,2018,4184,507,0]An[1118,13,1119,1119,3069,507,0]An[1118,13,1119,1119,4970,507,0]Am[1118,13,1714,1714,141,507,0]An[1118,13,1409,1409,1213,507,0]An[1118,13,1409,1409,4060,507,0]An[1118,13,1409,1409,4565,507,0]An[1118,13,4278,4278,5182,507,0]An[1118,13,4278,4278,5183,507,0]Am[1118,13,1812,1812,142,507,0]Ai[473,13,474,0,1470,508,0]Ai[473,13,474,0,1453,508,0]Ai[473,13,474,0,4040,508,0]Ai[473,13,474,0,4177,508,0]Ah[473,13,474,0,522,508,0]Ah[473,13,474,0,132,508,0]Ai[473,13,474,0,2317,508,0]An[3155,13,3156,1844,1471,508,0]An[3155,13,3156,3129,4871,508,0]An[3155,13,3156,1393,1454,508,0]An[3155,13,3156,1143,3104,508,0]An[3155,13,3156,1143,4347,508,0]An[3155,13,3156,1148,3324,508,0]An[3155,13,3156,3257,4041,508,0]An[3155,13,3156,2341,4765,508,0]An[3155,13,3156,1787,4866,508,0]An[3155,13,3156,1817,4178,508,0]Am[3155,13,3156,3156,523,508,0]Am[3155,13,3156,3156,133,508,0]An[3155,13,3156,3156,2318,508,0]An[3155,13,3156,3156,3080,508,0]Am[1219,13,1220,1220,140,509,0]An[1219,13,1220,1220,4051,509,0]An[1219,13,1220,1220,4023,509,0]An[1219,13,1407,1407,4059,509,0]An[1219,13,1407,1407,1212,509,0]An[1219,13,1407,1407,4459,509,0]Ak[532,13,533,533,4024,509,0]An[4064,13,4065,4065,5134,509,0]Al[473,13,474,3719,4584,509,0]Al[473,13,474,3719,2310,509,0]Ai[473,13,474,0,4311,509,0]Al[473,13,474,4244,5036,509,0]Al[473,13,474,1117,2311,509,0]Al[473,13,474,1117,4757,509,0]Al[473,13,474,3719,3276,509,0]Al[473,13,474,3719,3278,509,0]Al[473,13,474,3719,1877,509,0]Al[473,13,474,3719,1449,509,0]Al[473,13,474,3719,3059,509,0]Al[473,13,474,3719,2283,509,0]Al[473,13,474,3719,4206,509,0]Al[473,13,474,3719,4213,509,0]Al[473,13,474,3719,1440,509,0]Al[473,13,474,3719,5043,509,0]Al[473,13,474,3719,4870,509,0]Al[473,13,474,2340,2295,509,0]Al[473,13,474,2340,4320,509,0]Al[473,13,474,2340,1427,509,0]Al[473,13,474,4244,1448,509,0]Al[473,13,474,4244,3297,509,0]Al[473,13,474,4244,4971,509,0]Al[473,13,474,2372,2299,509,0]Al[473,13,474,2372,3306,509,0]Al[473,13,474,2372,4438,509,0]Ak[473,13,474,1117,516,509,0]Al[473,13,474,1117,4464,509,0]Al[473,13,474,4131,3303,509,0]Al[473,13,474,4131,4979,509,0]Al[473,13,474,4131,4457,509,0]Al[473,13,474,4131,3103,509,0]Al[473,13,474,4131,3305,509,0]Al[473,13,474,4131,4456,509,0]Al[473,13,474,1421,4223,509,0]Al[473,13,474,1160,4054,509,0]Al[473,13,474,4136,3309,509,0]Al[473,13,474,4136,3323,509,0]Al[473,13,474,4136,4222,509,0]Al[473,13,474,4136,1891,509,0]Ai[473,13,474,0,3300,509,0]Ai[473,13,474,0,4351,509,0]Ai[473,13,474,0,4767,509,0]Ai[473,13,474,0,4576,509,0]Ai[473,13,474,0,4196,509,0]Ai[473,13,474,0,4447,509,0]Ai[473,13,474,0,1470,509,0]Ai[473,13,474,0,5040,509,0]Ai[473,13,474,0,1175,509,0]Ai[473,13,474,0,1453,509,0]Ai[473,13,474,0,3298,509,0]Ai[473,13,474,0,3098,509,0]Ai[473,13,474,0,4925,509,0]Ai[473,13,474,0,3271,509,0]Ai[473,13,474,0,2330,509,0]Ai[473,13,474,0,3284,509,0]Ai[473,13,474,0,3100,509,0]Ai[473,13,474,0,4560,509,0]Ai[473,13,474,0,4168,509,0]Ai[473,13,474,0,4556,509,0]Ai[473,13,474,0,3094,509,0]Ai[473,13,474,0,4423,509,0]Ai[473,13,474,0,4570,509,0]Ai[473,13,474,0,4579,509,0]Ai[473,13,474,0,4895,509,0]Ai[473,13,474,0,4910,509,0]Ai[473,13,474,0,4040,509,0]Ai[473,13,474,0,4433,509,0]Ai[473,13,474,0,4216,509,0]Ai[473,13,474,0,5097,509,0]Ai[473,13,474,0,4162,509,0]Ai[473,13,474,0,4026,509,0]Ai[473,13,474,0,3081,509,0]Ai[473,13,474,0,4553,509,0]Ai[473,13,474,0,4759,509,0]Ai[473,13,474,0,4174,509,0]Ai[473,13,474,0,4177,509,0]Ai[473,13,474,0,4182,509,0]Ai[473,13,474,0,4930,509,0]Ai[473,13,474,0,3287,509,0]Ai[473,13,474,0,4793,509,0]Ah[473,13,474,0,522,509,0]Ah[473,13,474,0,132,509,0]Ai[473,13,474,0,2317,509,0]Ai[473,13,474,0,2327,509,0]Ai[473,13,3714,0,497,509,0]Aj[473,13,3714,0,3267,509,0]Aj[473,13,3714,0,1179,509,0]Aj[473,13,3714,0,4031,509,0]Aj[473,13,3714,0,1874,509,0]Aj[473,13,3714,0,4339,509,0]Aj[473,13,3714,0,4857,509,0]Aj[473,13,1724,0,1869,509,0]Ai[473,13,1724,0,510,509,0]Aj[473,13,1724,0,5196,509,0]Aj[473,13,1724,0,4029,509,0]Aj[473,13,1724,0,4934,509,0]Aj[473,13,1806,0,3318,509,0]Aj[473,13,1806,0,4322,509,0]Aj[473,13,1806,0,1192,509,0]Aj[473,13,1806,0,4789,509,0]Aj[473,13,1806,0,5034,509,0]Aj[473,13,1806,0,4791,509,0]Am[2394,13,2395,2395,131,509,0]An[2394,13,2395,2395,2286,509,0]An[3155,13,3156,4589,4312,509,0]An[3155,13,3156,3129,3072,509,0]An[3155,13,3156,1143,1436,509,0]An[3155,13,3156,2032,1895,509,0]An[3155,13,3156,2032,3304,509,0]An[3155,13,3156,2032,3310,509,0]An[3155,13,3156,3720,3279,509,0]An[3155,13,3156,1139,3301,509,0]An[3155,13,3156,1722,4352,509,0]An[3155,13,3156,2341,2296,509,0]An[3155,13,3156,2341,4318,509,0]An[3155,13,3156,2341,4768,509,0]An[3155,13,3156,2341,4330,509,0]An[3155,13,3156,1787,5260,509,0]An[3155,13,3156,4360,5228,509,0]An[3155,13,3156,1420,4577,509,0]An[3155,13,3156,1420,4197,509,0]An[3155,13,3156,1420,4448,509,0]An[3155,13,3156,1844,1471,509,0]An[3155,13,3156,1844,5041,509,0]An[3155,13,3156,3120,1176,509,0]An[3155,13,3156,3129,4871,509,0]An[3155,13,3156,1393,1454,509,0]An[3155,13,3156,1393,4212,509,0]An[3155,13,3156,1393,3299,509,0]An[3155,13,3156,1393,3099,509,0]An[3155,13,3156,1393,4869,509,0]An[3155,13,3156,1393,4926,509,0]An[3155,13,3156,1393,3274,509,0]An[3155,13,3156,1408,2331,509,0]An[3155,13,3156,1408,3285,509,0]An[3155,13,3156,1408,3101,509,0]An[3155,13,3156,1408,4561,509,0]An[3155,13,3156,1143,3104,509,0]An[3155,13,3156,1143,4347,509,0]An[3155,13,3156,1148,3324,509,0]An[3155,13,3156,1148,4039,509,0]An[3155,13,3156,2032,4058,509,0]An[3155,13,3156,2032,4458,509,0]An[3155,13,3156,2032,4172,509,0]An[3155,13,3156,3720,4557,509,0]An[3155,13,3156,3720,3095,509,0]An[3155,13,3156,3720,4424,509,0]An[3155,13,3156,3720,5129,509,0]An[3155,13,3156,3720,1197,509,0]An[3155,13,3156,1139,4571,509,0]An[3155,13,3156,3257,4580,509,0]An[3155,13,3156,3257,4896,509,0]An[3155,13,3156,3257,4911,509,0]An[3155,13,3156,3257,4041,509,0]An[3155,13,3156,1722,4434,509,0]An[3155,13,3156,1722,4218,509,0]An[3155,13,3156,1722,5098,509,0]An[3155,13,3156,1722,4164,509,0]An[3155,13,3156,1722,4027,509,0]An[3155,13,3156,1383,3082,509,0]An[3155,13,3156,1383,4439,509,0]An[3155,13,3156,1383,3291,509,0]An[3155,13,3156,1383,3313,509,0]An[3155,13,3156,1383,4179,509,0]An[3155,13,3156,1383,4555,509,0]An[3155,13,3156,1383,4761,509,0]An[3155,13,3156,1383,2289,509,0]An[3155,13,3156,2341,4765,509,0]An[3155,13,3156,2341,2300,509,0]An[3155,13,3156,2341,4175,509,0]An[3155,13,3156,2341,4021,509,0]An[3155,13,3156,2341,3307,509,0]An[3155,13,3156,1787,4342,509,0]An[3155,13,3156,1787,4569,509,0]An[3155,13,3156,1787,5133,509,0]An[3155,13,3156,1787,2303,509,0]An[3155,13,3156,1787,4866,509,0]An[3155,13,3156,1817,4559,509,0]An[3155,13,3156,1817,4319,509,0]An[3155,13,3156,1817,4178,509,0]An[3155,13,3156,1817,4183,509,0]Ak[3155,13,3156,0,4462,509,0]Ak[3155,13,3156,0,4931,509,0]An[3155,13,3156,4360,3288,509,0]An[3155,13,3156,4360,5233,509,0]An[3155,13,3156,4966,4794,509,0]Am[3155,13,3156,3156,523,509,0]Am[3155,13,3156,3156,133,509,0]An[3155,13,3156,3156,2318,509,0]An[3155,13,3156,3156,2328,509,0]An[3155,13,3156,3156,3080,509,0]An[3155,13,1720,1720,4353,509,0]An[3155,13,5082,5082,4978,509,0]An[3155,13,5082,5082,1194,509,0]An[3155,13,1725,1725,1870,509,0]Am[3155,13,1725,1725,511,509,0]An[3155,13,1725,1725,5197,509,0]An[3155,13,1725,1725,4030,509,0]An[3155,13,1725,1725,4937,509,0]An[3155,13,1807,1807,3319,509,0]An[3155,13,1807,1807,4323,509,0]Ak[3155,13,1807,0,1193,509,0]An[3155,13,1807,1807,4790,509,0]An[3155,13,1807,1807,5035,509,0]An[3155,13,1807,1807,4792,509,0]Al[3155,13,3715,531,498,509,0]Am[3155,13,3715,531,3268,509,0]An[3155,13,3715,1379,1180,509,0]An[3155,13,3715,1379,4032,509,0]An[3155,13,3715,2394,1875,509,0]An[3155,13,3715,2394,4340,509,0]An[3155,13,3715,3715,4858,509,0]An[3155,13,4825,4825,1195,509,0]An[3155,13,4825,4825,1198,509,0]An[2017,13,2018,2018,3322,509,0]Am[2017,13,2018,428,4463,509,0]An[2017,13,2018,2018,2315,509,0]An[2017,13,2018,2018,2329,509,0]An[2017,13,2018,2018,4184,509,0]An[1118,13,1119,1119,3069,509,0]An[1118,13,1119,1119,4970,509,0]Am[1118,13,1714,1714,141,509,0]An[1118,13,1409,1409,1213,509,0]An[1118,13,1409,1409,4060,509,0]An[1118,13,1409,1409,4565,509,0]An[1118,13,4278,4278,5182,509,0]An[1118,13,4278,4278,5183,509,0]Am[1118,13,1812,1812,142,509,0]Ac[532,7,533,0,0,0,0]Ae[4064,7,4065,0,0,0,0]Ae[4363,7,4364,0,0,0,0]Ae[2394,7,2395,0,0,0,0]Ae[4889,7,4890,0,0,0,0]Al[3155,13,3156,1143,1436,0,0]Ao[3155,13,3156,2341,4330,2301,0]Ah[473,12,1724,0,4859,0,0]Al[3155,13,3156,2032,1895,0,0]Al[3155,12,1725,1725,4860,0,0]Aj[473,13,474,3719,3278,0,0]Ab[1118,3,0,0,0,0,0]Ai[473,13,474,1117,516,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,13,3156,3720,3279,0,0]Al[3155,14,3156,1408,3285,0,0]Aj[473,12,474,2024,3263,0,0]Am[473,13,474,3719,4584,1445,0]Am[473,13,474,3719,2310,1445,0]Am[473,13,474,1117,2311,1445,0]Am[473,13,474,3719,3276,1445,0]Am[473,13,474,3719,1877,1445,0]Am[473,13,474,3719,1449,1445,0]Am[473,13,474,3719,3059,1445,0]Am[473,13,474,3719,2283,1445,0]Am[473,13,474,3719,4206,1445,0]Am[473,13,474,3719,4213,1445,0]Am[473,13,474,2340,1427,1445,0]Am[473,13,474,2372,4438,1445,0]Am[473,13,474,4136,1891,1445,0]?Aj[473,13,474,4131,3303,0,0]Al[3155,13,3156,2032,3304,0,0]Al[3155,13,5082,5082,4978,0,0]Al[3155,13,3156,3129,3072,0,0]Ak[2017,13,2018,428,4463,0,0]Ah[473,12,1724,0,4859,0,0]Al[3155,12,1725,1725,4860,0,0]32Ag[473,13,474,0,5097,0,0]Al[3155,13,3156,1722,5098,0,0]Aj[473,13,474,3719,3278,0,0]Al[3155,13,3156,3720,3279,0,0]Ah[473,12,1724,0,1865,0,0]Al[3155,12,1725,1725,1866,0,0]Al[1219,14,1407,1407,4459,0,0]Ag[473,14,474,0,2327,0,0]Al[3155,14,3156,3156,2328,0,0]Ai[3155,14,4568,0,1209,0,0]Aj[473,13,474,4131,3303,0,0]Aj[473,13,474,4131,4979,0,0]Al[3155,13,3156,2032,3304,0,0]Ag[473,12,474,0,3320,0,0]Aj[473,13,474,0,4447,3320,0]Aj[473,13,474,0,4793,3320,0]Al[3155,12,3156,1420,3321,0,0]Ao[3155,13,3156,1420,4448,3321,0]Ao[3155,13,3156,4966,4794,3321,0]Af[473,5,474,3120,0,0,0]Ah[3155,5,3156,3120,0,0,0]Ag[473,5,3714,1379,0,0,0]Ah[3155,5,3715,1379,0,0,0]Al[1118,15,1119,1119,4970,0,0]Ag[473,6,1806,1382,0,0,0]Ah[3155,6,1807,1382,0,0,0]Ae[3155,6,5082,0,0,0,0]Ae[3155,6,4825,0,0,0,0]Ah[3155,6,3156,3720,0,0,0]1Al[3155,15,3156,1383,3291,0,0]Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]Aj[473,15,474,4131,3103,0,0]Ag[473,15,474,0,4556,0,0]Al[3155,15,3156,1143,3104,0,0]Al[3155,15,3156,3720,4557,0,0]Al[3155,15,3156,1787,4569,0,0]0Ae[1219,5,1407,0,0,0,0]Ae[1118,5,1409,0,0,0,0]Al[1219,15,1220,1220,4051,0,0]Ag[473,13,474,0,4767,0,0]Al[3155,13,3156,2341,4768,0,0]Ab[1219,3,0,0,0,0,0]Ah[473,12,3714,0,4855,0,0]Ak[473,13,3714,0,3267,4855,0]Ak[473,13,3714,0,4031,4855,0]Ak[473,13,3714,0,4339,4855,0]Al[3155,12,3715,3715,4856,0,0]An[3155,13,3715,531,3268,4856,0]Ao[3155,13,3715,1379,4032,4856,0]Ao[3155,13,3715,2394,4340,4856,0]Al[1118,13,1119,1119,4970,0,0]Ag[473,14,474,0,1175,0,0]Ag[473,14,474,0,2330,0,0]Al[3155,14,3156,3120,1176,0,0]Al[3155,14,3156,1408,2331,0,0]Al[3155,14,3156,2341,4021,0,0]An[1219,13,1220,1220,140,1177,0]Ao[1219,13,1220,1220,4051,1177,0]Ao[1219,13,1220,1220,4023,1177,0]Am[473,13,474,3719,3278,1177,0]Am[473,13,474,3719,1440,1177,0]Am[473,13,474,3719,5043,1177,0]Am[473,13,474,3719,4870,1177,0]Am[473,13,474,2340,2295,1177,0]Am[473,13,474,2340,4320,1177,0]Am[473,13,474,4244,1448,1177,0]Al[473,13,474,1117,516,1177,0]Am[473,13,474,1117,4464,1177,0]Am[473,13,474,4131,4457,1177,0]Am[473,13,474,4131,3103,1177,0]Am[473,13,474,4131,3305,1177,0]Am[473,13,474,4131,4456,1177,0]Am[473,13,474,4136,3323,1177,0]Am[473,13,474,4136,4222,1177,0]Aj[473,13,474,0,1470,1177,0]Aj[473,13,474,0,5040,1177,0]Aj[473,13,474,0,1453,1177,0]Aj[473,13,474,0,3298,1177,0]Aj[473,13,474,0,3098,1177,0]Aj[473,13,474,0,4925,1177,0]Aj[473,13,474,0,3271,1177,0]Aj[473,13,474,0,2330,1177,0]Aj[473,13,474,0,3284,1177,0]Aj[473,13,474,0,3100,1177,0]Aj[473,13,474,0,4560,1177,0]Aj[473,13,474,0,4168,1177,0]Aj[473,13,474,0,4556,1177,0]Aj[473,13,474,0,3094,1177,0]Aj[473,13,474,0,4423,1177,0]Aj[473,13,474,0,4579,1177,0]Aj[473,13,474,0,4895,1177,0]Aj[473,13,474,0,4910,1177,0]Aj[473,13,474,0,4040,1177,0]Aj[473,13,474,0,4433,1177,0]Aj[473,13,474,0,4216,1177,0]Aj[473,13,474,0,5097,1177,0]Aj[473,13,474,0,4162,1177,0]Aj[473,13,474,0,4026,1177,0]Aj[473,13,474,0,3081,1177,0]Aj[473,13,474,0,4553,1177,0]Aj[473,13,474,0,4759,1177,0]Aj[473,13,474,0,4174,1177,0]Aj[473,13,474,0,4177,1177,0]Aj[473,13,474,0,4182,1177,0]Aj[473,13,474,0,4930,1177,0]Aj[473,13,474,0,3287,1177,0]Ai[473,13,474,0,522,1177,0]Ai[473,13,474,0,132,1177,0]Aj[473,13,474,0,2317,1177,0]Aj[473,13,3714,0,497,1177,0]Ak[473,13,3714,0,3267,1177,0]Ak[473,13,3714,0,1179,1177,0]Ak[473,13,3714,0,4031,1177,0]Ak[473,13,3714,0,1874,1177,0]Ak[473,13,3714,0,4339,1177,0]Ak[473,13,1806,0,1192,1177,0]Ak[473,13,1806,0,4789,1177,0]Ak[473,13,1806,0,5034,1177,0]Ak[473,13,1806,0,4791,1177,0]Ao[3155,13,3156,1844,1471,1177,0]Ao[3155,13,3156,1844,5041,1177,0]Ao[3155,13,3156,3129,4871,1177,0]Ao[3155,13,3156,1393,1454,1177,0]Ao[3155,13,3156,1393,4212,1177,0]Ao[3155,13,3156,1393,3299,1177,0]Ao[3155,13,3156,1393,3099,1177,0]Ao[3155,13,3156,1393,4869,1177,0]Ao[3155,13,3156,1393,4926,1177,0]Ao[3155,13,3156,1393,3274,1177,0]Ao[3155,13,3156,1408,2331,1177,0]Ao[3155,13,3156,1408,3285,1177,0]Ao[3155,13,3156,1408,3101,1177,0]Ao[3155,13,3156,1408,4561,1177,0]Ao[3155,13,3156,1143,3104,1177,0]Ao[3155,13,3156,1143,4347,1177,0]Ao[3155,13,3156,1148,3324,1177,0]Ao[3155,13,3156,1148,4039,1177,0]Ao[3155,13,3156,2032,4458,1177,0]Ao[3155,13,3156,2032,4172,1177,0]Ao[3155,13,3156,3720,4557,1177,0]Ao[3155,13,3156,3720,3095,1177,0]Ao[3155,13,3156,3720,4424,1177,0]Ao[3155,13,3156,3720,5129,1177,0]Ao[3155,13,3156,3257,4580,1177,0]Ao[3155,13,3156,3257,4896,1177,0]Ao[3155,13,3156,3257,4911,1177,0]Ao[3155,13,3156,3257,4041,1177,0]Ao[3155,13,3156,1722,4434,1177,0]Ao[3155,13,3156,1722,4218,1177,0]Ao[3155,13,3156,1722,5098,1177,0]Ao[3155,13,3156,1722,4164,1177,0]Ao[3155,13,3156,1722,4027,1177,0]Ao[3155,13,3156,1383,3082,1177,0]Ao[3155,13,3156,1383,3291,1177,0]Ao[3155,13,3156,1383,3313,1177,0]Ao[3155,13,3156,1383,4179,1177,0]Ao[3155,13,3156,1383,4555,1177,0]Ao[3155,13,3156,1383,4761,1177,0]Ao[3155,13,3156,1383,2289,1177,0]Ao[3155,13,3156,2341,4765,1177,0]Ao[3155,13,3156,2341,4175,1177,0]Ao[3155,13,3156,1787,4569,1177,0]Ao[3155,13,3156,1787,5133,1177,0]Ao[3155,13,3156,1787,2303,1177,0]Ao[3155,13,3156,1787,4866,1177,0]Ao[3155,13,3156,1817,4178,1177,0]Ao[3155,13,3156,1817,4183,1177,0]Al[3155,13,3156,0,4462,1177,0]Al[3155,13,3156,0,4931,1177,0]Ao[3155,13,3156,4360,3288,1177,0]Ao[3155,13,3156,4360,5233,1177,0]An[3155,13,3156,3156,523,1177,0]An[3155,13,3156,3156,133,1177,0]Ao[3155,13,3156,3156,2318,1177,0]Ao[3155,13,3156,3156,3080,1177,0]Ao[3155,13,1720,1720,4353,1177,0]Ao[3155,13,5082,5082,4978,1177,0]Ao[3155,13,5082,5082,1194,1177,0]Al[3155,13,1807,0,1193,1177,0]Ao[3155,13,1807,1807,4790,1177,0]Ao[3155,13,1807,1807,5035,1177,0]Ao[3155,13,1807,1807,4792,1177,0]Am[3155,13,3715,531,498,1177,0]An[3155,13,3715,531,3268,1177,0]Ao[3155,13,3715,1379,1180,1177,0]Ao[3155,13,3715,1379,4032,1177,0]Ao[3155,13,3715,2394,1875,1177,0]Ao[3155,13,3715,2394,4340,1177,0]Ao[3155,13,4825,4825,1198,1177,0]Ao[2017,13,2018,2018,2315,1177,0]Ao[2017,13,2018,2018,2329,1177,0]Ao[2017,13,2018,2018,4184,1177,0]Ao[1118,13,1119,1119,4970,1177,0]Ao[1118,13,1409,1409,4565,1177,0]Ao[1118,13,4278,4278,5182,1177,0]Ao[1118,13,4278,4278,5183,1177,0]Aj[473,13,474,3719,3278,0,0]Aj[473,13,474,2340,2295,0,0]Ai[473,13,474,1117,516,0,0]Al[3155,13,3156,3720,3279,0,0]Aj[473,13,474,0,4177,1183,0]Ao[3155,13,3156,1817,4178,1183,0]Ai[3155,14,3156,0,4462,0,0]Ag[473,13,474,0,2317,0,0]Al[3155,13,3156,3156,2318,0,0]Ag[473,13,474,0,4182,0,0]Al[3155,13,3156,1817,4183,0,0]Ah[473,14,1724,0,1869,0,0]Al[3155,14,1725,1725,1870,0,0]Al[3155,12,3156,1787,4901,0,0]Ao[3155,13,3156,1787,5260,4901,0];Al[1219,13,1407,1407,1212,0,0]Ai[532,13,533,533,4024,0,0]Al[1118,13,1409,1409,1213,0,0]Aj[473,12,474,2024,3263,0,0]Aj[473,13,474,4136,3309,0,0]Al[3155,13,3156,2032,3310,0,0]Aj[473,13,474,2372,2299,0,0]Al[3155,13,3156,2341,2296,0,0]Aj[473,12,474,2024,1886,0,0]Am[473,13,474,1421,4223,1886,0]Am[473,13,474,1160,4054,1886,0]Ae[1219,2,1220,0,0,0,0]Ak[1219,14,1220,1220,140,0,0]Aj[473,13,474,3719,3278,0,0]Aj[473,13,474,2340,2295,0,0]Ai[473,13,474,1117,516,0,0]Aj[473,14,474,3719,1440,0,0]Al[3155,13,3156,3720,3279,0,0]Ae[1118,2,1119,0,0,0,0]Ag[473,13,474,0,3300,0,0]Al[3155,13,3156,1139,3301,0,0]Aj[473,13,474,4136,3309,0,0]Al[3155,13,3156,2032,3310,0,0]Al[3155,13,5082,5082,4978,0,0]Af[473,5,474,2340,0,0,0]Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]Al[1219,15,1220,1220,4023,0,0]0Ag[473,15,474,0,3298,0,0]Al[3155,15,3156,1393,3299,0,0]Ah[3155,5,3156,1143,0,0,0]Af[473,15,474,0,522,0,0]Al[2394,15,2395,2395,2286,0,0]Ak[3155,15,3156,3156,523,0,0]Af[473,5,474,3719,0,0,0]Al[3155,15,4825,4825,1195,0,0]:9Af[473,5,474,4244,0,0,0]2Al[3155,15,3156,2032,4058,0,0]Af[473,5,474,1393,0,0,0]Ah[3155,5,3156,1393,0,0,0]Ae[3155,8,1720,0,0,0,0]987?Al[3155,15,3156,3720,1197,0,0]?Af[473,5,474,1844,0,0,0]Ah[3155,5,3156,1844,0,0,0]Aj[473,13,474,2340,2295,0,0]Aj[473,13,474,2372,2299,0,0]Al[3155,13,3156,2032,1895,0,0]Al[3155,13,3156,2341,2296,0,0]An[1219,13,1220,1220,140,1434,0]Ao[1219,13,1220,1220,4051,1434,0]Ao[1219,13,1220,1220,4023,1434,0]Ao[1219,13,1407,1407,4059,1434,0]Ao[1219,13,1407,1407,1212,1434,0]Ao[1219,13,1407,1407,4459,1434,0]Al[532,13,533,533,4024,1434,0]Ao[4064,13,4065,4065,5134,1434,0]Am[473,13,474,3719,4584,1434,0]Am[473,13,474,3719,2310,1434,0]Aj[473,13,474,0,4311,1434,0]Am[473,13,474,4244,5036,1434,0]Am[473,13,474,1117,2311,1434,0]Am[473,13,474,1117,4757,1434,0]Am[473,13,474,3719,3276,1434,0]Am[473,13,474,3719,3278,1434,0]Am[473,13,474,3719,1877,1434,0]Am[473,13,474,3719,1449,1434,0]Am[473,13,474,3719,3059,1434,0]Am[473,13,474,3719,2283,1434,0]Am[473,13,474,3719,4206,1434,0]Am[473,13,474,3719,4213,1434,0]Am[473,13,474,3719,1440,1434,0]Am[473,13,474,3719,5043,1434,0]Am[473,13,474,3719,4870,1434,0]Am[473,13,474,2340,2295,1434,0]Am[473,13,474,2340,4320,1434,0]Am[473,13,474,2340,1427,1434,0]Am[473,13,474,4244,1448,1434,0]Am[473,13,474,4244,3297,1434,0]Am[473,13,474,4244,4971,1434,0]Am[473,13,474,2372,2299,1434,0]Am[473,13,474,2372,3306,1434,0]Am[473,13,474,2372,4438,1434,0]Al[473,13,474,1117,516,1434,0]Am[473,13,474,1117,4464,1434,0]Am[473,13,474,4131,3303,1434,0]Am[473,13,474,4131,4979,1434,0]Am[473,13,474,4131,4457,1434,0]Am[473,13,474,4131,3103,1434,0]Am[473,13,474,4131,3305,1434,0]Am[473,13,474,4131,4456,1434,0]Am[473,13,474,1421,4223,1434,0]Am[473,13,474,1160,4054,1434,0]Am[473,13,474,4136,3309,1434,0]Am[473,13,474,4136,3323,1434,0]Am[473,13,474,4136,4222,1434,0]Am[473,13,474,4136,1891,1434,0]Aj[473,13,474,0,3300,1434,0]Aj[473,13,474,0,4351,1434,0]Aj[473,13,474,0,4767,1434,0]Aj[473,13,474,0,4576,1434,0]Aj[473,13,474,0,4196,1434,0]Aj[473,13,474,0,4447,1434,0]Aj[473,13,474,0,1470,1434,0]Aj[473,13,474,0,5040,1434,0]Aj[473,13,474,0,1175,1434,0]Aj[473,13,474,0,1453,1434,0]Aj[473,13,474,0,3298,1434,0]Aj[473,13,474,0,3098,1434,0]Aj[473,13,474,0,4925,1434,0]Aj[473,13,474,0,3271,1434,0]Aj[473,13,474,0,2330,1434,0]Aj[473,13,474,0,3284,1434,0]Aj[473,13,474,0,3100,1434,0]Aj[473,13,474,0,4560,1434,0]Aj[473,13,474,0,4168,1434,0]Aj[473,13,474,0,4556,1434,0]Aj[473,13,474,0,3094,1434,0]Aj[473,13,474,0,4423,1434,0]Aj[473,13,474,0,4570,1434,0]Aj[473,13,474,0,4579,1434,0]Aj[473,13,474,0,4895,1434,0]Aj[473,13,474,0,4910,1434,0]Aj[473,13,474,0,4040,1434,0]Aj[473,13,474,0,4433,1434,0]Aj[473,13,474,0,4216,1434,0]Aj[473,13,474,0,5097,1434,0]Aj[473,13,474,0,4162,1434,0]Aj[473,13,474,0,4026,1434,0]Aj[473,13,474,0,3081,1434,0]Aj[473,13,474,0,4553,1434,0]Aj[473,13,474,0,4759,1434,0]Aj[473,13,474,0,4174,1434,0]Aj[473,13,474,0,4177,1434,0]Aj[473,13,474,0,4182,1434,0]Aj[473,13,474,0,4930,1434,0]Aj[473,13,474,0,3287,1434,0]Aj[473,13,474,0,4793,1434,0]Ai[473,13,474,0,522,1434,0]Ai[473,13,474,0,132,1434,0]Aj[473,13,474,0,2317,1434,0]Aj[473,13,474,0,2327,1434,0]Aj[473,13,3714,0,497,1434,0]Ak[473,13,3714,0,3267,1434,0]Ak[473,13,3714,0,1179,1434,0]Ak[473,13,3714,0,4031,1434,0]Ak[473,13,3714,0,1874,1434,0]Ak[473,13,3714,0,4339,1434,0]Ak[473,13,3714,0,4857,1434,0]Ak[473,13,1724,0,1869,1434,0]Aj[473,13,1724,0,510,1434,0]Ak[473,13,1724,0,5196,1434,0]Ak[473,13,1724,0,4029,1434,0]Ak[473,13,1724,0,4934,1434,0]Ak[473,13,1806,0,3318,1434,0]Ak[473,13,1806,0,4322,1434,0]Ak[473,13,1806,0,1192,1434,0]Ak[473,13,1806,0,4789,1434,0]Ak[473,13,1806,0,5034,1434,0]Ak[473,13,1806,0,4791,1434,0]An[2394,13,2395,2395,131,1434,0]Ao[2394,13,2395,2395,2286,1434,0]Ao[3155,13,3156,4589,4312,1434,0]Ao[3155,13,3156,3129,3072,1434,0]Ao[3155,13,3156,1143,1436,1434,0]Ao[3155,13,3156,2032,1895,1434,0]Ao[3155,13,3156,2032,3304,1434,0]Ao[3155,13,3156,2032,3310,1434,0]Ao[3155,13,3156,3720,3279,1434,0]Ao[3155,13,3156,1139,3301,1434,0]Ao[3155,13,3156,1722,4352,1434,0]Ao[3155,13,3156,2341,2296,1434,0]Ao[3155,13,3156,2341,4318,1434,0]Ao[3155,13,3156,2341,4768,1434,0]Ao[3155,13,3156,2341,4330,1434,0]Ao[3155,13,3156,1787,5260,1434,0]Ao[3155,13,3156,4360,5228,1434,0]Ao[3155,13,3156,1420,4577,1434,0]Ao[3155,13,3156,1420,4197,1434,0]Ao[3155,13,3156,1420,4448,1434,0]Ao[3155,13,3156,1844,1471,1434,0]Ao[3155,13,3156,1844,5041,1434,0]Ao[3155,13,3156,3120,1176,1434,0]Ao[3155,13,3156,3129,4871,1434,0]Ao[3155,13,3156,1393,1454,1434,0]Ao[3155,13,3156,1393,4212,1434,0]Ao[3155,13,3156,1393,3299,1434,0]Ao[3155,13,3156,1393,3099,1434,0]Ao[3155,13,3156,1393,4869,1434,0]Ao[3155,13,3156,1393,4926,1434,0]Ao[3155,13,3156,1393,3274,1434,0]Ao[3155,13,3156,1408,2331,1434,0]Ao[3155,13,3156,1408,3285,1434,0]Ao[3155,13,3156,1408,3101,1434,0]Ao[3155,13,3156,1408,4561,1434,0]Ao[3155,13,3156,1143,3104,1434,0]Ao[3155,13,3156,1143,4347,1434,0]Ao[3155,13,3156,1148,3324,1434,0]Ao[3155,13,3156,1148,4039,1434,0]Ao[3155,13,3156,2032,4058,1434,0]Ao[3155,13,3156,2032,4458,1434,0]Ao[3155,13,3156,2032,4172,1434,0]Ao[3155,13,3156,3720,4557,1434,0]Ao[3155,13,3156,3720,3095,1434,0]Ao[3155,13,3156,3720,4424,1434,0]Ao[3155,13,3156,3720,5129,1434,0]Ao[3155,13,3156,3720,1197,1434,0]Ao[3155,13,3156,1139,4571,1434,0]Ao[3155,13,3156,3257,4580,1434,0]Ao[3155,13,3156,3257,4896,1434,0]Ao[3155,13,3156,3257,4911,1434,0]Ao[3155,13,3156,3257,4041,1434,0]Ao[3155,13,3156,1722,4434,1434,0]Ao[3155,13,3156,1722,4218,1434,0]Ao[3155,13,3156,1722,5098,1434,0]Ao[3155,13,3156,1722,4164,1434,0]Ao[3155,13,3156,1722,4027,1434,0]Ao[3155,13,3156,1383,3082,1434,0]Ao[3155,13,3156,1383,4439,1434,0]Ao[3155,13,3156,1383,3291,1434,0]Ao[3155,13,3156,1383,3313,1434,0]Ao[3155,13,3156,1383,4179,1434,0]Ao[3155,13,3156,1383,4555,1434,0]Ao[3155,13,3156,1383,4761,1434,0]Ao[3155,13,3156,1383,2289,1434,0]Ao[3155,13,3156,2341,4765,1434,0]Ao[3155,13,3156,2341,2300,1434,0]Ao[3155,13,3156,2341,4175,1434,0]Ao[3155,13,3156,2341,4021,1434,0]Ao[3155,13,3156,2341,3307,1434,0]Ao[3155,13,3156,1787,4342,1434,0]Ao[3155,13,3156,1787,4569,1434,0]Ao[3155,13,3156,1787,5133,1434,0]Ao[3155,13,3156,1787,2303,1434,0]Ao[3155,13,3156,1787,4866,1434,0]Ao[3155,13,3156,1817,4559,1434,0]Ao[3155,13,3156,1817,4319,1434,0]Ao[3155,13,3156,1817,4178,1434,0]Ao[3155,13,3156,1817,4183,1434,0]Al[3155,13,3156,0,4462,1434,0]Al[3155,13,3156,0,4931,1434,0]Ao[3155,13,3156,4360,3288,1434,0]Ao[3155,13,3156,4360,5233,1434,0]Ao[3155,13,3156,4966,4794,1434,0]An[3155,13,3156,3156,523,1434,0]An[3155,13,3156,3156,133,1434,0]Ao[3155,13,3156,3156,2318,1434,0]Ao[3155,13,3156,3156,2328,1434,0]Ao[3155,13,3156,3156,3080,1434,0]Ao[3155,13,1720,1720,4353,1434,0]Ao[3155,13,5082,5082,4978,1434,0]Ao[3155,13,5082,5082,1194,1434,0]Ao[3155,13,1725,1725,1870,1434,0]An[3155,13,1725,1725,511,1434,0]Ao[3155,13,1725,1725,5197,1434,0]Ao[3155,13,1725,1725,4030,1434,0]Ao[3155,13,1725,1725,4937,1434,0]Ao[3155,13,1807,1807,3319,1434,0]Ao[3155,13,1807,1807,4323,1434,0]Al[3155,13,1807,0,1193,1434,0]Ao[3155,13,1807,1807,4790,1434,0]Ao[3155,13,1807,1807,5035,1434,0]Ao[3155,13,1807,1807,4792,1434,0]Am[3155,13,3715,531,498,1434,0]An[3155,13,3715,531,3268,1434,0]Ao[3155,13,3715,1379,1180,1434,0]Ao[3155,13,3715,1379,4032,1434,0]Ao[3155,13,3715,2394,1875,1434,0]Ao[3155,13,3715,2394,4340,1434,0]Ao[3155,13,3715,3715,4858,1434,0]Ao[3155,13,4825,4825,1195,1434,0]Ao[3155,13,4825,4825,1198,1434,0]Ao[2017,13,2018,2018,3322,1434,0]An[2017,13,2018,428,4463,1434,0]Ao[2017,13,2018,2018,2315,1434,0]Ao[2017,13,2018,2018,2329,1434,0]Ao[2017,13,2018,2018,4184,1434,0]Ao[1118,13,1119,1119,3069,1434,0]Ao[1118,13,1119,1119,4970,1434,0]An[1118,13,1714,1714,141,1434,0]Ao[1118,13,1409,1409,1213,1434,0]Ao[1118,13,1409,1409,4060,1434,0]Ao[1118,13,1409,1409,4565,1434,0]Ao[1118,13,4278,4278,5182,1434,0]Ao[1118,13,4278,4278,5183,1434,0]An[1118,13,1812,1812,142,1434,0]Ak[1219,14,1220,1220,140,0,0]Ac[532,7,533,0,0,0,0]Aj[473,13,474,2340,2295,0,0]Ae[1118,2,1119,0,0,0,0]Ae[4889,7,4890,0,0,0,0]Aj[473,13,474,3719,3278,0,0]Ae[3155,2,3156,0,0,0,0]Al[3155,13,3156,2341,2296,0,0]Af[473,2,474,3156,0,0,0]Ag[473,13,474,0,4930,0,0]Ag[473,14,474,0,3284,0,0]Ag[473,14,474,0,4216,0,0]5Ai[3155,13,3156,0,4931,0,0]Al[3155,14,3156,1408,3285,0,0]Al[3155,14,3156,1722,4218,0,0]Ai[3155,14,3156,0,4462,0,0]Aj[473,13,474,0,1470,3073,0]Aj[473,13,474,0,1453,3073,0]Aj[473,13,474,0,3081,3073,0]Aj[473,13,474,0,4930,3073,0]Aj[473,13,474,0,2317,3073,0]Ao[3155,13,3156,1844,1471,3073,0]Ao[3155,13,3156,1393,1454,3073,0]Ao[3155,13,3156,1383,3082,3073,0]Ao[3155,13,3156,1383,3291,3073,0]Al[3155,13,3156,1787,4569,0,0]Al[3155,13,3156,0,4931,3073,0]Ao[3155,13,3156,3156,2318,3073,0]Al[3155,12,1720,1720,3073,0,0]Al[2017,14,2018,2018,2315,0,0]Aj[473,13,474,0,1470,3078,0]Aj[473,13,474,0,1453,3078,0]Aj[473,13,474,0,3081,3078,0]Aj[473,13,474,0,4930,3078,0]Aj[473,13,474,0,2317,3078,0]Ao[3155,13,3156,1844,1471,3078,0]Ao[3155,13,3156,1393,1454,3078,0]Ao[3155,13,3156,1383,3082,3078,0]Ao[3155,13,3156,1383,3291,3078,0]Ao[3155,13,3156,1383,2289,3078,0]Al[3155,13,3156,0,4931,3078,0]Ao[3155,13,3156,3156,2318,3078,0]Al[3155,12,1720,1720,3078,0,0]Aj[473,13,474,4136,3309,0,0]Al[3155,13,3156,2032,3310,0,0]Al[1118,13,1409,1409,4060,0,0]Al[3155,13,3156,2341,2296,0,0]Ag[473,14,1724,0,510,0,0]Ak[3155,14,1725,1725,511,0,0]Aj[473,13,474,2372,2299,0,0]3Ag[473,12,474,0,3320,0,0]Aj[473,13,474,0,4447,3320,0]Aj[473,13,474,0,4793,3320,0]Al[3155,12,3156,1420,3321,0,0]Ao[3155,13,3156,1420,4448,3321,0]Ao[3155,13,3156,4966,4794,3321,0]Al[1118,14,1119,1119,3069,0,0]Ag[473,14,474,0,1175,0,0]Al[3155,14,3156,3120,1176,0,0]Ah[473,13,3714,0,3280,0,0]Aj[473,13,3714,0,497,3280,0]Ak[473,13,3714,0,1179,3280,0]Ak[473,13,3714,0,1874,3280,0]Al[3155,13,3715,3715,3281,0,0]Am[3155,13,3715,531,498,3281,0]Ao[3155,13,3715,1379,1180,3281,0]Ao[3155,13,3715,2394,1875,3281,0]Ag[473,14,1724,0,510,0,0]Ak[3155,14,1725,1725,511,0,0]Ai[3155,14,4568,0,4799,0,0]Aj[473,14,474,3719,1440,0,0]Ag[473,14,474,0,4553,0,0]Ag[473,14,474,0,4759,0,0]Ah[473,14,3096,0,3055,0,0]Al[3155,14,3156,1383,4555,0,0]Al[3155,14,3156,1383,4761,0,0]Ai[3155,14,3156,0,4462,0,0]Ai[3155,14,1196,0,4426,0,0]Ai[3155,14,3097,0,3056,0,0]Aj[473,13,474,2340,2295,0,0]Aj[473,13,474,2372,2299,0,0]Al[3155,13,3156,2341,2296,0,0]Ah[473,14,1724,0,1869,0,0]Al[3155,13,3156,3156,3080,0,0]Al[3155,14,1725,1725,1870,0,0]Ac[473,4,474,0,0,0,0]Ag[473,2,1724,1725,0,0,0]Ae[3155,4,3156,0,0,0,0]Ae[3155,2,1725,0,0,0,0]Ac[532,7,533,0,0,0,0]:Ae[1118,2,1119,0,0,0,0]Ae[4889,7,4890,0,0,0,0]Am[473,13,474,3719,4870,1185,0]Am[473,13,474,4131,4457,1185,0]Am[473,13,474,4131,3103,1185,0]Am[473,13,474,4131,3305,1185,0]Am[473,13,474,4131,4456,1185,0]Am[473,13,474,4136,3323,1185,0]Am[473,13,474,4136,4222,1185,0]Aj[473,13,474,0,4556,1185,0]Ao[3155,13,3156,3720,4557,1185,0]Ag[473,14,474,0,2327,0,0]Al[3155,14,3156,3156,2328,0,0]Al[2017,14,2018,2018,2329,0,0]Ai[473,13,474,1117,516,0,0]Ah[473,14,3096,0,4207,0,0]Ai[3155,14,3097,0,4208,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]Al[2017,14,2018,2018,2315,0,0]Ag[473,14,474,0,3100,0,0]Ag[473,14,474,0,4560,0,0]Al[3155,14,3156,1408,3101,0,0]Al[3155,14,3156,1408,4561,0,0]3210Ah[473,12,1724,0,1865,0,0]Al[3155,12,1725,1725,1866,0,0]Ah[473,12,3714,0,3280,0,0]Aj[473,13,3714,0,497,3280,0]Ak[473,13,3714,0,1179,3280,0]Ak[473,13,3714,0,1874,3280,0]Al[3155,12,3715,3715,3281,0,0]Am[3155,13,3715,531,498,3281,0]Ao[3155,13,3715,1379,1180,3281,0]Ao[3155,13,3715,2394,1875,3281,0]Al[3155,15,3156,3129,4871,0,0]Al[3155,15,3156,1143,3104,0,0]Al[3155,15,3156,1148,3324,0,0]Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]Ah[473,10,1724,1725,0,0,0]Af[3155,10,1725,0,0,0,0]Al[3155,15,3156,1787,4569,0,0]Ag[473,5,1724,1725,0,0,0]Ae[3155,5,1725,0,0,0,0]Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]Ag[473,5,3714,2394,0,0,0]Ah[3155,5,3715,2394,0,0,0]Al[1118,15,1119,1119,4970,0,0]Af[473,5,474,3719,0,0,0]5454Ag[473,10,474,2024,0,0,0];:Aj[473,15,474,4136,3323,0,0]Af[473,5,474,4136,0,0,0]Al[3155,15,3156,1148,3324,0,0]Al[3155,15,3156,1148,4039,0,0]Al[3155,15,3156,2032,4058,0,0]Ah[3155,5,3156,2032,0,0,0]Aj[473,15,474,4131,4457,0,0]Al[3155,15,3156,1383,3291,0,0]Al[3155,15,3156,1787,2303,0,0]Ag[473,12,474,0,5088,0,0]Al[3155,12,3156,4360,5089,0,0]Ao[3155,13,3156,4360,5228,5089,0]Ao[3155,13,3156,4360,5233,5089,0]Ak[1219,14,1220,1220,140,0,0]An[1219,13,1220,1220,140,4425,0]An[2394,13,2395,2395,131,4425,0]Ak[2394,14,2395,2395,131,0,0]Ao[1118,13,1119,1119,3069,4425,0]An[1118,13,1714,1714,141,4425,0]An[1118,13,1812,1812,142,4425,0]Aj[473,13,474,0,1470,3275,0]Aj[473,13,474,0,1453,3275,0]Aj[473,13,474,0,4040,3275,0]Aj[473,13,474,0,4177,3275,0]Ai[473,13,474,0,522,3275,0]Ai[473,13,474,0,132,3275,0]Aj[473,13,474,0,2317,3275,0]Ao[3155,13,3156,1844,1471,3275,0]Ao[3155,13,3156,3129,4871,3275,0]Ao[3155,13,3156,1393,1454,3275,0]Ao[3155,13,3156,1143,3104,3275,0]Ao[3155,13,3156,1143,4347,3275,0]Ao[3155,13,3156,1148,3324,3275,0]Ao[3155,13,3156,3257,4041,3275,0]Ao[3155,13,3156,2341,4765,3275,0]Ao[3155,13,3156,1787,4866,3275,0]Ao[3155,13,3156,1817,4178,3275,0]An[3155,13,3156,3156,523,3275,0]An[3155,13,3156,3156,133,3275,0]Ao[3155,13,3156,3156,2318,3275,0]Aj[473,13,474,2340,2295,0,0]Al[3155,13,3156,2341,2296,0,0]Aj[473,13,474,2372,2299,0,0]Ah[473,12,1724,0,3091,0,0]Ah[473,12,1724,0,3084,0,0]Al[3155,13,3156,2341,2300,0,0]Al[3155,12,1725,1725,3092,0,0]Al[3155,12,1725,1725,3085,0,0]Ao[1219,13,1407,1407,4059,1860,0]Ao[1219,13,1407,1407,1212,1860,0]Al[532,13,533,533,4024,1860,0]Aj[473,13,474,0,4311,1860,0]Am[473,13,474,2340,4320,1860,0]Aj[473,13,474,0,1470,1860,0]Aj[473,13,474,0,1453,1860,0]Aj[473,13,474,0,3298,1860,0]Aj[473,13,474,0,3271,1860,0]Aj[473,13,474,0,2330,1860,0]Aj[473,13,474,0,3284,1860,0]Aj[473,13,474,0,3100,1860,0]Aj[473,13,474,0,4560,1860,0]Aj[473,13,474,0,4168,1860,0]Aj[473,13,474,0,4579,1860,0]Aj[473,13,474,0,4895,1860,0]Aj[473,13,474,0,4910,1860,0]Aj[473,13,474,0,4040,1860,0]Aj[473,13,474,0,4433,1860,0]Aj[473,13,474,0,5097,1860,0]Aj[473,13,474,0,4793,1860,0]Ak[473,13,3714,0,3267,1860,0]Ak[473,13,3714,0,4031,1860,0]Ak[473,13,3714,0,4339,1860,0]Ak[473,13,3714,0,4857,1860,0]Ao[3155,13,3156,4589,4312,1860,0]Ao[3155,13,3156,4360,5228,1860,0]Ao[3155,13,3156,1844,1471,1860,0]Ao[3155,13,3156,1393,1454,1860,0]Ao[3155,13,3156,1393,3299,1860,0]Ao[3155,13,3156,1393,3274,1860,0]Ao[3155,13,3156,1408,2331,1860,0]Ao[3155,13,3156,1408,3285,1860,0]Ao[3155,13,3156,1408,3101,1860,0]Ao[3155,13,3156,1408,4561,1860,0]Ao[3155,13,3156,2032,4458,1860,0]Ao[3155,13,3156,2032,4172,1860,0]Ao[3155,13,3156,3257,4580,1860,0]Ao[3155,13,3156,3257,4896,1860,0]Ao[3155,13,3156,3257,4911,1860,0]Ao[3155,13,3156,3257,4041,1860,0]Ao[3155,13,3156,1722,4434,1860,0]Ao[3155,13,3156,1722,5098,1860,0]Ao[3155,13,3156,4360,5233,1860,0]Ao[3155,13,3156,4966,4794,1860,0]An[3155,13,3715,531,3268,1860,0]Ao[3155,13,3715,1379,4032,1860,0]Ao[3155,13,3715,2394,4340,1860,0]Ao[3155,13,3715,3715,4858,1860,0]Ao[2017,13,2018,2018,2315,1860,0]Ao[1118,13,1409,1409,1213,1860,0]Ao[1118,13,1409,1409,4565,1860,0]Al[3155,13,3156,1817,4319,0,0]Al[3155,13,3156,1787,4342,0,0]Al[3155,13,3156,1787,4569,0,0]Al[3155,14,3156,1787,5133,0,0]Ai[3155,14,4865,0,4932,0,0]Ah[3155,14,4568,0,512,0,0]Ai[3155,14,4568,0,1207,0,0]Ai[3155,14,4568,0,1209,0,0]Ai[3155,14,4568,0,4799,0,0]Ah[473,12,1724,0,3091,0,0]Ah[473,12,1724,0,3084,0,0]Al[3155,12,1725,1725,3092,0,0]Al[3155,12,1725,1725,3085,0,0]Aj[473,13,474,4131,3303,0,0]Aj[473,13,474,4131,4979,0,0]Ah[473,13,3714,0,3280,0,0]Aj[473,13,3714,0,497,3280,0]Ak[473,13,3714,0,1179,3280,0]Ak[473,13,3714,0,1874,3280,0]Al[3155,13,3715,3715,3281,0,0]Am[3155,13,3715,531,498,3281,0]Ao[3155,13,3715,1379,1180,3281,0]Ao[3155,13,3715,2394,1875,3281,0]Ag[473,13,474,0,4579,0,0]Al[3155,13,3156,3257,4580,0,0]Ab[2017,3,0,0,0,0,0]:6Aj[473,13,474,3719,3278,0,0]Aj[473,13,474,1117,2311,0,0]Al[3155,14,3156,1787,5133,0,0]Ai[3155,14,4865,0,4932,0,0]Ah[3155,14,4568,0,512,0,0]Ai[3155,14,4568,0,1207,0,0]Ai[3155,14,4568,0,4799,0,0]Aj[473,13,474,4136,3309,0,0]Al[3155,13,3156,2032,3310,0,0]Ak[1219,14,1220,1220,140,0,0]Al[1219,14,1407,1407,1212,0,0]Al[3155,13,3156,3720,3279,0,0]Ah[473,12,1724,0,3091,0,0]Al[3155,12,1725,1725,3092,0,0]Am[1219,13,1220,1220,140,127,0]An[1219,13,1220,1220,4051,127,0]An[1219,13,1220,1220,4023,127,0]An[1219,13,1407,1407,4059,127,0]An[1219,13,1407,1407,1212,127,0]An[1219,13,1407,1407,4459,127,0]Ak[532,13,533,533,4024,127,0]An[4064,13,4065,4065,5134,127,0]Al[473,13,474,3719,4584,127,0]Al[473,13,474,3719,2310,127,0]Ai[473,13,474,0,4311,127,0]Al[473,13,474,4244,5036,127,0]Al[473,13,474,1117,2311,127,0]Al[473,13,474,1117,4757,127,0]Al[473,13,474,3719,3276,127,0]Al[473,13,474,3719,3278,127,0]Al[473,13,474,3719,1877,127,0]Al[473,13,474,3719,1449,127,0]Al[473,13,474,3719,3059,127,0]Al[473,13,474,3719,2283,127,0]Al[473,13,474,3719,4206,127,0]Al[473,13,474,3719,4213,127,0]Al[473,13,474,3719,1440,127,0]Al[473,13,474,3719,5043,127,0]Al[473,13,474,3719,4870,127,0]Al[473,13,474,2340,2295,127,0]Al[473,13,474,2340,4320,127,0]Al[473,13,474,2340,1427,127,0]Al[473,13,474,4244,1448,127,0]Al[473,13,474,4244,3297,127,0]Al[473,13,474,4244,4971,127,0]Al[473,13,474,2372,2299,127,0]Al[473,13,474,2372,3306,127,0]Al[473,13,474,2372,4438,127,0]Ak[473,13,474,1117,516,127,0]Al[473,13,474,1117,4464,127,0]Al[473,13,474,4131,3303,127,0]Al[473,13,474,4131,4979,127,0]Al[473,13,474,4131,4457,127,0]Al[473,13,474,4131,3103,127,0]Al[473,13,474,4131,3305,127,0]Al[473,13,474,4131,4456,127,0]Al[473,13,474,1421,4223,127,0]Al[473,13,474,1160,4054,127,0]Al[473,13,474,4136,3309,127,0]Al[473,13,474,4136,3323,127,0]Al[473,13,474,4136,4222,127,0]Al[473,13,474,4136,1891,127,0]Ai[473,13,474,0,3300,127,0]Ai[473,13,474,0,4351,127,0]Ai[473,13,474,0,4767,127,0]Ai[473,13,474,0,4576,127,0]Ai[473,13,474,0,4196,127,0]Ai[473,13,474,0,4447,127,0]Ai[473,13,474,0,1470,127,0]Ai[473,13,474,0,5040,127,0]Ai[473,13,474,0,1175,127,0]Ai[473,13,474,0,1453,127,0]Ai[473,13,474,0,3298,127,0]Ai[473,13,474,0,3098,127,0]Ai[473,13,474,0,4925,127,0]Ai[473,13,474,0,3271,127,0]Ai[473,13,474,0,2330,127,0]Ai[473,13,474,0,3284,127,0]Ai[473,13,474,0,3100,127,0]Ai[473,13,474,0,4560,127,0]Ai[473,13,474,0,4168,127,0]Ai[473,13,474,0,4556,127,0]Ai[473,13,474,0,3094,127,0]Ai[473,13,474,0,4423,127,0]Ai[473,13,474,0,4570,127,0]Ai[473,13,474,0,4579,127,0]Ai[473,13,474,0,4895,127,0]Ai[473,13,474,0,4910,127,0]Ai[473,13,474,0,4040,127,0]Ai[473,13,474,0,4433,127,0]Ai[473,13,474,0,4216,127,0]Ai[473,13,474,0,5097,127,0]Ai[473,13,474,0,4162,127,0]Ai[473,13,474,0,4026,127,0]Ai[473,13,474,0,3081,127,0]Ai[473,13,474,0,4553,127,0]Ai[473,13,474,0,4759,127,0]Ai[473,13,474,0,4174,127,0]Ai[473,13,474,0,4177,127,0]Ai[473,13,474,0,4182,127,0]Ai[473,13,474,0,4930,127,0]Ai[473,13,474,0,3287,127,0]Ai[473,13,474,0,4793,127,0]Ah[473,13,474,0,522,127,0]Ah[473,13,474,0,132,127,0]Ai[473,13,474,0,2317,127,0]Ai[473,13,474,0,2327,127,0]Ai[473,13,3714,0,497,127,0]Aj[473,13,3714,0,3267,127,0]Aj[473,13,3714,0,1179,127,0]Aj[473,13,3714,0,4031,127,0]Aj[473,13,3714,0,1874,127,0]Aj[473,13,3714,0,4339,127,0]Aj[473,13,3714,0,4857,127,0]Aj[473,13,1724,0,1869,127,0]Ai[473,13,1724,0,510,127,0]Aj[473,13,1724,0,5196,127,0]Aj[473,13,1724,0,4029,127,0]Aj[473,13,1724,0,4934,127,0]Aj[473,13,1806,0,3318,127,0]Aj[473,13,1806,0,4322,127,0]Aj[473,13,1806,0,1192,127,0]Aj[473,13,1806,0,4789,127,0]Aj[473,13,1806,0,5034,127,0]Aj[473,13,1806,0,4791,127,0]Am[2394,13,2395,2395,131,127,0]An[2394,13,2395,2395,2286,127,0]An[3155,13,3156,4589,4312,127,0]An[3155,13,3156,3129,3072,127,0]An[3155,13,3156,1143,1436,127,0]An[3155,13,3156,2032,1895,127,0]An[3155,13,3156,2032,3304,127,0]An[3155,13,3156,2032,3310,127,0]An[3155,13,3156,3720,3279,127,0]An[3155,13,3156,1139,3301,127,0]An[3155,13,3156,1722,4352,127,0]An[3155,13,3156,2341,2296,127,0]An[3155,13,3156,2341,4318,127,0]An[3155,13,3156,2341,4768,127,0]An[3155,13,3156,2341,4330,127,0]An[3155,13,3156,1787,5260,127,0]An[3155,13,3156,4360,5228,127,0]An[3155,13,3156,1420,4577,127,0]An[3155,13,3156,1420,4197,127,0]An[3155,13,3156,1420,4448,127,0]An[3155,13,3156,1844,1471,127,0]An[3155,13,3156,1844,5041,127,0]An[3155,13,3156,3120,1176,127,0]An[3155,13,3156,3129,4871,127,0]An[3155,13,3156,1393,1454,127,0]An[3155,13,3156,1393,4212,127,0]An[3155,13,3156,1393,3299,127,0]An[3155,13,3156,1393,3099,127,0]An[3155,13,3156,1393,4869,127,0]An[3155,13,3156,1393,4926,127,0]An[3155,13,3156,1393,3274,127,0]An[3155,13,3156,1408,2331,127,0]An[3155,13,3156,1408,3285,127,0]An[3155,13,3156,1408,3101,127,0]An[3155,13,3156,1408,4561,127,0]An[3155,13,3156,1143,3104,127,0]An[3155,13,3156,1143,4347,127,0]An[3155,13,3156,1148,3324,127,0]An[3155,13,3156,1148,4039,127,0]An[3155,13,3156,2032,4058,127,0]An[3155,13,3156,2032,4458,127,0]An[3155,13,3156,2032,4172,127,0]An[3155,13,3156,3720,4557,127,0]An[3155,13,3156,3720,3095,127,0]An[3155,13,3156,3720,4424,127,0]An[3155,13,3156,3720,5129,127,0]An[3155,13,3156,3720,1197,127,0]An[3155,13,3156,1139,4571,127,0]An[3155,13,3156,3257,4580,127,0]An[3155,13,3156,3257,4896,127,0]An[3155,13,3156,3257,4911,127,0]An[3155,13,3156,3257,4041,127,0]An[3155,13,3156,1722,4434,127,0]An[3155,13,3156,1722,4218,127,0]An[3155,13,3156,1722,5098,127,0]An[3155,13,3156,1722,4164,127,0]An[3155,13,3156,1722,4027,127,0]An[3155,13,3156,1383,3082,127,0]An[3155,13,3156,1383,4439,127,0]An[3155,13,3156,1383,3291,127,0]An[3155,13,3156,1383,3313,127,0]An[3155,13,3156,1383,4179,127,0]An[3155,13,3156,1383,4555,127,0]An[3155,13,3156,1383,4761,127,0]An[3155,13,3156,1383,2289,127,0]An[3155,13,3156,2341,4765,127,0]An[3155,13,3156,2341,2300,127,0]An[3155,13,3156,2341,4175,127,0]An[3155,13,3156,2341,4021,127,0]An[3155,13,3156,2341,3307,127,0]An[3155,13,3156,1787,4342,127,0]An[3155,13,3156,1787,4569,127,0]An[3155,13,3156,1787,5133,127,0]An[3155,13,3156,1787,2303,127,0]An[3155,13,3156,1787,4866,127,0]An[3155,13,3156,1817,4559,127,0]An[3155,13,3156,1817,4319,127,0]An[3155,13,3156,1817,4178,127,0]An[3155,13,3156,1817,4183,127,0]Ak[3155,13,3156,0,4462,127,0]Ak[3155,13,3156,0,4931,127,0]An[3155,13,3156,4360,3288,127,0]An[3155,13,3156,4360,5233,127,0]An[3155,13,3156,4966,4794,127,0]Am[3155,13,3156,3156,523,127,0]Am[3155,13,3156,3156,133,127,0]An[3155,13,3156,3156,2318,127,0]An[3155,13,3156,3156,2328,127,0]An[3155,13,3156,3156,3080,127,0]An[3155,13,1720,1720,4353,127,0]An[3155,13,5082,5082,4978,127,0]An[3155,13,5082,5082,1194,127,0]An[3155,13,1725,1725,1870,127,0]Am[3155,13,1725,1725,511,127,0]An[3155,13,1725,1725,5197,127,0]An[3155,13,1725,1725,4030,127,0]An[3155,13,1725,1725,4937,127,0]An[3155,13,1807,1807,3319,127,0]An[3155,13,1807,1807,4323,127,0]Ak[3155,13,1807,0,1193,127,0]An[3155,13,1807,1807,4790,127,0]An[3155,13,1807,1807,5035,127,0]An[3155,13,1807,1807,4792,127,0]Al[3155,13,3715,531,498,127,0]Am[3155,13,3715,531,3268,127,0]An[3155,13,3715,1379,1180,127,0]An[3155,13,3715,1379,4032,127,0]An[3155,13,3715,2394,1875,127,0]An[3155,13,3715,2394,4340,127,0]An[3155,13,3715,3715,4858,127,0]An[3155,13,4825,4825,1195,127,0]An[3155,13,4825,4825,1198,127,0]An[2017,13,2018,2018,3322,127,0]Am[2017,13,2018,428,4463,127,0]An[2017,13,2018,2018,2315,127,0]An[2017,13,2018,2018,2329,127,0]An[2017,13,2018,2018,4184,127,0]An[1118,13,1119,1119,3069,127,0]An[1118,13,1119,1119,4970,127,0]Am[1118,13,1714,1714,141,127,0]An[1118,13,1409,1409,1213,127,0]An[1118,13,1409,1409,4060,127,0]An[1118,13,1409,1409,4565,127,0]An[1118,13,4278,4278,5182,127,0]An[1118,13,4278,4278,5183,127,0]Am[1118,13,1812,1812,142,127,0]Aj[473,13,474,3719,5043,0,0]Aj[473,13,474,1117,4464,0,0]Ag[473,13,474,0,4162,0,0]Al[3155,13,3156,1722,4164,0,0]Al[3155,14,3156,1787,5133,0,0]Ai[3155,14,4865,0,4932,0,0]Ah[3155,14,4568,0,512,0,0]Ai[3155,14,4568,0,1207,0,0]Af[473,5,474,3719,0,0,0]Al[1219,15,1220,1220,4023,0,0]Ae[2394,6,2395,0,0,0,0]Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]Ah[3155,5,3156,1383,0,0,0]Aj[473,15,474,3719,4870,0,0]Al[3155,15,3156,3129,4871,0,0]Af[473,5,474,2340,0,0,0]Ah[3155,5,3156,2341,0,0,0]Al[3155,15,3156,2032,4058,0,0]Af[473,5,474,2372,0,0,0]2Ah[3155,6,3156,1787,0,0,0]Ag[473,19,474,0,1470,0,0]Al[3155,19,3156,1844,1471,0,0]Ag[473,19,474,0,4930,0,0]Ai[3155,19,3156,0,4931,0,0]=Ah[473,12,1724,0,4217,0,0]>Ak[473,13,1806,0,4791,4217,0]>Al[3155,12,1725,1725,4219,0,0]>Ao[3155,13,1807,1807,4792,4219,0]Ao[1118,13,4278,4278,5183,4219,0]An[2394,13,2395,2395,131,4042,0]Ao[2394,13,2395,2395,2286,4042,0]Ag[473,14,474,0,3100,0,0]Ag[473,14,474,0,4560,0,0]Al[3155,14,3156,1408,3101,0,0]Al[3155,14,3156,1408,4561,0,0]Ag[473,13,474,0,4216,0,0]Al[3155,13,3156,1722,4218,0,0]Aj[473,13,474,2024,4421,0,0]0Ak[2017,13,2018,428,4463,0,0]Al[3155,13,3156,3720,3279,0,0]Al[1118,13,1409,1409,4565,0,0]Ae[1118,2,1119,0,0,0,0]Aj[473,13,474,2372,2299,0,0]Al[3155,13,3156,2341,2300,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]6Ag[473,13,474,0,4579,0,0]Al[3155,13,3156,3257,4580,0,0]32Ak[1219,14,1220,1220,140,0,0]Ag[473,14,474,0,2327,0,0]Al[3155,14,3156,3156,2328,0,0]Al[2017,14,2018,2018,2329,0,0]Ah[473,14,3714,0,4857,0,0]Al[3155,14,3715,3715,4858,0,0]Al[3155,13,3156,2032,1895,0,0]Al[3155,13,3156,4360,5228,0,0]Al[3155,13,3156,4360,5233,0,0]Aj[473,13,474,4136,3309,0,0]Al[3155,13,3156,2032,3310,0,0]>=Al[1219,14,1407,1407,1212,0,0]Ai[532,14,533,533,4024,0,0]Ah[473,13,1806,0,4789,0,0]Ah[473,13,1806,0,4791,0,0]Al[3155,13,1807,1807,4790,0,0]Al[3155,13,1807,1807,4792,0,0]6Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]Af[473,5,474,4589,0,0,0]Ah[3155,5,3156,4589,0,0,0]Ag[473,15,474,0,4556,0,0]Ag[473,15,474,0,4174,0,0]Al[3155,15,3156,3720,4557,0,0]Al[3155,15,3156,2341,4175,0,0]Ah[3155,6,3156,2341,0,0,0]Ah[3155,5,3156,1817,0,0,0]Af[473,5,474,2340,0,0,0]Aj[473,15,474,4131,4456,0,0]Ag[473,5,1806,1807,0,0,0]Ae[3155,5,1807,0,0,0,0]Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]Ag[473,19,474,0,4930,0,0]Ai[3155,19,3156,0,4931,0,0]Ah[3155,5,3156,2341,0,0,0]Al[3155,15,3156,1787,4866,0,0]?=5454Al[3155,15,3156,2341,4318,0,0]Ag[473,5,3714,2394,0,0,0]Ah[3155,5,3715,2394,0,0,0]Ah[3155,5,3156,1787,0,0,0]98Ah[3155,6,3156,1143,0,0,0]:9Af[473,5,474,1722,0,0,0]Ah[3155,5,3156,1722,0,0,0]Ae[3155,5,1720,0,0,0,0]Al[3155,13,3156,3720,3279,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]Al[1118,14,1119,1119,3069,0,0]Ae[3155,2,3156,0,0,0,0]Ag[473,13,474,0,4433,0,0]Al[3155,13,3156,1722,4434,0,0]Aj[473,12,474,2024,4421,0,0]Ab[4363,3,0,0,0,0,0]Ak[1219,14,1220,1220,140,0,0]Al[1219,14,1407,1407,4059,0,0]98Aj[473,13,474,2372,3306,0,0]Al[3155,13,3156,2341,3307,0,0]Ag[473,13,474,0,4895,0,0]Al[3155,13,3156,3257,4896,0,0];==;:Ak[1219,14,1220,1220,140,0,0]21Al[1118,14,1119,1119,3069,0,0]Aj[473,13,474,4131,3303,0,0]Aj[473,13,474,4131,4979,0,0]Al[3155,13,3156,2032,3304,0,0]Aj[473,13,474,3719,3276,0,0]Aj[473,13,474,2372,2299,0,0]Ai[473,13,474,1117,516,0,0]Al[3155,13,3156,2341,2300,0,0]<;Ag[473,13,474,0,1470,0,0]Ag[473,13,474,0,1453,0,0]Al[3155,13,3156,1844,1471,0,0]Al[3155,13,3156,1393,1454,0,0]20Ae[1219,7,1407,0,0,0,0]?>Al[1219,14,1407,1407,1212,0,0]Ao[1118,13,1119,1119,4970,3109,0]Ag[473,15,474,0,3081,0,0]Af[473,5,474,1383,0,0,0]Al[3155,15,3156,1383,3082,0,0]Ah[3155,5,3156,1383,0,0,0]Af[473,6,474,3720,0,0,0]Ah[3155,6,3156,3720,0,0,0]Al[3155,15,3156,2341,4318,0,0]Ah[3155,5,3156,1817,0,0,0]Af[473,5,474,1408,0,0,0]Ah[3155,5,3156,1408,0,0,0]97Ae[1118,5,1409,0,0,0,0]:8Ah[3155,6,3156,1787,0,0,0]Af[473,5,474,1139,0,0,0]Ah[3155,5,3156,1139,0,0,0]Al[3155,15,3156,1393,4869,0,0]Af[473,5,474,1420,0,0,0]Ah[3155,5,3156,1420,0,0,0]Af[473,5,474,3257,0,0,0]Ah[3155,5,3156,3257,0,0,0]Ag[473,15,474,0,4556,0,0]Al[3155,15,3156,3720,4557,0,0]Af[473,5,474,3719,0,0,0]Aj[473,15,474,4131,3103,0,0]Aj[473,15,474,4131,3305,0,0]Aj[473,15,474,4136,3323,0,0]Aj[473,15,474,4136,4222,0,0]An[1219,13,1220,1220,140,4317,0]Ao[1219,13,1220,1220,4051,4317,0]Ao[1219,13,1220,1220,4023,4317,0]Am[473,13,474,3719,3278,4317,0]Am[473,13,474,3719,1440,4317,0]Am[473,13,474,3719,5043,4317,0]Am[473,13,474,3719,4870,4317,0]Am[473,13,474,2340,2295,4317,0]Am[473,13,474,2340,4320,4317,0]Am[473,13,474,4244,1448,4317,0]Al[473,13,474,1117,516,4317,0]Am[473,13,474,1117,4464,4317,0]Am[473,13,474,4131,4457,4317,0]Am[473,13,474,4131,3103,4317,0]Am[473,13,474,4131,3305,4317,0]Am[473,13,474,4131,4456,4317,0]Am[473,13,474,4136,3323,4317,0]Am[473,13,474,4136,4222,4317,0]Aj[473,13,474,0,1470,4317,0]Aj[473,13,474,0,5040,4317,0]Aj[473,13,474,0,1453,4317,0]Aj[473,13,474,0,3298,4317,0]Aj[473,13,474,0,3098,4317,0]Aj[473,13,474,0,4925,4317,0]Aj[473,13,474,0,3271,4317,0]Aj[473,13,474,0,2330,4317,0]Aj[473,13,474,0,3284,4317,0]Aj[473,13,474,0,3100,4317,0]Aj[473,13,474,0,4560,4317,0]Aj[473,13,474,0,4168,4317,0]Aj[473,13,474,0,4556,4317,0]Aj[473,13,474,0,3094,4317,0]Aj[473,13,474,0,4423,4317,0]Aj[473,13,474,0,4579,4317,0]Aj[473,13,474,0,4895,4317,0]Aj[473,13,474,0,4910,4317,0]Aj[473,13,474,0,4040,4317,0]Aj[473,13,474,0,4433,4317,0]Aj[473,13,474,0,4216,4317,0]Aj[473,13,474,0,5097,4317,0]Aj[473,13,474,0,4162,4317,0]Aj[473,13,474,0,4026,4317,0]Aj[473,13,474,0,3081,4317,0]Aj[473,13,474,0,4553,4317,0]Aj[473,13,474,0,4759,4317,0]Aj[473,13,474,0,4174,4317,0]Aj[473,13,474,0,4177,4317,0]Aj[473,13,474,0,4182,4317,0]Aj[473,13,474,0,4930,4317,0]Aj[473,13,474,0,3287,4317,0]Ai[473,13,474,0,522,4317,0]Ai[473,13,474,0,132,4317,0]Aj[473,13,474,0,2317,4317,0]Aj[473,13,3714,0,497,4317,0]Ak[473,13,3714,0,3267,4317,0]Ak[473,13,3714,0,1179,4317,0]Ak[473,13,3714,0,4031,4317,0]Ak[473,13,3714,0,1874,4317,0]Ak[473,13,3714,0,4339,4317,0]Ak[473,13,1806,0,1192,4317,0]Ak[473,13,1806,0,4789,4317,0]Ak[473,13,1806,0,5034,4317,0]Ak[473,13,1806,0,4791,4317,0]Ao[3155,13,3156,1844,1471,4317,0]Ao[3155,13,3156,1844,5041,4317,0]Ao[3155,13,3156,3129,4871,4317,0]Ao[3155,13,3156,1393,1454,4317,0]Ao[3155,13,3156,1393,4212,4317,0]Ao[3155,13,3156,1393,3299,4317,0]Ao[3155,13,3156,1393,3099,4317,0]Ao[3155,13,3156,1393,4869,4317,0]Ao[3155,13,3156,1393,4926,4317,0]Ao[3155,13,3156,1393,3274,4317,0]Ao[3155,13,3156,1408,2331,4317,0]Ao[3155,13,3156,1408,3285,4317,0]Ao[3155,13,3156,1408,3101,4317,0]Ao[3155,13,3156,1408,4561,4317,0]Ao[3155,13,3156,1143,3104,4317,0]Ao[3155,13,3156,1143,4347,4317,0]Ao[3155,13,3156,1148,3324,4317,0]Ao[3155,13,3156,1148,4039,4317,0]Ao[3155,13,3156,2032,4458,4317,0]Ao[3155,13,3156,2032,4172,4317,0]Ao[3155,13,3156,3720,4557,4317,0]Ao[3155,13,3156,3720,3095,4317,0]Ao[3155,13,3156,3720,4424,4317,0]Ao[3155,13,3156,3720,5129,4317,0]Ao[3155,13,3156,3257,4580,4317,0]Ao[3155,13,3156,3257,4896,4317,0]Ao[3155,13,3156,3257,4911,4317,0]Ao[3155,13,3156,3257,4041,4317,0]Ao[3155,13,3156,1722,4434,4317,0]Ao[3155,13,3156,1722,4218,4317,0]Ao[3155,13,3156,1722,5098,4317,0]Ao[3155,13,3156,1722,4164,4317,0]Ao[3155,13,3156,1722,4027,4317,0]Ao[3155,13,3156,1383,3082,4317,0]Ao[3155,13,3156,1383,3291,4317,0]Ao[3155,13,3156,1383,3313,4317,0]Ao[3155,13,3156,1383,4179,4317,0]Ao[3155,13,3156,1383,4555,4317,0]Ao[3155,13,3156,1383,4761,4317,0]Ao[3155,13,3156,1383,2289,4317,0]Ao[3155,13,3156,2341,4765,4317,0]Ao[3155,13,3156,2341,4175,4317,0]Ao[3155,13,3156,1787,4569,4317,0]Ao[3155,13,3156,1787,5133,4317,0]Ao[3155,13,3156,1787,2303,4317,0]Ao[3155,13,3156,1787,4866,4317,0]Ao[3155,13,3156,1817,4178,4317,0]Ao[3155,13,3156,1817,4183,4317,0]Al[3155,13,3156,0,4462,4317,0]Al[3155,13,3156,0,4931,4317,0]Ao[3155,13,3156,4360,3288,4317,0]Ao[3155,13,3156,4360,5233,4317,0]An[3155,13,3156,3156,523,4317,0]An[3155,13,3156,3156,133,4317,0]Ao[3155,13,3156,3156,2318,4317,0]Ao[3155,13,3156,3156,3080,4317,0]Ao[3155,13,1720,1720,4353,4317,0]Ao[3155,13,5082,5082,4978,4317,0]Ao[3155,13,5082,5082,1194,4317,0]Al[3155,13,1807,0,1193,4317,0]Ao[3155,13,1807,1807,4790,4317,0]Ao[3155,13,1807,1807,5035,4317,0]Ao[3155,13,1807,1807,4792,4317,0]Am[3155,13,3715,531,498,4317,0]An[3155,13,3715,531,3268,4317,0]Ao[3155,13,3715,1379,1180,4317,0]Ao[3155,13,3715,1379,4032,4317,0]Ao[3155,13,3715,2394,1875,4317,0]Ao[3155,13,3715,2394,4340,4317,0]Ao[3155,13,4825,4825,1198,4317,0]Ao[2017,13,2018,2018,2315,4317,0]Ao[2017,13,2018,2018,2329,4317,0]Ao[2017,13,2018,2018,4184,4317,0]Ao[1118,13,1119,1119,4970,4317,0]Ao[1118,13,1409,1409,4565,4317,0]Ao[1118,13,4278,4278,5182,4317,0]Ao[1118,13,4278,4278,5183,4317,0]Af[473,7,474,2024,0,0,0]Ag[473,13,474,0,4433,0,0]Al[3155,13,3156,1722,4434,0,0]Ah[473,12,1724,0,3091,0,0]Al[3155,12,1725,1725,3092,0,0]Al[2017,14,2018,2018,2315,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]10Ae[1118,7,1119,0,0,0,0]Aj[473,13,474,2340,2295,0,0]Ae[4363,7,4364,0,0,0,0]Ae[1219,7,1220,0,0,0,0]Al[3155,13,3156,3720,3279,0,0]Aj[473,13,474,3719,3278,0,0]1Al[3155,13,3156,2032,4458,0,0]Al[1219,13,1407,1407,1212,0,0]Al[1219,14,1407,1407,4059,0,0]Aj[473,12,474,2024,1886,0,0]Am[473,13,474,1421,4223,1886,0]Am[473,13,474,1160,4054,1886,0]Ah[473,12,1724,0,1887,0,0]Ak[473,13,1806,0,3318,1887,0]Al[3155,12,1725,1725,1888,0,0]Ao[3155,13,1807,1807,3319,1888,0]Af[473,5,474,1117,0,0,0]Ag[473,15,474,0,3081,0,0]Af[473,5,474,1383,0,0,0]Al[3155,15,3156,1383,3082,0,0]Ah[3155,5,3156,1383,0,0,0]Ag[473,15,474,0,4556,0,0]Al[3155,15,3156,3720,4557,0,0]Ah[3155,5,3156,2341,0,0,0]Al[3155,15,3156,3720,1197,0,0]Af[473,5,474,2341,0,0,0]2Ag[473,15,474,0,4174,0,0]Al[3155,15,3156,2341,4175,0,0]Ag[473,19,474,0,4930,0,0]Ai[3155,19,3156,0,4931,0,0]Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]32Ag[473,15,474,0,3094,0,0]Al[3155,15,3156,3720,3095,0,0]Ag[473,15,474,0,3081,0,0]?0?0?43Ag[473,5,1806,1807,0,0,0]Ae[3155,5,1807,0,0,0,0]10Af[473,5,474,4966,0,0,0]Ah[3155,5,3156,4966,0,0,0]Al[3155,15,4825,4825,1195,0,0]7Al[3155,15,3156,1393,4869,0,0]7Al[3155,15,3156,1787,4569,0,0]Ag[473,12,474,0,2323,0,0]Aj[473,13,474,0,4311,2323,0]Aj[473,13,474,0,4196,2323,0]Al[3155,12,3156,1420,2324,0,0]Ao[3155,13,3156,4589,4312,2324,0]Ao[3155,13,3156,1420,4197,2324,0]Ae[4064,7,4065,0,0,0,0]Aj[473,13,474,3719,3278,0,0]Al[3155,13,3156,3720,3279,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]32An[1219,13,1220,1220,140,4435,0]An[2394,13,2395,2395,131,4435,0]Ao[2394,13,2395,2395,2286,4435,0]Ao[1118,13,1119,1119,3069,4435,0]An[1118,13,1714,1714,141,4435,0]An[1118,13,1812,1812,142,4435,0]Ak[2017,13,2018,428,4463,0,0]Ag[473,13,474,0,4579,0,0]Al[3155,13,3156,3257,4580,0,0]:9Ae[3155,2,3156,0,0,0,0]Aj[473,13,474,2340,2295,0,0]Al[3155,13,3156,2341,2296,0,0]?>43Aj[473,13,474,4131,3303,0,0]Ae[4363,7,4364,0,0,0,0]7Al[1118,14,1119,1119,3069,0,0]80Ae[1219,7,1220,0,0,0,0]Ag[473,13,474,0,4216,0,0]Ag[473,13,474,0,4162,0,0]Al[3155,13,3156,1722,4218,0,0]Al[3155,13,3156,1722,4164,0,0]Ag[473,14,474,0,4216,0,0]Al[3155,14,3156,1722,4218,0,0]53Aj[473,13,474,4136,3309,0,0]Ag[473,19,474,0,4930,0,0]Ai[3155,19,3156,0,4931,0,0]Ag[473,15,474,0,4556,0,0]Al[3155,15,3156,1393,4869,0,0]Al[3155,15,3156,3720,4557,0,0]Ah[473,10,3714,3715,0,0,0]Af[3155,10,3715,0,0,0,0]Ag[473,5,3714,3715,0,0,0]Ae[3155,5,3715,0,0,0,0]Ah[473,10,1724,1725,0,0,0]Af[3155,10,1725,0,0,0,0]Ag[473,15,474,0,4174,0,0]Al[3155,15,3156,2341,4175,0,0]Ag[473,10,474,3720,0,0,0]Ai[3155,10,3156,3720,0,0,0]Ah[3155,6,3156,1787,0,0,0]Ah[3155,6,3156,1393,0,0,0]Af[473,6,474,3719,0,0,0]Ah[3155,6,3156,3129,0,0,0]Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]10Aj[473,15,474,3719,4870,0,0]Al[3155,15,3156,3129,4871,0,0]Ag[473,14,474,0,3100,0,0]Al[3155,14,3156,1408,3101,0,0]Ah[473,14,3714,0,4857,0,0]Al[3155,14,3715,3715,4858,0,0]Aj[473,13,474,3719,3278,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]10Al[3155,13,3156,3129,3072,0,0]Aj[473,13,474,2340,2295,0,0]Al[3155,13,3156,2341,2296,0,0]Ab[4889,3,0,0,0,0,0]Ao[1118,13,1119,1119,4970,3109,0]Aj[473,13,474,4136,3309,0,0]Ag[473,19,474,0,4930,0,0]Ai[3155,19,3156,0,4931,0,0]Af[473,5,474,3257,0,0,0]Ah[3155,5,3156,3257,0,0,0]Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]54Ai[3155,10,3156,1787,0,0,0]Al[3155,15,3156,3720,1197,0,0]7600Al[3155,15,3156,1143,4347,0,0]4365Al[3155,15,4825,4825,1195,0,0]Ae[1118,9,1119,0,0,0,0]:9Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]<;<;Af[473,6,474,1393,0,0,0]Ah[3155,6,3156,1393,0,0,0]32Al[1219,15,1220,1220,4051,0,0]Ac[473,5,474,0,0,0,0]Ae[3155,5,3156,0,0,0,0]Ag[473,15,474,0,4174,0,0]Ag[473,5,1724,1725,0,0,0]Al[3155,15,3156,2341,4175,0,0]Al[3155,15,3156,1787,4866,0,0]Ae[3155,5,1725,0,0,0,0]An[1219,13,1220,1220,140,4425,0]An[2394,13,2395,2395,131,4425,0]Ao[1118,13,1119,1119,3069,4425,0]An[1118,13,1714,1714,141,4425,0]An[1118,13,1812,1812,142,4425,0]Ag[473,13,474,0,4579,0,0]Al[3155,13,3156,3257,4580,0,0]Ag[473,13,474,0,1470,0,0]Al[3155,13,3156,1844,1471,0,0]Aj[473,14,474,2340,4320,0,0]Aj[473,13,474,3719,5043,0,0]Ag[473,13,474,0,4433,0,0]Al[3155,13,3156,1722,4434,0,0]Ag[473,13,474,0,4311,0,0]Al[3155,13,3156,4589,4312,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]7Al[3155,13,3156,3720,3279,0,0]Aj[473,13,474,3719,3278,0,0]Ah[473,13,1806,0,4789,0,0]Ah[473,13,1806,0,4791,0,0]Al[3155,13,1807,1807,4790,0,0]Al[3155,13,1807,1807,4792,0,0]45;:Ao[4064,13,4065,4065,5134,4924,0]Ag[473,13,474,0,4570,0,0]Al[3155,13,3156,1139,4571,0,0]Ae[1118,6,1119,0,0,0,0]Af[473,5,474,4244,0,0,0]Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]10Ag[473,15,474,0,3094,0,0]Al[3155,15,3156,3720,3095,0,0]Ae[3155,5,5082,0,0,0,0]Af[473,5,474,4131,0,0,0]Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]Al[3155,15,4825,4825,1195,0,0]Aj[473,14,474,2340,4320,0,0]Ai[473,13,474,0,522,3061,0]Ai[473,13,474,0,132,3061,0]An[3155,13,3156,3156,523,3061,0]An[3155,13,3156,3156,133,3061,0]Ao[2394,13,2395,2395,2286,3314,0]Ag[473,13,474,0,4910,0,0]Al[3155,13,3156,3257,4911,0,0]Ah[473,12,1724,0,1887,0,0]Ak[473,13,1806,0,3318,1887,0]Al[3155,12,1725,1725,1888,0,0]Ao[3155,13,1807,1807,3319,1888,0]Aj[473,13,474,3719,3278,0,0]Ag[473,12,474,0,4863,0,0]Al[3155,12,3156,3720,4864,0,0]Ag[473,13,474,0,4579,0,0]Al[3155,13,3156,3257,4580,0,0]Ag[473,14,474,0,3100,0,0]Al[3155,14,3156,1408,3101,0,0]Ah[473,12,3714,0,3280,0,0]Aj[473,13,3714,0,497,3280,0]Ak[473,13,3714,0,1179,3280,0]Ak[473,13,3714,0,1874,3280,0]Al[3155,12,3715,3715,3281,0,0]Am[3155,13,3715,531,498,3281,0]Ao[3155,13,3715,1379,1180,3281,0]Ao[3155,13,3715,2394,1875,3281,0]Aj[473,14,474,2340,4320,0,0]Al[3155,13,3156,2032,1895,0,0]Ah[3155,2,4358,4135,0,0,0]Ae[1219,7,1220,0,0,0,0]==Ai[473,13,474,1117,516,0,0]Ak[2017,13,2018,428,4463,0,0]032Ah[473,12,1724,0,1887,0,0]Ak[473,13,1806,0,3318,1887,0]Ae[3155,2,3156,0,0,0,0]Al[3155,12,1725,1725,1888,0,0]Ao[3155,13,1807,1807,3319,1888,0]Ag[473,10,474,4360,0,0,0]Ai[3155,10,3156,4360,0,0,0]Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]Ag[473,15,474,0,4174,0,0]Al[3155,15,3156,2341,4175,0,0]Ag[473,19,474,0,4930,0,0]Ai[3155,19,3156,0,4931,0,0]Af[473,5,474,1722,0,0,0]Ah[3155,5,3156,1722,0,0,0]Ah[473,12,1724,0,3091,0,0]Al[3155,12,1725,1725,3092,0,0]Aj[473,13,474,2340,2295,0,0]Al[3155,13,3156,3720,3279,0,0]Al[3155,13,3156,2341,2296,0,0]Al[3155,13,3156,3156,3080,0,0]Ah[473,13,3714,0,4339,0,0]Al[3155,13,3715,2394,4340,0,0]Aj[473,13,474,2024,3107,0,0]0Aj[473,13,474,3719,3278,0,0]6Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]Ag[473,13,474,0,4579,0,0]Al[3155,13,3156,3257,4580,0,0]Al[1118,14,1119,1119,3069,0,0]Ag[473,19,474,0,4930,0,0]Ai[3155,19,3156,0,4931,0,0]Al[3155,15,4825,4825,1195,0,0]Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]10Ag[473,15,474,0,4925,0,0]Ag[473,15,474,0,3094,0,0]Ag[473,15,474,0,4423,0,0]Al[3155,15,3156,1393,4926,0,0]Al[3155,15,3156,3720,3095,0,0]Al[3155,15,3156,3720,4424,0,0]Ah[3155,5,3156,3720,0,0,0]Al[3155,15,3156,1393,4869,0,0]Ag[473,15,474,0,3081,0,0]Al[3155,15,3156,1383,3082,0,0]Ah[3155,5,3156,1787,0,0,0]Ae[4064,5,4065,0,0,0,0]Al[1118,13,1119,1119,3069,0,0]Aj[473,13,474,3719,3278,0,0]Ah[473,12,1724,0,1887,0,0]Ak[473,13,1806,0,3318,1887,0]Al[3155,12,1725,1725,1888,0,0]Ao[3155,13,1807,1807,3319,1888,0]Ah[3155,7,5013,5012,0,0,0]0Al[3155,13,3156,3720,3279,0,0]6Ai[473,13,474,1117,516,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]Ao[4064,13,4065,4065,5134,4924,0]0Ag[473,13,474,0,4162,0,0]Al[3155,13,3156,1722,4164,0,0]Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]10Ag[473,13,474,0,4895,0,0]Al[3155,13,3156,3257,4896,0,0]Am[1219,13,1220,1220,140,495,0]Am[2394,13,2395,2395,131,495,0]An[1118,13,1119,1119,3069,495,0]Am[1118,13,1714,1714,141,495,0]Am[1118,13,1812,1812,142,495,0]??Ah[473,13,3714,0,3280,0,0]Aj[473,13,3714,0,497,3280,0]Al[3155,13,3715,3715,3281,0,0]Am[3155,13,3715,531,498,3281,0]Ai[473,13,474,1117,516,0,0]An[1219,13,1220,1220,140,4435,0]An[2394,13,2395,2395,131,4435,0]Ao[2394,13,2395,2395,2286,4435,0]Ao[1118,13,1119,1119,3069,4435,0]An[1118,13,1714,1714,141,4435,0]An[1118,13,1812,1812,142,4435,0]Ag[473,13,474,0,4216,0,0]Al[3155,13,3156,1722,4218,0,0]Ag[473,19,474,0,4930,0,0]Ai[3155,19,3156,0,4931,0,0]Ae[1118,5,4278,0,0,0,0]0Ag[473,13,474,0,4433,0,0]Al[3155,13,3156,1722,4434,0,0]Ag[473,13,474,0,4162,0,0]Al[3155,13,3156,1722,4164,0,0]Aj[473,13,474,2340,2295,0,0]Aj[473,13,474,2372,2299,0,0]Al[3155,13,3156,2341,2300,0,0]Al[3155,13,3156,3720,3279,0,0]Ao[4064,13,4065,4065,5134,4924,0];:Ag[473,5,1724,1725,0,0,0]Ae[3155,5,1725,0,0,0,0]Ah[473,15,1806,0,1192,0,0]Ai[3155,15,1807,0,1193,0,0]?>?>Aj[473,13,474,3719,3278,0,0]6Ag[473,13,474,0,4579,0,0]Al[3155,13,3156,3257,4580,0,0]Ae[4064,7,4065,0,0,0,0]Ae[4363,7,4364,0,0,0,0]Ag[473,14,474,0,3284,0,0]Al[3155,14,3156,1408,3285,0,0]?Ah[477,12,1735,0,4243,0,0]>Ak[477,13,1818,0,4819,4243,0]>Al[3173,12,1736,1736,4245,0,0]>Ao[3173,13,1819,1819,4820,4245,0]Ao[1127,13,4305,4305,5210,4245,0]An[2408,13,2409,2409,132,4066,0]Ao[2408,13,2409,2409,2299,4066,0]Ag[477,14,478,0,3118,0,0]Ag[477,14,478,0,4587,0,0]Al[3173,14,3174,1419,3119,0,0]Al[3173,14,3174,1419,4588,0,0]Ag[477,13,478,0,4242,0,0]Al[3173,13,3174,1733,4244,0,0]Aj[477,13,478,2036,4448,0,0]0Ak[2029,13,2030,431,4490,0,0]Ai[477,13,478,1126,520,0,0]Al[3173,13,3174,3741,3298,0,0]Al[1127,13,1418,1418,4592,0,0]Ae[1127,2,1128,0,0,0,0]Aj[477,13,478,2385,2312,0,0]Al[3173,13,3174,2354,2313,0,0]Ag[477,14,478,0,3303,0,0]Al[3173,14,3174,1419,3304,0,0]6Ag[477,13,478,0,4606,0,0]Al[3173,13,3174,3276,4607,0,0]32Ak[1228,14,1229,1229,141,0,0]Ag[477,14,478,0,2340,0,0]Al[3173,14,3174,3174,2341,0,0]Al[2029,14,2030,2030,2342,0,0]Ah[477,14,3735,0,4885,0,0]Al[3173,14,3736,3736,4886,0,0]Al[3173,13,3174,2044,1907,0,0]Al[3173,13,3174,4387,5257,0,0]Al[3173,13,3174,4387,5262,0,0]Aj[477,13,478,4162,3328,0,0]Al[3173,13,3174,2044,3329,0,0]>=Al[1228,14,1417,1417,1221,0,0]Ai[536,14,537,537,4048,0,0]Ah[477,13,1818,0,4817,0,0]Ah[477,13,1818,0,4819,0,0]Al[3173,13,1819,1819,4818,0,0]Al[3173,13,1819,1819,4820,0,0]6Ah[477,15,1818,0,1201,0,0]Ai[3173,15,1819,0,1202,0,0]Af[477,5,478,4616,0,0,0]Ah[3173,5,3174,4616,0,0,0]Ag[477,15,478,0,4583,0,0]Ag[477,15,478,0,4200,0,0]Al[3173,15,3174,3741,4584,0,0]Al[3173,15,3174,2354,4201,0,0]Ah[3173,6,3174,2354,0,0,0]Ah[3173,5,3174,1829,0,0,0]Af[477,5,478,2353,0,0,0]Aj[477,15,478,4157,4483,0,0]Ag[477,5,1818,1819,0,0,0]Ae[3173,5,1819,0,0,0,0]Ag[477,15,478,0,3099,0,0]Al[3173,15,3174,1393,3100,0,0]Ag[477,19,478,0,4959,0,0]Ai[3173,19,3174,0,4960,0,0]Ah[3173,5,3174,2354,0,0,0]Al[3173,15,3174,1798,4894,0,0]?=5454Al[3173,15,3174,2354,4345,0,0]Ag[477,5,3735,2408,0,0,0]Ah[3173,5,3736,2408,0,0,0]Ah[3173,5,3174,1798,0,0,0]98Ah[3173,6,3174,1152,0,0,0]:9Af[477,5,478,1733,0,0,0]Ah[3173,5,3174,1733,0,0,0]Ae[3173,5,1731,0,0,0,0]Al[3173,13,3174,3741,3298,0,0]Ag[477,14,478,0,3303,0,0]Al[3173,14,3174,1419,3304,0,0]Al[1127,14,1128,1128,3086,0,0]Ae[3173,2,3174,0,0,0,0]Ag[477,13,478,0,4460,0,0]Al[3173,13,3174,1733,4461,0,0]Aj[477,12,478,2036,4448,0,0]Ab[4390,3,0,0,0,0,0]Ak[1228,14,1229,1229,141,0,0]Al[1228,14,1417,1417,4083,0,0]98Aj[477,13,478,2385,3325,0,0]Al[3173,13,3174,2354,3326,0,0]Ag[477,13,478,0,4924,0,0]Al[3173,13,3174,3276,4925,0,0];==;:Ak[1228,14,1229,1229,141,0,0]21Al[1127,14,1128,1128,3086,0,0]Aj[477,13,478,4157,3322,0,0]Aj[477,13,478,4157,5008,0,0]Al[3173,13,3174,2044,3323,0,0]Aj[477,13,478,3740,3295,0,0]Aj[477,13,478,2385,2312,0,0]Ai[477,13,478,1126,520,0,0]Al[3173,13,3174,2354,2313,0,0]<;Ag[477,13,478,0,1480,0,0]Ag[477,13,478,0,1463,0,0]Al[3173,13,3174,1856,1481,0,0]Al[3173,13,3174,1403,1464,0,0]20Ae[1228,7,1417,0,0,0,0]?>Al[1228,14,1417,1417,1221,0,0]Ao[1127,13,1128,1128,4999,3127,0]Ag[477,15,478,0,3099,0,0]Af[477,5,478,1393,0,0,0]Al[3173,15,3174,1393,3100,0,0]Ah[3173,5,3174,1393,0,0,0]Af[477,6,478,3741,0,0,0]Ah[3173,6,3174,3741,0,0,0]Al[3173,15,3174,2354,4345,0,0]Ah[3173,5,3174,1829,0,0,0]Af[477,5,478,1419,0,0,0]Ah[3173,5,3174,1419,0,0,0]97Ae[1127,5,1418,0,0,0,0]:8Ah[3173,6,3174,1798,0,0,0]Af[477,5,478,1148,0,0,0]Ah[3173,5,3174,1148,0,0,0]Al[3173,15,3174,1403,4897,0,0]Af[477,5,478,1430,0,0,0]Ah[3173,5,3174,1430,0,0,0]Af[477,5,478,3276,0,0,0]Ah[3173,5,3174,3276,0,0,0]Ag[477,15,478,0,4583,0,0]Al[3173,15,3174,3741,4584,0,0]Af[477,5,478,3740,0,0,0]Aj[477,15,478,4157,3121,0,0]Aj[477,15,478,4157,3324,0,0]Aj[477,15,478,4162,3342,0,0]Aj[477,15,478,4162,4248,0,0]An[1228,13,1229,1229,141,4344,0]Ao[1228,13,1229,1229,4075,4344,0]Ao[1228,13,1229,1229,4047,4344,0]Am[477,13,478,3740,3297,4344,0]Am[477,13,478,3740,1450,4344,0]Am[477,13,478,3740,5072,4344,0]Am[477,13,478,3740,4898,4344,0]Am[477,13,478,2353,2308,4344,0]Am[477,13,478,2353,4347,4344,0]Am[477,13,478,4270,1458,4344,0]Al[477,13,478,1126,520,4344,0]Am[477,13,478,1126,4491,4344,0]Am[477,13,478,4157,4484,4344,0]Am[477,13,478,4157,3121,4344,0]Am[477,13,478,4157,3324,4344,0]Am[477,13,478,4157,4483,4344,0]Am[477,13,478,4162,3342,4344,0]Am[477,13,478,4162,4248,4344,0]Aj[477,13,478,0,1480,4344,0]Aj[477,13,478,0,5069,4344,0]Aj[477,13,478,0,1463,4344,0]Aj[477,13,478,0,3317,4344,0]Aj[477,13,478,0,3116,4344,0]Aj[477,13,478,0,4954,4344,0]Aj[477,13,478,0,3290,4344,0]Aj[477,13,478,0,2343,4344,0]Aj[477,13,478,0,3303,4344,0]Aj[477,13,478,0,3118,4344,0]Aj[477,13,478,0,4587,4344,0]Aj[477,13,478,0,4194,4344,0]Aj[477,13,478,0,4583,4344,0]Aj[477,13,478,0,3112,4344,0]Aj[477,13,478,0,4450,4344,0]Aj[477,13,478,0,4606,4344,0]Aj[477,13,478,0,4924,4344,0]Aj[477,13,478,0,4939,4344,0]Aj[477,13,478,0,4064,4344,0]Aj[477,13,478,0,4460,4344,0]Aj[477,13,478,0,4242,4344,0]Aj[477,13,478,0,5126,4344,0]Aj[477,13,478,0,4188,4344,0]Aj[477,13,478,0,4050,4344,0]Aj[477,13,478,0,3099,4344,0]Aj[477,13,478,0,4580,4344,0]Aj[477,13,478,0,4787,4344,0]Aj[477,13,478,0,4200,4344,0]Aj[477,13,478,0,4203,4344,0]Aj[477,13,478,0,4208,4344,0]Aj[477,13,478,0,4959,4344,0]Aj[477,13,478,0,3306,4344,0]Ai[477,13,478,0,526,4344,0]Ai[477,13,478,0,133,4344,0]Aj[477,13,478,0,2330,4344,0]Aj[477,13,478,0,3097,4344,0]Aj[477,13,3735,0,501,4344,0]Ak[477,13,3735,0,3286,4344,0]Ak[477,13,3735,0,1188,4344,0]Ak[477,13,3735,0,4055,4344,0]Ak[477,13,3735,0,1886,4344,0]Ak[477,13,3735,0,4366,4344,0]Ak[477,13,1818,0,1201,4344,0]Ak[477,13,1818,0,4817,4344,0]Ak[477,13,1818,0,5063,4344,0]Ak[477,13,1818,0,4819,4344,0]Ao[3173,13,3174,1856,1481,4344,0]Ao[3173,13,3174,1856,5070,4344,0]Ao[3173,13,3174,3147,4899,4344,0]Ao[3173,13,3174,1403,1464,4344,0]Ao[3173,13,3174,1403,4238,4344,0]Ao[3173,13,3174,1403,3318,4344,0]Ao[3173,13,3174,1403,3117,4344,0]Ao[3173,13,3174,1403,4897,4344,0]Ao[3173,13,3174,1403,4955,4344,0]Ao[3173,13,3174,1403,3293,4344,0]Ao[3173,13,3174,1419,2344,4344,0]Ao[3173,13,3174,1419,3304,4344,0]Ao[3173,13,3174,1419,3119,4344,0]Ao[3173,13,3174,1419,4588,4344,0]Ao[3173,13,3174,1152,3122,4344,0]Ao[3173,13,3174,1152,4374,4344,0]Ao[3173,13,3174,1157,3343,4344,0]Ao[3173,13,3174,1157,4063,4344,0]Ao[3173,13,3174,2044,4485,4344,0]Ao[3173,13,3174,2044,4198,4344,0]Ao[3173,13,3174,3741,4584,4344,0]Ao[3173,13,3174,3741,3113,4344,0]Ao[3173,13,3174,3741,4451,4344,0]Ao[3173,13,3174,3741,5157,4344,0]Ao[3173,13,3174,3276,4607,4344,0]Ao[3173,13,3174,3276,4925,4344,0]Ao[3173,13,3174,3276,4940,4344,0]Ao[3173,13,3174,3276,4065,4344,0]Ao[3173,13,3174,1733,4461,4344,0]Ao[3173,13,3174,1733,4244,4344,0]Ao[3173,13,3174,1733,5127,4344,0]Ao[3173,13,3174,1733,4190,4344,0]Ao[3173,13,3174,1733,4051,4344,0]Ao[3173,13,3174,1393,3100,4344,0]Ao[3173,13,3174,1393,3310,4344,0]Ao[3173,13,3174,1393,3332,4344,0]Ao[3173,13,3174,1393,4205,4344,0]Ao[3173,13,3174,1393,4582,4344,0]Ao[3173,13,3174,1393,4789,4344,0]Ao[3173,13,3174,1393,2302,4344,0]Ao[3173,13,3174,2354,4793,4344,0]Ao[3173,13,3174,2354,4201,4344,0]Ao[3173,13,3174,1798,4596,4344,0]Ao[3173,13,3174,1798,5160,4344,0]Ao[3173,13,3174,1798,2316,4344,0]Ao[3173,13,3174,1798,4894,4344,0]Ao[3173,13,3174,1829,4204,4344,0]Ao[3173,13,3174,1829,4209,4344,0]Al[3173,13,3174,0,4489,4344,0]Al[3173,13,3174,0,4960,4344,0]Ao[3173,13,3174,4387,3307,4344,0]Ao[3173,13,3174,4387,5262,4344,0]An[3173,13,3174,3174,527,4344,0]An[3173,13,3174,3174,134,4344,0]Ao[3173,13,3174,3174,2331,4344,0]Ao[3173,13,3174,3174,3098,4344,0]Ao[3173,13,1731,1731,4380,4344,0]Ao[3173,13,5111,5111,5007,4344,0]Ao[3173,13,5111,5111,1203,4344,0]Al[3173,13,1819,0,1202,4344,0]Ao[3173,13,1819,1819,4818,4344,0]Ao[3173,13,1819,1819,5064,4344,0]Ao[3173,13,1819,1819,4820,4344,0]Am[3173,13,3736,535,502,4344,0]An[3173,13,3736,535,3287,4344,0]Ao[3173,13,3736,1389,1189,4344,0]Ao[3173,13,3736,1389,4056,4344,0]Ao[3173,13,3736,2408,1887,4344,0]Ao[3173,13,3736,2408,4367,4344,0]Ao[3173,13,4853,4853,1207,4344,0]Ao[2029,13,2030,2030,2328,4344,0]Ao[2029,13,2030,2030,2342,4344,0]Ao[2029,13,2030,2030,4210,4344,0]Ao[1127,13,1128,1128,4999,4344,0]Ao[1127,13,1418,1418,4592,4344,0]Ao[1127,13,4305,4305,5209,4344,0]Ao[1127,13,4305,4305,5210,4344,0]Af[477,7,478,2036,0,0,0]Ag[477,13,478,0,4460,0,0]Al[3173,13,3174,1733,4461,0,0]Ah[477,12,1735,0,3109,0,0]Al[3173,12,1736,1736,3110,0,0]Al[2029,14,2030,2030,2328,0,0]Ag[477,14,478,0,3303,0,0]Al[3173,14,3174,1419,3304,0,0]10Ae[1127,7,1128,0,0,0,0]Aj[477,13,478,2353,2308,0,0]Ae[4390,7,4391,0,0,0,0]Ae[1228,7,1229,0,0,0,0]Al[3173,13,3174,3741,3298,0,0]Aj[477,13,478,3740,3297,0,0]1Al[3173,13,3174,2044,4485,0,0]Al[1228,13,1417,1417,1221,0,0]Al[1228,14,1417,1417,4083,0,0]Aj[477,12,478,2036,1898,0,0]Am[477,13,478,1431,4249,1898,0]Am[477,13,478,1169,4078,1898,0]Ah[477,12,1735,0,1899,0,0]Ak[477,13,1818,0,3337,1899,0]Al[3173,12,1736,1736,1900,0,0]Ao[3173,13,1819,1819,3338,1900,0]Af[477,5,478,1126,0,0,0]Ag[477,15,478,0,3099,0,0]Af[477,5,478,1393,0,0,0]Al[3173,15,3174,1393,3100,0,0]Ah[3173,5,3174,1393,0,0,0]Ag[477,15,478,0,4583,0,0]Al[3173,15,3174,3741,4584,0,0]Ah[3173,5,3174,2354,0,0,0]Al[3173,15,3174,3741,1206,0,0]Af[477,5,478,2354,0,0,0]2Ag[477,15,478,0,4200,0,0]Al[3173,15,3174,2354,4201,0,0]Ag[477,19,478,0,4959,0,0]Ai[3173,19,3174,0,4960,0,0]Ah[477,15,1818,0,1201,0,0]Ai[3173,15,1819,0,1202,0,0]32Ag[477,15,478,0,3112,0,0]Al[3173,15,3174,3741,3113,0,0]Ag[477,15,478,0,3099,0,0]?0?0?43Ag[477,5,1818,1819,0,0,0]Ae[3173,5,1819,0,0,0,0]10Af[477,5,478,4995,0,0,0]Ah[3173,5,3174,4995,0,0,0]Al[3173,15,4853,4853,1204,0,0]7Al[3173,15,3174,1403,4897,0,0]7Al[3173,15,3174,1798,4596,0,0]Ag[477,12,478,0,2336,0,0]Aj[477,13,478,0,4338,2336,0]Aj[477,13,478,0,4222,2336,0]Al[3173,12,3174,1430,2337,0,0]Ao[3173,13,3174,4616,4339,2337,0]Ao[3173,13,3174,1430,4223,2337,0]Ae[4088,7,4089,0,0,0,0]Aj[477,13,478,3740,3297,0,0]Al[3173,13,3174,3741,3298,0,0]Ag[477,14,478,0,3303,0,0]Al[3173,14,3174,1419,3304,0,0]32An[1228,13,1229,1229,141,4462,0]An[2408,13,2409,2409,132,4462,0]Ao[2408,13,2409,2409,2299,4462,0]Ao[1127,13,1128,1128,3086,4462,0]An[1127,13,1725,1725,142,4462,0]An[1127,13,1824,1824,143,4462,0]Ak[2029,13,2030,431,4490,0,0]Ag[477,13,478,0,4606,0,0]Al[3173,13,3174,3276,4607,0,0]:9Ae[3173,2,3174,0,0,0,0]Aj[477,13,478,2353,2308,0,0]Al[3173,13,3174,2354,2309,0,0]?>43Aj[477,13,478,4157,3322,0,0]Ae[4390,7,4391,0,0,0,0]7Al[1127,14,1128,1128,3086,0,0]80Ae[1228,7,1229,0,0,0,0]Ag[477,13,478,0,4242,0,0]Ag[477,13,478,0,4188,0,0]Al[3173,13,3174,1733,4244,0,0]Al[3173,13,3174,1733,4190,0,0]Ag[477,14,478,0,4242,0,0]Al[3173,14,3174,1733,4244,0,0]53Aj[477,13,478,4162,3328,0,0]Ag[477,19,478,0,4959,0,0]Ai[3173,19,3174,0,4960,0,0]Ag[477,15,478,0,4583,0,0]Al[3173,15,3174,1403,4897,0,0]Al[3173,15,3174,3741,4584,0,0]Ah[477,10,3735,3736,0,0,0]Af[3173,10,3736,0,0,0,0]Ag[477,5,3735,3736,0,0,0]Ae[3173,5,3736,0,0,0,0]Ah[477,10,1735,1736,0,0,0]Af[3173,10,1736,0,0,0,0]Ag[477,15,478,0,4200,0,0]Al[3173,15,3174,2354,4201,0,0]Ag[477,10,478,3741,0,0,0]Ai[3173,10,3174,3741,0,0,0]Ah[3173,6,3174,1798,0,0,0]Ah[3173,6,3174,1403,0,0,0]Af[477,6,478,3740,0,0,0]Ah[3173,6,3174,3147,0,0,0]Ag[477,15,478,0,3099,0,0]Al[3173,15,3174,1393,3100,0,0]10Aj[477,15,478,3740,4898,0,0]Al[3173,15,3174,3147,4899,0,0]Ag[477,14,478,0,3118,0,0]Al[3173,14,3174,1419,3119,0,0]Ah[477,14,3735,0,4885,0,0]Al[3173,14,3736,3736,4886,0,0]Aj[477,13,478,3740,3297,0,0]Ag[477,14,478,0,3303,0,0]Al[3173,14,3174,1419,3304,0,0]10Al[3173,13,3174,3147,3089,0,0]Aj[477,13,478,2353,2308,0,0]Al[3173,13,3174,2354,2309,0,0]Ab[4917,3,0,0,0,0,0]Ao[1127,13,1128,1128,4999,3127,0]3Aj[477,13,478,4162,3328,0,0]Ag[477,19,478,0,4959,0,0]Ai[3173,19,3174,0,4960,0,0]Af[477,5,478,3276,0,0,0]Ah[3173,5,3174,3276,0,0,0]Ah[477,15,1818,0,1201,0,0]Ai[3173,15,1819,0,1202,0,0]54Ai[3173,10,3174,1798,0,0,0]Al[3173,15,3174,3741,1206,0,0]7600Al[3173,15,3174,1152,4374,0,0]4365Al[3173,15,4853,4853,1204,0,0]Ae[1127,9,1128,0,0,0,0]:9Ag[477,15,478,0,3099,0,0]Al[3173,15,3174,1393,3100,0,0]<;<;Af[477,6,478,1403,0,0,0]Ah[3173,6,3174,1403,0,0,0]32Al[1228,15,1229,1229,4075,0,0]Ac[477,5,478,0,0,0,0]Ae[3173,5,3174,0,0,0,0]Ag[477,15,478,0,4200,0,0]Ag[477,5,1735,1736,0,0,0]Al[3173,15,3174,2354,4201,0,0]Al[3173,15,3174,1798,4894,0,0]Ae[3173,5,1736,0,0,0,0]An[1228,13,1229,1229,141,4452,0]An[2408,13,2409,2409,132,4452,0]Ao[1127,13,1128,1128,3086,4452,0]An[1127,13,1725,1725,142,4452,0]An[1127,13,1824,1824,143,4452,0]Ag[477,13,478,0,4606,0,0]Al[3173,13,3174,3276,4607,0,0]Ag[477,13,478,0,1480,0,0]Al[3173,13,3174,1856,1481,0,0]Aj[477,14,478,2353,4347,0,0]Aj[477,13,478,3740,5072,0,0]Ag[477,13,478,0,4460,0,0]Al[3173,13,3174,1733,4461,0,0]Ag[477,13,478,0,4338,0,0]Al[3173,13,3174,4616,4339,0,0]Ag[477,14,478,0,3303,0,0]Al[3173,14,3174,1419,3304,0,0]7Al[3173,13,3174,3741,3298,0,0]Aj[477,13,478,3740,3297,0,0]Ah[477,13,1818,0,4817,0,0]Ah[477,13,1818,0,4819,0,0]Al[3173,13,1819,1819,4818,0,0]Al[3173,13,1819,1819,4820,0,0]45;:Ao[4088,13,4089,4089,5161,4953,0]Ag[477,13,478,0,4597,0,0]Al[3173,13,3174,1148,4598,0,0]Ae[1127,6,1128,0,0,0,0]Af[477,5,478,4270,0,0,0]Ag[477,15,478,0,3099,0,0]Al[3173,15,3174,1393,3100,0,0]10Ag[477,15,478,0,3112,0,0]Al[3173,15,3174,3741,3113,0,0]Ae[3173,5,5111,0,0,0,0]Af[477,5,478,4157,0,0,0]Ah[477,15,1818,0,1201,0,0]Ai[3173,15,1819,0,1202,0,0]Al[3173,15,4853,4853,1204,0,0]Aj[477,14,478,2353,4347,0,0]Ai[477,13,478,0,526,3078,0]Ai[477,13,478,0,133,3078,0]An[3173,13,3174,3174,527,3078,0]An[3173,13,3174,3174,134,3078,0]Ao[2408,13,2409,2409,2299,3333,0]Ag[477,13,478,0,4939,0,0]Al[3173,13,3174,3276,4940,0,0]Ah[477,12,1735,0,1899,0,0]Ak[477,13,1818,0,3337,1899,0]Al[3173,12,1736,1736,1900,0,0]Ao[3173,13,1819,1819,3338,1900,0]Aj[477,13,478,3740,3297,0,0]Ag[477,12,478,0,4891,0,0]Al[3173,12,3174,3741,4892,0,0]Ag[477,13,478,0,4606,0,0]Al[3173,13,3174,3276,4607,0,0]Ag[477,14,478,0,3118,0,0]Al[3173,14,3174,1419,3119,0,0]Ah[477,12,3735,0,3299,0,0]Aj[477,13,3735,0,501,3299,0]Ak[477,13,3735,0,1188,3299,0]Ak[477,13,3735,0,1886,3299,0]Al[3173,12,3736,3736,3300,0,0]Am[3173,13,3736,535,502,3300,0]Ao[3173,13,3736,1389,1189,3300,0]Ao[3173,13,3736,2408,1887,3300,0]Aj[477,14,478,2353,4347,0,0]Al[3173,13,3174,2044,1907,0,0]Ah[3173,2,4385,4161,0,0,0]Ae[1228,7,1229,0,0,0,0]==Ai[477,13,478,1126,520,0,0]Ak[2029,13,2030,431,4490,0,0]032Ah[477,12,1735,0,1899,0,0]Ak[477,13,1818,0,3337,1899,0]Ae[3173,2,3174,0,0,0,0]Al[3173,12,1736,1736,1900,0,0]Ao[3173,13,1819,1819,3338,1900,0]Ag[477,10,478,4387,0,0,0]Ai[3173,10,3174,4387,0,0,0]Ah[477,15,1818,0,1201,0,0]Ai[3173,15,1819,0,1202,0,0]Ag[477,15,478,0,4200,0,0]Al[3173,15,3174,2354,4201,0,0]Ag[477,19,478,0,4959,0,0]Ai[3173,19,3174,0,4960,0,0]Af[477,5,478,1733,0,0,0]Ah[3173,5,3174,1733,0,0,0]Ah[477,12,1735,0,3109,0,0]Al[3173,12,1736,1736,3110,0,0]Aj[477,13,478,2353,2308,0,0]Al[3173,13,3174,3741,3298,0,0]Al[3173,13,3174,2354,2309,0,0]Ah[477,13,3735,0,4366,0,0]Al[3173,13,3736,2408,4367,0,0]Aj[477,13,478,2036,3125,0,0]0Aj[477,13,478,3740,3297,0,0]5Ag[477,14,478,0,3303,0,0]Al[3173,14,3174,1419,3304,0,0]Ag[477,13,478,0,4606,0,0]Al[3173,13,3174,3276,4607,0,0]Al[1127,14,1128,1128,3086,0,0]Ag[477,19,478,0,4959,0,0]Ai[3173,19,3174,0,4960,0,0]Al[3173,15,4853,4853,1204,0,0]Ah[477,15,1818,0,1201,0,0]Ai[3173,15,1819,0,1202,0,0]10Ag[477,15,478,0,4954,0,0]Ag[477,15,478,0,3112,0,0]Ag[477,15,478,0,4450,0,0]Al[3173,15,3174,1403,4955,0,0]Al[3173,15,3174,3741,3113,0,0]Al[3173,15,3174,3741,4451,0,0]Ah[3173,5,3174,3741,0,0,0]Ag[477,15,478,0,3099,0,0]Al[3173,15,3174,1393,3100,0,0]Ah[3173,5,3174,1798,0,0,0]Ae[4088,5,4089,0,0,0,0]Al[1127,13,1128,1128,3086,0,0]Aj[477,13,478,3740,3297,0,0]Ah[477,12,1735,0,1899,0,0]Ak[477,13,1818,0,3337,1899,0]Al[3173,12,1736,1736,1900,0,0]Ao[3173,13,1819,1819,3338,1900,0]Ah[3173,7,5042,5041,0,0,0]0Al[3173,13,3174,3741,3298,0,0]6Ai[477,13,478,1126,520,0,0]Ag[477,14,478,0,3303,0,0]Al[3173,14,3174,1419,3304,0,0]Ao[4088,13,4089,4089,5161,4953,0]0Ag[477,13,478,0,4188,0,0]Al[3173,13,3174,1733,4190,0,0]Ah[477,15,1818,0,1201,0,0]Ai[3173,15,1819,0,1202,0,0]10Ag[477,13,478,0,4924,0,0]Al[3173,13,3174,3276,4925,0,0]Am[1228,13,1229,1229,141,499,0]Am[2408,13,2409,2409,132,499,0]An[1127,13,1128,1128,3086,499,0]Am[1127,13,1725,1725,142,499,0]Am[1127,13,1824,1824,143,499,0]??Ah[477,13,3735,0,3299,0,0]Aj[477,13,3735,0,501,3299,0]Al[3173,13,3736,3736,3300,0,0]Am[3173,13,3736,535,502,3300,0]Ai[477,13,478,1126,520,0,0]An[1228,13,1229,1229,141,4462,0]An[2408,13,2409,2409,132,4462,0]Ao[2408,13,2409,2409,2299,4462,0]Ao[1127,13,1128,1128,3086,4462,0]An[1127,13,1725,1725,142,4462,0]An[1127,13,1824,1824,143,4462,0]Ag[477,13,478,0,4242,0,0]Al[3173,13,3174,1733,4244,0,0]Ag[477,19,478,0,4959,0,0]Ai[3173,19,3174,0,4960,0,0]Ae[1127,5,4305,0,0,0,0]0Ag[477,13,478,0,4460,0,0]Al[3173,13,3174,1733,4461,0,0]Ag[477,13,478,0,4188,0,0]Al[3173,13,3174,1733,4190,0,0]Aj[477,13,478,2353,2308,0,0]Aj[477,13,478,2385,2312,0,0]Al[3173,13,3174,2354,2313,0,0]Al[3173,13,3174,3741,3298,0,0]Ao[4088,13,4089,4089,5161,4953,0];:Ag[477,5,1735,1736,0,0,0]Ae[3173,5,1736,0,0,0,0]Ah[477,15,1818,0,1201,0,0]Ai[3173,15,1819,0,1202,0,0]?>?>Aj[477,13,478,3740,3297,0,0]6Ag[477,13,478,0,4606,0,0]Al[3173,13,3174,3276,4607,0,0]Ae[4088,7,4089,0,0,0,0]Ae[4390,7,4391,0,0,0,0]Ag[477,14,478,0,3303,0,0]Al[3173,14,3174,1419,3304,0,0]?>>>>A`[\"{IFfBCAb}\",[]]?A`[\"{KIbBFL`}\",[]]A`[\"{cc{}}\",[\"T\"]]0000A`[\"{KIbABCf}\",[]]111Aa[\"{AJ@fAHCd}\",[]]22A`[\"{BBChIFf}\",[]]33333333A`[\"{BHMnIFd}\",[]]A`[\"{BEFlIFd}\",[]]5A`[\"{BEHbIFd}\",[]]A`[\"{BBNfIFd}\",[]]A`[\"{BBOhIFd}\",[]]A`[\"{BCMjIFd}\",[]]A`[\"{BCOdIFd}\",[]]A`[\"{BHLlIFd}\",[]]A`[\"{BHGfIFd}\",[]]A`[\"{BEIlIFd}\",[]]A`[\"{AIN`IFd}\",[]]>>>>>>>>>>>>An[\"{{{AHO`{c}}}AJAjCNh}\",[\"S\"]]??????????????Bm[\"{{{AHO`{KI`}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bk[\"{{{AHO`{KFn}}{AHO`{CGbc}}}ICjKF`}\",[\"H\"]]Bn[\"{{{AHO`{AOL`}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{B@Mf}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bm[\"{{{AHO`{DAl}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bm[\"{{{AHO`{A@j}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{ABCd}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{AHCb}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bm[\"{{{AHO`{KIb}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{BFDf}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bk[\"{{{AHO`{KG`}}{AHO`{CGbc}}}ICjKF`}\",[\"H\"]]Bn[\"{{{AHO`{AHFd}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{BBBl}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{AJAn}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{AOLb}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{BEGb}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{BFCl}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{B@Mh}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bm[\"{{{AHO`{DAn}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bm[\"{{{AHO`{A@l}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{ABCf}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Bn[\"{{{AHO`{AHCd}}{AHO`{CGbc}}}ICjKF`}\",[\"__H\"]]Aa[\"{{}c{}}\",[\"U\"]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n[\"{{}ICj}\",[]]0000Cc[\"{{{AHO`{CGbKDl}}KCb}{{KGd{{KFl{IDb}}AHFd}}}}\",[]]Cf[\"{{{AHO`{CGbBB@j}}}{{KFl{c}}}{}}\",[\"Iterator::Item\"]]Dl[\"{{{AHO`{BFBn}}ICh{AHO`{CGb{K@j{Oh}}}}}{{KGd{{AHO`{{K@j{Oh}}}}AOJj}}}}\",[]]Bi[\"{{{AHO`{CGbNNf}}A@l}{{KFl{ABCf}}}}\",[]]Dl[\"{{{AHO`{BFC`}}ICh{AHO`{CGb{K@j{Oh}}}}}{{KGd{{AHO`{{K@j{Oh}}}}AOJl}}}}\",[]]Bf[\"{{{AHO`{AILb}}KFn}{{KFl{DA`}}}}\",[]]Bf[\"{{{AHO`{DA`}}}{{KGd{ICjAIKd}}}}\",[]]A`[\"{AILnCNb}\",[]]Am[\"{{{AHO`{CGbAILd}}}ICj}\",[]]A`[\"{AIM`CNb}\",[]]Ck[\"{{{ABb{{AHO`{CGbAIJd}}}}{AHO`{CGbNIn}}}{{DAh{ICj}}}}\",[]]Dk[\"{{{ABb{{AHO`{CGbBD@f}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABb{{AHO`{CGbABBf}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABb{{AHO`{CGbABBh}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABb{{AHO`{CGbAIKn}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{ABb{{AHO`{CGbNLb}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{ABb{{AHO`{CGbKFf}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABb{{AHO`{CGbAH@h}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABb{{AHO`{CGbAAO`}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABb{{AHO`{CGbBAA`}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABb{{AHO`{CGbBAAn}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{ABb{{AHO`{CGbKCj}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{ABb{{AHO`{CGbBBNb}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{ABb{{AHO`{CGbNMn}}}}{AHO`{CGbNIn}}}{{DAh{c}}}{}}\",[\"Future::Output\"]]Bf[\"{{{AHO`{CGbAILd}}}{{KFl{IFl}}}}\",[]]De[\"{{{AHO`{CGbAIOd}}{AHO`{CGb{K@j{Oh}}}}}{{KGd{{KFl{KCb}}AHFb}}}}\",[]]Bm[\"{{{AHO`{CGbAIOf}}DC`}{{KGd{KDlBBBl}}}}\",[]]C`[\"{{DAn{AHO`{CGbc}}}{{KGd{BGAnIFf}}}A@f}\",[\"R\"]]Bf[\"{{{AHO`{CGbAHBb}}}{{KFl{ICn}}}}\",[]]Ec[\"{{{AHO`{BCAd}}AJAj{AHO`{CGb{K@j{AIMh}}}}{AHO`{CGb{K@j{ABC`}}}}}{{KGf{KCb}}}}\",[]]Cn[\"{{{AHO`{BFBn}}ICh{AHO`{CGb{ABh{Oh}}}}}{{KGd{ICjAOJj}}}}\",[]]Cn[\"{{{AHO`{BFC`}}ICh{AHO`{CGb{ABh{Oh}}}}}{{KGd{ICjAOJl}}}}\",[]]C`[\"{{{AHO`{CGbAHBb}}ICnDC`}{{KGd{ICjBFDf}}}}\",[]]Cc[\"{{{AHO`{BCAd}}AJAj{AHO`{ABDl}}}{{KGf{ICj}}}}\",[]]Bn[\"{{{AHO`{CGbBH@l}}CN`}{{AHO`{CGbBH@l}}}}\",[]]Bn[\"{{{AHO`{CGbBH@n}}CN`}{{AHO`{CGbBH@n}}}}\",[]]Aj[\"{{{AHO`{AILb}}}DAl}\",[]]Aj[\"{{{AHO`{AILd}}}DAn}\",[]]Cn[\"{{{AHO`{NJj}}{AHO`{{K@j{Oh}}}}{AHO`{CGb{K@j{Oh}}}}}ICj}\",[]]Cn[\"{{{AHO`{NJl}}{AHO`{{K@j{Oh}}}}{AHO`{CGb{K@j{Oh}}}}}ICj}\",[]]A`[\"{BC@lCNb}\",[]]A`[\"{ABDhKCb}\",[]]A`[\"{ABDjKCb}\",[]]o[\"{IHdDC`}\",[]]Bm[\"{{{AHO`{CGbAIOd}}KI`}{{KGd{ICjB@Ld}}}}\",[]]Bm[\"{{{AHO`{CGbBGB`}}KI`}{{KGd{ICjB@Ld}}}}\",[]]Bm[\"{{{AHO`{CGbAIOf}}KIb}{{KGd{ICjB@Ll}}}}\",[]]Bn[\"{{{AHO`{AJAf}}{AHO`{CMl}}}{{KFl{ICn}}}}\",[]]Bn[\"{{{AHO`{BBOd}}{AHO`{CMl}}}{{KFl{ICn}}}}\",[]]Bn[\"{{{AHO`{BEJj}}{AHO`{CMl}}}{{KFl{ICn}}}}\",[]]Bn[\"{{{AHO`{AJAh}}{AHO`{CMl}}}{{KFl{ICn}}}}\",[]]Bn[\"{{{AHO`{BBOf}}{AHO`{CMl}}}{{KFl{ICn}}}}\",[]]Bn[\"{{{AHO`{BEJl}}{AHO`{CMl}}}{{KFl{ICn}}}}\",[]]Bd[\"{{{AHO`{BEGf}}}{{KFl{BB@h}}}}\",[]]Bd[\"{{{AHO`{BEGh}}}{{KFl{BB@j}}}}\",[]]Bi[\"{{{A@b{BFBf}}NK`CMn}{{A@d{AILf}}}}\",[]]Bi[\"{{{A@b{AIJl}}NK`CMn}{{A@d{AILf}}}}\",[]]Bi[\"{{{A@b{AOJn}}NK`CMn}{{A@d{AILf}}}}\",[]]Bi[\"{{{A@b{BBAl}}NK`CMn}{{A@d{AILf}}}}\",[]]Bi[\"{{{A@b{BFBh}}NK`CMn}{{A@d{AILh}}}}\",[]]Bi[\"{{{A@b{AIJn}}NK`CMn}{{A@d{AILh}}}}\",[]]Bi[\"{{{A@b{AOK`}}NK`CMn}{{A@d{AILh}}}}\",[]]Bi[\"{{{A@b{BBAn}}NK`CMn}{{A@d{AILh}}}}\",[]]Aj[\"{BG@n{{A@b{BFBh}}}}\",[]]o[\"{ID`ICn}\",[]]A`[\"{ABDnCNb}\",[]]o[\"{IDbICn}\",[]]A`[\"{ABE`CNb}\",[]]Aa[\"{AOIjBCM`}\",[]]Ai[\"{{{AHO`{AAj}}}AAj}\",[]]Ak[\"{{{AHO`{AOMf}}}AOMf}\",[]]Ak[\"{{{AHO`{AOIn}}}AOIn}\",[]]Ak[\"{{{AHO`{AILb}}}AILb}\",[]]Ai[\"{{{AHO`{KEd}}}KEd}\",[]]Ak[\"{{{AHO`{BGJ`}}}BGJ`}\",[]]Ak[\"{{{AHO`{BFDd}}}BFDd}\",[]]Ak[\"{{{AHO`{AB@h}}}AB@h}\",[]]Ak[\"{{{AHO`{BAOf}}}BAOf}\",[]]Ai[\"{{{AHO`{KFd}}}KFd}\",[]]Ai[\"{{{AHO`{DA`}}}DA`}\",[]]Ak[\"{{{AHO`{BCAf}}}BCAf}\",[]]Ak[\"{{{AHO`{BC@h}}}BC@h}\",[]]Ak[\"{{{AHO`{AHFb}}}AHFb}\",[]]Ak[\"{{{AHO`{AIOh}}}AIOh}\",[]]Ak[\"{{{AHO`{BC@f}}}BC@f}\",[]]Ak[\"{{{AHO`{AJAl}}}AJAl}\",[]]Ak[\"{{{AHO`{BAC`}}}BAC`}\",[]]Ai[\"{{{AHO`{KI`}}}KI`}\",[]]Ak[\"{{{AHO`{BGIj}}}BGIj}\",[]]Ai[\"{{{AHO`{KFn}}}KFn}\",[]]Ak[\"{{{AHO`{AINj}}}AINj}\",[]]Ak[\"{{{AHO`{AHEh}}}AHEh}\",[]]Ak[\"{{{AHO`{BFKd}}}BFKd}\",[]]Ak[\"{{{AHO`{AIKd}}}AIKd}\",[]]Ak[\"{{{AHO`{ABDn}}}ABDn}\",[]]Ak[\"{{{AHO`{AILn}}}AILn}\",[]]Ak[\"{{{AHO`{AHEl}}}AHEl}\",[]]Ak[\"{{{AHO`{BCMf}}}BCMf}\",[]]Ak[\"{{{AHO`{B@Ld}}}B@Ld}\",[]]Ak[\"{{{AHO`{BCLn}}}BCLn}\",[]]Ak[\"{{{AHO`{AHE`}}}AHE`}\",[]]Ak[\"{{{AHO`{BBLd}}}BBLd}\",[]]Ak[\"{{{AHO`{BCOl}}}BCOl}\",[]]Ak[\"{{{AHO`{BFGh}}}BFGh}\",[]]Ak[\"{{{AHO`{BFIf}}}BFIf}\",[]]Ak[\"{{{AHO`{AOL`}}}AOL`}\",[]]Ak[\"{{{AHO`{BBMh}}}BBMh}\",[]]Ak[\"{{{AHO`{BABd}}}BABd}\",[]]Ak[\"{{{AHO`{BH@l}}}BH@l}\",[]]Ak[\"{{{AHO`{B@Kh}}}B@Kh}\",[]]Ak[\"{{{AHO`{AOJd}}}AOJd}\",[]]Ak[\"{{{AHO`{AHCf}}}AHCf}\",[]]Ak[\"{{{AHO`{BCLh}}}BCLh}\",[]]Ak[\"{{{AHO`{BEFf}}}BEFf}\",[]]Ak[\"{{{AHO`{B@M`}}}B@M`}\",[]]Ak[\"{{{AHO`{B@Mf}}}B@Mf}\",[]]Ak[\"{{{AHO`{B@N`}}}B@N`}\",[]]Ak[\"{{{AHO`{BFKn}}}BFKn}\",[]]Ak[\"{{{AHO`{AIMd}}}AIMd}\",[]]Ai[\"{{{AHO`{DAl}}}DAl}\",[]]Ai[\"{{{AHO`{A@j}}}A@j}\",[]]Ak[\"{{{AHO`{ABCd}}}ABCd}\",[]]Ak[\"{{{AHO`{AHCb}}}AHCb}\",[]]Ai[\"{{{AHO`{CNj}}}CNj}\",[]]Ak[\"{{{AHO`{AIJl}}}AIJl}\",[]]Ai[\"{{{AHO`{IDh}}}IDh}\",[]]Ak[\"{{{AHO`{AOJn}}}AOJn}\",[]]Ai[\"{{{AHO`{NKl}}}NKl}\",[]]Ak[\"{{{AHO`{BBAl}}}BBAl}\",[]]Ai[\"{{{AHO`{IFb}}}IFb}\",[]]Ak[\"{{{AHO`{BEJb}}}BEJb}\",[]]Ak[\"{{{AHO`{BGHn}}}BGHn}\",[]]Ak[\"{{{AHO`{BEJf}}}BEJf}\",[]]Ai[\"{{{AHO`{KIb}}}KIb}\",[]]Ak[\"{{{AHO`{BGIl}}}BGIl}\",[]]Ak[\"{{{AHO`{BFDf}}}BFDf}\",[]]Ai[\"{{{AHO`{KG`}}}KG`}\",[]]Ak[\"{{{AHO`{BAAl}}}BAAl}\",[]]Ak[\"{{{AHO`{AINl}}}AINl}\",[]]Ak[\"{{{AHO`{AHEj}}}AHEj}\",[]]Ak[\"{{{AHO`{BFDb}}}BFDb}\",[]]Ak[\"{{{AHO`{BFKf}}}BFKf}\",[]]Ak[\"{{{AHO`{AIKj}}}AIKj}\",[]]Ak[\"{{{AHO`{ABE`}}}ABE`}\",[]]Ak[\"{{{AHO`{AIM`}}}AIM`}\",[]]Ak[\"{{{AHO`{AHEn}}}AHEn}\",[]]Ak[\"{{{AHO`{BCMh}}}BCMh}\",[]]Ak[\"{{{AHO`{AHFd}}}AHFd}\",[]]Ak[\"{{{AHO`{BBBl}}}BBBl}\",[]]Ak[\"{{{AHO`{AJAn}}}AJAn}\",[]]Ak[\"{{{AHO`{AOKn}}}AOKn}\",[]]Ak[\"{{{AHO`{BC@j}}}BC@j}\",[]]Ak[\"{{{AHO`{B@Ll}}}B@Ll}\",[]]Ak[\"{{{AHO`{BCM`}}}BCM`}\",[]]Ak[\"{{{AHO`{AHEb}}}AHEb}\",[]]Ak[\"{{{AHO`{BBLf}}}BBLf}\",[]]Ak[\"{{{AHO`{BHDj}}}BHDj}\",[]]Ak[\"{{{AHO`{BCOn}}}BCOn}\",[]]Ak[\"{{{AHO`{BFGj}}}BFGj}\",[]]Ak[\"{{{AHO`{BFIh}}}BFIh}\",[]]Ak[\"{{{AHO`{AOLb}}}AOLb}\",[]]Ak[\"{{{AHO`{BBMj}}}BBMj}\",[]]Ak[\"{{{AHO`{BABh}}}BABh}\",[]]Ak[\"{{{AHO`{BH@n}}}BH@n}\",[]]Ak[\"{{{AHO`{B@Kl}}}B@Kl}\",[]]Ak[\"{{{AHO`{AOJf}}}AOJf}\",[]]Ak[\"{{{AHO`{AHCh}}}AHCh}\",[]]Ak[\"{{{AHO`{AIMl}}}AIMl}\",[]]Ak[\"{{{AHO`{AJ@h}}}AJ@h}\",[]]Ak[\"{{{AHO`{B@Mj}}}B@Mj}\",[]]Ak[\"{{{AHO`{BCLl}}}BCLl}\",[]]Ak[\"{{{AHO`{BEFj}}}BEFj}\",[]]Ak[\"{{{AHO`{AAOl}}}AAOl}\",[]]Ak[\"{{{AHO`{BEGb}}}BEGb}\",[]]Ak[\"{{{AHO`{B@Mb}}}B@Mb}\",[]]Ak[\"{{{AHO`{BCNh}}}BCNh}\",[]]Ak[\"{{{AHO`{BHE`}}}BHE`}\",[]]Ak[\"{{{AHO`{ABAh}}}ABAh}\",[]]Ak[\"{{{AHO`{BFCl}}}BFCl}\",[]]Ak[\"{{{AHO`{B@Mh}}}B@Mh}\",[]]Ak[\"{{{AHO`{B@Nb}}}B@Nb}\",[]]Ak[\"{{{AHO`{BCAb}}}BCAb}\",[]]Ak[\"{{{AHO`{BFL`}}}BFL`}\",[]]Ak[\"{{{AHO`{AIMf}}}AIMf}\",[]]Ak[\"{{{AHO`{BIAl}}}BIAl}\",[]]Ai[\"{{{AHO`{DAn}}}DAn}\",[]]Ai[\"{{{AHO`{A@l}}}A@l}\",[]]Ak[\"{{{AHO`{ABCf}}}ABCf}\",[]]Ak[\"{{{AHO`{AHCd}}}AHCd}\",[]]Ak[\"{{{AHO`{BBCh}}}BBCh}\",[]]Ak[\"{{{AHO`{BGAn}}}BGAn}\",[]]Ai[\"{{{AHO`{IFf}}}IFf}\",[]]Ai[\"{{{AHO`{IFd}}}IFd}\",[]]Ak[\"{{{AHO`{BEJd}}}BEJd}\",[]]Ak[\"{{{AHO`{BGI`}}}BGI`}\",[]]Ak[\"{{{AHO`{BEJh}}}BEJh}\",[]]Ai[\"{{{AHO`{CNl}}}CNl}\",[]]Ak[\"{{{AHO`{AIJn}}}AIJn}\",[]]Ai[\"{{{AHO`{IDj}}}IDj}\",[]]Ak[\"{{{AHO`{AOK`}}}AOK`}\",[]]Ai[\"{{{AHO`{NKn}}}NKn}\",[]]Ak[\"{{{AHO`{BBAn}}}BBAn}\",[]]Ai[\"{{{AHO`{IFn}}}IFn}\",[]]Ak[\"{{{AHO`{ABC`}}}ABC`}\",[]]Ak[\"{{{AHO`{ABDl}}}ABDl}\",[]]Ak[\"{{{AHO`{B@Nd}}}B@Nd}\",[]]Ak[\"{{{AHO`{BG@n}}}BG@n}\",[]]Ak[\"{{{AHO`{BCN`}}}BCN`}\",[]]Ak[\"{{{AHO`{BHKb}}}BHKb}\",[]]Ak[\"{{{AHO`{BHKd}}}BHKd}\",[]]Bn[\"{{{AHO`{AILb}}KI`{AHO`{{K@j{Oh}}}}}ICj}\",[]]Bn[\"{{{AHO`{AB@h}}KI`{AHO`{{K@j{Oh}}}}}ICj}\",[]]Bf[\"{{{AHO`{DA`}}}{{KGd{ICjBBLd}}}}\",[]]Bf[\"{{{AHO`{CGbAILd}}NK`KIbICn}ICj}\",[]]Bj[\"{{{AHO`{B@Mf}}}{{AHO`{{K@j{Oh}}}}}}\",[]]Bj[\"{{{AHO`{B@Mh}}}{{AHO`{{K@j{Oh}}}}}}\",[]]Aa[\"{BCAbAIMl}\",[]]A`[\"{ABCdCNb}\",[]]A`[\"{ABCfCNb}\",[]]A`[\"{B@N`DC`}\",[]]A`[\"{B@NbDC`}\",[]]l[\"{NKb}\",[]]l[\"{NKd}\",[]]Cf[\"{{{AHO`{BFHd}}{AHO`{CGbA@f}}}{{KGd{B@MhBFCl}}}}\",[]]Cf[\"{{{AHO`{BIE`}}{AHO`{CGbA@f}}}{{KGd{B@MhBFCl}}}}\",[]]Bc[\"{{{AHO`{AILd}}}{{ABh{KG`}}}}\",[]]Bd[\"{{{AHO`{IHj}}{AHO`{CMl}}}ICj}\",[]]Am[\"{{{AHO`{AOJ`}}KCb}ICj}\",[]]Ai[\"{{{AHO`{IHl}}}ICj}\",[]]Bg[\"{{{ABb{{AHO`{CGbAIJd}}}}NK`}ICj}\",[]]Bm[\"{{{AHO`{CGbAJ@`}}KI`}{{KGd{ICjB@Ld}}}}\",[]]Bm[\"{{{AHO`{CGbAJ@b}}KIb}{{KGd{ICjB@Ll}}}}\",[]]Am[\"{ABA`{{KGd{ICjAIOj}}}}\",[]]Dc[\"{{{AHO`{CGbAB@j}}ABAb{AHO`{CGb{ABh{Oh}}}}}{{KGd{ABDjAIOl}}}}\",[]]Bj[\"{{{AHO`{NMd}}{ABb{{A@d{KEn}}}}}ICj}\",[]]Bk[\"{{{AHO`{BACb}}{ABb{{A@d{KEn}}}}}ICj}\",[]]Bk[\"{{{AHO`{AOMl}}{ABb{{A@d{KEn}}}}}ICj}\",[]]o[\"{AAjDC`}\",[]]Ak[\"{{{AHO`{AILb}}}BCMf}\",[]]Ak[\"{{{AHO`{AB@h}}}BAOf}\",[]]Aj[\"{{{AHO`{DA`}}}AHEl}\",[]]A`[\"{KEdBCMf}\",[]]An[\"{{{AHO`{CGbAILd}}}BCMh}\",[]]Ce[\"{{{AHO`{CGbAINn}}{KFl{KHl}}}{{AHO`{CGbAINn}}}}\",[]]Ce[\"{{{AHO`{CGbAIO`}}{KFl{KHl}}}{{AHO`{CGbAIO`}}}}\",[]]Ck[\"{{{AHO`{CGbAJ@`}}{AHO`{{K@j{Oh}}}}}{{KGd{KCbAJAl}}}}\",[]]Ck[\"{{{AHO`{CGbAJ@b}}{AHO`{{K@j{Oh}}}}}{{KGd{KCbAJAn}}}}\",[]]Bl[\"{{{AHO`{BGAn}}{AHO`{CGbc}}}ICjKDj}\",[\"W\"]]Aj[\"{{{AHO`{AB@h}}}KCj}\",[]]An[\"{ABA`{{KGd{AIKnBCLn}}}}\",[]]Bi[\"{{{AHO`{CGbNNf}}A@l}{{KFl{ABCf}}}}\",[]]Fj[\"{{{AHO`{CGbAB@j}}ABAbNK`{AHO`{CGb{ABh{Oh}}}}{KFl{{A@b{BABh}}}}}{{KGd{{KC`{BEGbAILd}}{A@d{AOIj}}}}}}\",[]]Am[\"{AHO`{{AHO`{c}}}{}}\",[\"T\"]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A`[\"{AAjAOIn}\",[]]Bc[\"{{AJ@fBBLbAAj}{{KGh{AOJ`}}}}\",[]]Aj[\"{AJ@f{{KGf{AB@h}}}}\",[]]Ba[\"{{}{{KGd{ICj{A@d{IEd}}}}}}\",[]]Ak[\"{{{AHO`{AILb}}}BCLn}\",[]]Be[\"{{{AHO`{AB@j}}}{{AHO`{BBMj}}}}\",[]]o[\"{OhBFKn}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{BABdA@b}\",[]]o[\"{OhBFL`}\",[]]A`[\"{AIM`CNb}\",[]]A`[\"{BABhA@b}\",[]]A`[\"{BCAbKFl}\",[]]Bm[\"{{{AHO`{CGbc}}}{{KGd{KI`BBCh}}}A@f}\",[\"B\"]]Bm[\"{{{AHO`{CGbc}}}{{KGd{KFnBBCh}}}A@f}\",[\"B\"]]Bn[\"{{{AHO`{CGbc}}}{{KGd{AHCfBBCh}}}A@f}\",[\"B\"]]Bn[\"{{{AHO`{CGbc}}}{{KGd{BFKnBBCh}}}A@f}\",[\"B\"]]Bn[\"{{{AHO`{CGbc}}}{{KGd{ABCdBBCh}}}A@f}\",[\"B\"]]Bi[\"{{{AHO`{CGbc}}}{{KGl{KIb}}}A@f}\",[\"B\"]]Bi[\"{{{AHO`{CGbc}}}{{KGl{KG`}}}A@f}\",[\"B\"]]Bj[\"{{{AHO`{CGbc}}}{{KGl{AHCh}}}A@f}\",[\"B\"]]Bj[\"{{{AHO`{CGbc}}}{{KGl{AIMl}}}A@f}\",[\"B\"]]Ek[\"{{{AHO`{CGb{KEl{AAOb}}}}{AHO`{c}}{AHO`{{K@j{CN`}}}}DC`}{{KGd{BCNhBFCl}}}{BFHdIHh}}\",[\"\"]]Bj[\"{{{AHO`{CGbc}}}{{KGl{BFL`}}}A@f}\",[\"B\"]]Bj[\"{{{AHO`{CGbc}}}{{KGl{ABCf}}}A@f}\",[\"B\"]]Bj[\"{{{AHO`{CGbc}}}{{KGl{AHBd}}}A@f}\",[\"B\"]]A`[\"{ABC`KFl}\",[]]Bk[\"{{{AHO`{KI`}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bk[\"{{{AHO`{KFn}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bl[\"{{{AHO`{AHCf}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bl[\"{{{AHO`{BFKn}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bl[\"{{{AHO`{ABCd}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bk[\"{{{AHO`{KIb}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bk[\"{{{AHO`{KG`}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bl[\"{{{AHO`{AHCh}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bl[\"{{{AHO`{AIMl}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bl[\"{{{AHO`{AAOl}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bl[\"{{{AHO`{BFL`}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bl[\"{{{AHO`{ABCf}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bl[\"{{{AHO`{AHBn}}{AHO`{CGbc}}}ICjKDj}\",[\"B\"]]Bj[\"{{{AHO`{CGbAJ@`}}}{{KGd{ICjB@Ld}}}}\",[]]Bj[\"{{{AHO`{CGbAJ@b}}}{{KGd{ICjAOKn}}}}\",[]]An[\"{{{AHO`{AONh}}AB@d}ICj}\",[]]Ea[\"{{{AHO`{CGbAB@j}}NK`AHCd{KFl{B@Nb}}AAOb{AHO`{CGb{ABh{Oh}}}}}{{KFl{BAOb}}}}\",[]]o[\"{D@dNKb}\",[]]o[\"{D@fNKd}\",[]]A`[\"{ABA`ICj}\",[]]Ba[\"{{{AHO`{CGbAB@j}}ABAb}ICj}\",[]]Bh[\"{{{AHO`{AJAf}}{AHO`{CMl}}ICn}ICj}\",[]]Bh[\"{{{AHO`{BBOd}}{AHO`{CMl}}ICn}ICj}\",[]]Bh[\"{{{AHO`{BEJj}}{AHO`{CMl}}ICn}ICj}\",[]]Bh[\"{{{AHO`{AJAh}}{AHO`{CMl}}ICn}ICj}\",[]]Bh[\"{{{AHO`{BBOf}}{AHO`{CMl}}ICn}ICj}\",[]]Bh[\"{{{AHO`{BEJl}}{AHO`{CMl}}ICn}ICj}\",[]]A`[\"{AHAlDC`}\",[]]o[\"{ID`CNb}\",[]]o[\"{IDbCNb}\",[]]Ch[\"{{{AHO`{CGbAILf}}NK`NK`CNbCNbDC`{AHO`{BAAj}}}ICj}\",[]]Cg[\"{{{AHO`{CGbCNj}}NK`NK`CNbCNbDC`{AHO`{BAAj}}}ICj}\",[]]Cg[\"{{{AHO`{CGbIDh}}NK`NK`CNbCNbDC`{AHO`{BAAj}}}ICj}\",[]]Cg[\"{{{AHO`{CGbNKl}}NK`NK`CNbCNbDC`{AHO`{BAAj}}}ICj}\",[]]Ch[\"{{{AHO`{CGbAILh}}NK`NK`CNbCNbDC`{AHO`{BAAl}}}ICj}\",[]]Cg[\"{{{AHO`{CGbCNl}}NK`NK`CNbCNbDC`{AHO`{BAAl}}}ICj}\",[]]Cg[\"{{{AHO`{CGbIDj}}NK`NK`CNbCNbDC`{AHO`{BAAl}}}ICj}\",[]]Cg[\"{{{AHO`{CGbNKn}}NK`NK`CNbCNbDC`{AHO`{BAAl}}}ICj}\",[]]o[\"{D@dNKb}\",[]]o[\"{D@fNKd}\",[]]o[\"{BEKfOh}\",[]]A`[\"{KEdBCLn}\",[]]A`[\"{BCLhICn}\",[]]A`[\"{BEFfICn}\",[]]Aa[\"{AH@`BFD`}\",[]]A`[\"{BCLlICn}\",[]]A`[\"{BEFjICn}\",[]]A`[\"{BCAbKHl}\",[]]Aa[\"{BBLjBCM`}\",[]]Aa[\"{AH@bBFDb}\",[]]Bg[\"{{{AHO`{AB@h}}AHFl}{{KGf{ICj}}}}\",[]]A`[\"{ABA`ICj}\",[]]Cf[\"{{{AHO`{CGbAB@j}}ABAb{AHO`{CGb{ABh{Oh}}}}}ABDj}\",[]]Ak[\"{{{AHO`{AHCb}}}AJ@f}\",[]]l[\"{NKb}\",[]]Ak[\"{{{AHO`{AHCd}}}AJ@f}\",[]]l[\"{NKd}\",[]]An[\"{{AB@fAAj}{{KGh{ICj}}}}\",[]]B`[\"{{BABdAJ@f}{{KGf{AB@h}}}}\",[]]Bc[\"{BAB`{{KGd{ICj{A@d{IEd}}}}}}\",[]]Bk[\"{{{AHO`{BFDd}}}{{KFl{{AHO`{IEd}}}}}}\",[]]Bk[\"{{{AHO`{BC@h}}}{{KFl{{AHO`{IEd}}}}}}\",[]]Bk[\"{{{AHO`{AHFb}}}{{KFl{{AHO`{IEd}}}}}}\",[]]Bk[\"{{{AHO`{AIOh}}}{{KFl{{AHO`{IEd}}}}}}\",[]]Bk[\"{{{AHO`{BC@f}}}{{KFl{{AHO`{IEd}}}}}}\",[]]Bk[\"{{{AHO`{AJAl}}}{{KFl{{AHO`{IEd}}}}}}\",[]]Bk[\"{{{AHO`{BAC`}}}{{KFl{{AHO`{IEd}}}}}}\",[]]Bk[\"{{{AHO`{BCLn}}}{{KFl{{AHO`{IEd}}}}}}\",[]]Bk[\"{{{AHO`{BCM`}}}{{KFl{{AHO`{IEd}}}}}}\",[]]A`[\"{ABDhKFl}\",[]]A`[\"{ABDjKFl}\",[]]A`[\"{ABDlKFl}\",[]]Bg[\"{{{AHO`{DA`}}}{{KGd{AINjAIKd}}}}\",[]]Aa[\"{BAAbAINj}\",[]]Aa[\"{BAAdAINl}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]A`[\"{ABC`KCb}\",[]]Aa[\"{AHElABDn}\",[]]Aa[\"{BCMfABDn}\",[]]Aa[\"{AHEnABE`}\",[]]Aa[\"{BCMhABE`}\",[]]3210Dh[\"{{{AHO`{NJj}}{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}{{KGd{ICjAOJj}}}}\",[]]Dh[\"{{{AHO`{NJl}}{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}{{KGd{ICjAOJl}}}}\",[]]Aj[\"{{{AHO`{AILf}}}CNb}\",[]]Ai[\"{{{AHO`{CNj}}}CNb}\",[]]Ai[\"{{{AHO`{IDh}}}CNb}\",[]]Ai[\"{{{AHO`{NKl}}}CNb}\",[]]Aj[\"{{{AHO`{AILh}}}CNb}\",[]]Ai[\"{{{AHO`{CNl}}}CNb}\",[]]Ai[\"{{{AHO`{IDj}}}CNb}\",[]]Ai[\"{{{AHO`{NKn}}}CNb}\",[]]Aj[\"{{{AHO`{BGOj}}}KCb}\",[]]Aj[\"{{{AHO`{BGOl}}}KCb}\",[]]Aj[\"{{{AHO`{BIAb}}}KCb}\",[]]Aj[\"{{{AHO`{BIAl}}}KCb}\",[]]o[\"{AAjKCb}\",[]]n[\"{{}NIl}\",[]]0A`[\"{A@hAAOf}\",[]]111Ba[\"{{AHO`{AHO`{c}}}ABBj{}}\",[\"K\"]]0000000000000000000Cg[\"{{{AHO`{AB@h}}AJ@f{AHO`{CMl}}}{{KGd{AIKnB@M`}}}}\",[]]Dl[\"{{{AHO`{CGbAB@j}}NK`B@KlAJ@f{AHO`{CMl}}}{{KGd{{KC`{BEGbAILd}}B@Mb}}}}\",[]]Bd[\"{{{AHO`{ABA`}}}{{KFl{BEGf}}}}\",[]]Dm[\"{{{AHO`{AHDj}}KFnCNb{AHO`{{K@j{Oh}}}}{AHO`{CGbAAOb}}}{{KGd{ICjAOJj}}}}\",[]]Ca[\"{{{AHO`{AHCl}}KCb{AHO`{CGb{K@j{Oh}}}}}ICj}\",[]]Bd[\"{{{AHO`{ABAb}}}{{KFl{BEGh}}}}\",[]]Dm[\"{{{AHO`{AHDl}}KG`CNb{AHO`{{K@j{Oh}}}}{AHO`{CGbAAOb}}}{{KGd{ICjAOJl}}}}\",[]]Ca[\"{{{AHO`{AHCn}}KCb{AHO`{CGb{K@j{Oh}}}}}ICj}\",[]]o[\"{{}AONf}\",[]]n[\"{{}IHj}\",[]]o[\"{{}AOJ`}\",[]]o[\"{{}BANd}\",[]]o[\"{{}BAOf}\",[]]n[\"{{}KI`}\",[]]n[\"{{}KFn}\",[]]o[\"{{}AINj}\",[]]o[\"{{}AIKd}\",[]]o[\"{{}ABDn}\",[]]o[\"{{}AILn}\",[]]o[\"{{}AHEl}\",[]]o[\"{{}BCMf}\",[]]o[\"{{}B@Ld}\",[]]o[\"{{}BCOl}\",[]]o[\"{{}BFGh}\",[]]o[\"{{}BFIf}\",[]]o[\"{{}AOL`}\",[]]o[\"{{}BBMh}\",[]]o[\"{{}BH@l}\",[]]o[\"{{}BEJj}\",[]]o[\"{{}AIJl}\",[]]o[\"{{}AOJn}\",[]]o[\"{{}BBAl}\",[]]o[\"{{}BFBj}\",[]]o[\"{{}BANf}\",[]]o[\"{{}BIAb}\",[]]n[\"{{}KIb}\",[]]n[\"{{}KG`}\",[]]o[\"{{}AINl}\",[]]o[\"{{}AIKj}\",[]]o[\"{{}ABE`}\",[]]o[\"{{}AIM`}\",[]]o[\"{{}AHEn}\",[]]o[\"{{}BCMh}\",[]]o[\"{{}BC@j}\",[]]o[\"{{}B@Ll}\",[]]o[\"{{}BCOn}\",[]]o[\"{{}BFGj}\",[]]o[\"{{}BFIh}\",[]]o[\"{{}AOLb}\",[]]o[\"{{}BBMj}\",[]]o[\"{{}BH@n}\",[]]o[\"{{}BIAl}\",[]]o[\"{{}BEJl}\",[]]o[\"{{}AIJn}\",[]]o[\"{{}AOK`}\",[]]o[\"{{}BBAn}\",[]]o[\"{{}BFBl}\",[]]o[\"{{}ABC`}\",[]]n[\"{{}IHl}\",[]]o[\"{{}BCN`}\",[]]o[\"{{}BAOd}\",[]]Ak[\"{{{AHO`{BBBb}}}B@Mh}\",[]]Ak[\"{{{AHO`{BCNh}}}B@Mh}\",[]]Aa[\"{BHE`B@Mh}\",[]]Aa[\"{BFLbB@Mh}\",[]]A`[\"{D@hB@Mh}\",[]]A`[\"{IH`B@Mh}\",[]]A`[\"{IHdB@Mh}\",[]]Aa[\"{BEKfB@Mh}\",[]]Cg[\"{{{AHO`{AHDj}}KFnCNb{AHO`{CGb{K@j{Oh}}}}KCb}ICj}\",[]]Ca[\"{{{AHO`{AHCl}}KCb{AHO`{CGb{K@j{Oh}}}}}ICj}\",[]]Cg[\"{{{AHO`{AHDl}}KG`CNb{AHO`{CGb{K@j{Oh}}}}KCb}ICj}\",[]]Ca[\"{{{AHO`{AHCn}}KCb{AHO`{CGb{K@j{Oh}}}}}ICj}\",[]]Aj[\"{{{AHO`{AIOd}}}DC`}\",[]]Aj[\"{{{AHO`{BGB`}}}DC`}\",[]]Ak[\"{{{AHO`{AILf}}}BFBj}\",[]]Aj[\"{{{AHO`{CNj}}}BFBj}\",[]]Aj[\"{{{AHO`{IDh}}}BFBj}\",[]]Aj[\"{{{AHO`{NKl}}}BFBj}\",[]]Ak[\"{{{AHO`{AILh}}}BFBl}\",[]]Aj[\"{{{AHO`{CNl}}}BFBl}\",[]]Aj[\"{{{AHO`{IDj}}}BFBl}\",[]]Aj[\"{{{AHO`{NKn}}}BFBl}\",[]]Bn[\"{{{AHO`{CGbBCOl}}CMn}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}CMn}{{AHO`{CGbBCOn}}}}\",[]]Bf[\"{{{AHO`{CGbAILf}}NK`CNbCNb}ICj}\",[]]Bf[\"{{{AHO`{CGbAILh}}NK`CNbCNb}ICj}\",[]]Aj[\"{{{AHO`{AILb}}}KFf}\",[]]Bc[\"{{{AHO`{ABBh}}}{{KFl{KFn}}}}\",[]]Aa[\"{BHE`B@Mh}\",[]]Aa[\"{BFLbB@Mh}\",[]]A`[\"{D@hB@Mh}\",[]]A`[\"{IH`B@Mh}\",[]]Aa[\"{BEKfB@Mh}\",[]]Aj[\"{{{AHO`{AJ@`}}}NMn}\",[]]Bn[\"{{{AHO`{AJ@b}}}{{KGd{{KFl{KIb}}B@Ll}}}}\",[]]o[\"{AAjKCb}\",[]]o[\"{IHjKCb}\",[]]Am[\"{{{AHO`{CGbAILd}}}NNf}\",[]]Aj[\"{{{AHO`{AHDj}}}KCb}\",[]]Aj[\"{{{AHO`{AHDl}}}KCb}\",[]]A`[\"{AHO`KHn}\",[]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Bd[\"{{{AHO`{BGJ`}}}{{KFl{AILb}}}}\",[]]Bc[\"{{{AHO`{BCAf}}}{{KFl{DA`}}}}\",[]]Bn[\"{{{AHO`{CGbB@Kh}}CN`}{{AHO`{CGbB@Kh}}}}\",[]]Bn[\"{{{AHO`{CGbB@Kl}}CN`}{{AHO`{CGbB@Kl}}}}\",[]]A`[\"{BHE`CN`}\",[]]A`[\"{BFLbCN`}\",[]]o[\"{D@hCN`}\",[]]o[\"{IH`CN`}\",[]]A`[\"{AHAlKFl}\",[]]Aa[\"{ABDlAHO`}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]Aa[\"{BC@lAB@d}\",[]]A`[\"{KDlBC@j}\",[]]Aa[\"{AHElAILn}\",[]]Aa[\"{BCMfAILn}\",[]]Aa[\"{AHEnAIM`}\",[]]Aa[\"{BCMhAIM`}\",[]]3210Bh[\"{{{AHO`{CGbc}}KCb}B@Mf{A@fIHh}}\",[\"\"]]Bh[\"{{{AHO`{CGbc}}KCb}B@Mh{A@fIHh}}\",[\"\"]]o[\"{{}BCNj}\",[]]o[\"{{}BCNl}\",[]]A`[\"{CNbBIAb}\",[]]Cd[\"{{{AHO`{CMl}}}{{KGh{AOMfc}}}{}}\",[\"FromStr::Err\"]]Cd[\"{{{AHO`{CMl}}}{{KGh{AOInc}}}{}}\",[\"FromStr::Err\"]]o[\"{CN`KI`}\",[]]o[\"{CN`KIb}\",[]]Al[\"{CNb{{KGd{KI`BGIj}}}}\",[]]Al[\"{CNb{{KGd{KIbBGIl}}}}\",[]]Aa[\"{BGGnAOJh}\",[]]Aa[\"{BIEbBB@b}\",[]]Aa[\"{BGH`AOJh}\",[]]Aa[\"{BIEdBB@b}\",[]]Af[\"{{}{{KFl{Ab}}}}\",[]]0000Aj[\"{{{AHO`{AILd}}}DC`}\",[]]Bo[\"{{{AHO`{CGbBFIf}}AB@d}{{AHO`{CGbBFIf}}}}\",[]]Bo[\"{{{AHO`{CGbBFIh}}AB@d}{{AHO`{CGbBFIh}}}}\",[]]Bb[\"{{{A@d{AILf}}}{{A@d{A@`}}}}\",[]]Ba[\"{{{A@d{CNj}}}{{A@d{A@`}}}}\",[]]Ba[\"{{{A@d{IDh}}}{{A@d{A@`}}}}\",[]]Ba[\"{{{A@d{NKl}}}{{A@d{A@`}}}}\",[]]Bb[\"{{{A@d{AILh}}}{{A@d{A@`}}}}\",[]]Ba[\"{{{A@d{CNl}}}{{A@d{A@`}}}}\",[]]Ba[\"{{{A@d{IDj}}}{{A@d{A@`}}}}\",[]]Ba[\"{{{A@d{NKn}}}{{A@d{A@`}}}}\",[]]Aj[\"{{{AHO`{BGJ`}}}DC`}\",[]]Bo[\"{{{AHO`{CGbBH@l}}AB@d}{{AHO`{CGbBH@l}}}}\",[]]Bo[\"{{{AHO`{CGbBH@n}}AB@d}{{AHO`{CGbBH@n}}}}\",[]]Bc[\"{{{AHO`{AIKn}}}{{KFl{KFb}}}}\",[]]Bc[\"{{{AHO`{ABA`}}}{{KFl{KFb}}}}\",[]]Bm[\"{{{AHO`{DA`}}}{{KGd{{KFl{KFb}}AIKd}}}}\",[]]Bc[\"{{{AHO`{AHCb}}}{{KFl{KFb}}}}\",[]]Bc[\"{{{AHO`{ABAb}}}{{KFl{KFb}}}}\",[]]Bc[\"{{{AHO`{AHCd}}}{{KFl{KFb}}}}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]Bc[\"{{{AHO`{AHBb}}}{{KFl{KCb}}}}\",[]]Am[\"{{{AHO`{AONh}}KCb}ICj}\",[]]Aj[\"{{{AHO`{AILb}}}NLb}\",[]]Bg[\"{{{AHO`{AJ@`}}}{{KGd{CElB@Ld}}}}\",[]]Bg[\"{{{AHO`{AJ@b}}}{{KGd{CElB@Ll}}}}\",[]]A`[\"{BAOjKFl}\",[]]A`[\"{BAOlKFl}\",[]]A`[\"{AHAlKFl}\",[]]A`[\"{AOIjKFl}\",[]]Ck[\"{{{AHO`{CGbBBMh}}{KFl{{IIh{Oh}}}}}{{AHO`{CGbBBMh}}}}\",[]]Ck[\"{{{AHO`{CGbBBMj}}{KFl{{IIh{Oh}}}}}{{AHO`{CGbBBMj}}}}\",[]]A`[\"{BFBjKFl}\",[]]A`[\"{BFBlKFl}\",[]]Ac[\"{AHO`c{}}\",[\"T\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Ao[\"{c{{KGd{e}}}{}{}}\",[\"U\",\"T\"]]000000000000000000000000000000000000000000000000000000Al[\"{CNb{{KGd{KI`BGIj}}}}\",[]]Al[\"{ICh{{KGd{KI`BGIj}}}}\",[]]Al[\"{KCb{{KGd{KI`BGIj}}}}\",[]]3333333333333333333Aj[\"{AB@d{{KGd{AOL`}}}}\",[]]444444Ai[\"{CNb{{KGd{AHCf}}}}\",[]]55555555555555555555555555555Bc[\"{{{A@b{B@Kf}}}{{KGd{BEJb}}}}\",[]]Aj[\"{B@Kf{{KGd{BEJb}}}}\",[]]77Bc[\"{{{A@b{BABb}}}{{KGd{BEJf}}}}\",[]]Aj[\"{BABb{{KGd{BEJf}}}}\",[]]999999999999999999999Al[\"{ICh{{KGd{KIbBGIl}}}}\",[]]Al[\"{KCb{{KGd{KIbBGIl}}}}\",[]];Al[\"{CNb{{KGd{KIbBGIl}}}}\",[]]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Bm[\"{B@Kf{{KGd{BEJdc}}}{}}\",[\"TryFrom::Error\"]]Cf[\"{{{A@b{B@Kf}}}{{KGd{BEJdc}}}{}}\",[\"TryFrom::Error\"]]Ao[\"{c{{KGd{e}}}{}{}}\",[\"U\",\"T\"]]00Cf[\"{{{A@b{BABb}}}{{KGd{BEJhc}}}{}}\",[\"TryFrom::Error\"]]Bm[\"{BABb{{KGd{BEJhc}}}{}}\",[\"TryFrom::Error\"]]22222222222222222222222Aj[\"{{}{{KGd{c}}}{}}\",[\"U\"]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cd[\"{{{ABb{{AHO`{CGbc}}}}{AHO`{CGbNIn}}}DAh{}}\",[\"F\"]]000000000Cc[\"{{{AHO`{BCAd}}AJAj{AHO`{ABDl}}}{{KGf{ICj}}}}\",[]]Bk[\"{{{AHO`{BGOj}}B@Mf}{{KGd{ICjAIMd}}}}\",[]]Bk[\"{{{AHO`{BGOl}}B@Mh}{{KGd{ICjAIMf}}}}\",[]]0Bk[\"{{{AHO`{BIAb}}B@Mh}{{KGd{ICjAIMf}}}}\",[]]Ak[\"{{{AHO`{AILb}}}AAO`}\",[]]Bi[\"{{{AHO`{CGbBACd}}}{{KGd{KI`IF`}}}}\",[]]Bi[\"{{{AHO`{CGbBACd}}}{{KGd{DAlIF`}}}}\",[]]Bi[\"{{{AHO`{CGbBACd}}}{{KGd{A@jIF`}}}}\",[]]Bj[\"{{{AHO`{CGbBACd}}}{{KGd{ABCdIF`}}}}\",[]]Bf[\"{{{AHO`{CGbBACd}}}{{KGj{KIb}}}}\",[]]Bf[\"{{{AHO`{CGbBACd}}}{{KGj{DAn}}}}\",[]]Bf[\"{{{AHO`{CGbBACd}}}{{KGj{A@l}}}}\",[]]Bg[\"{{{AHO`{CGbBACd}}}{{KGj{ABCf}}}}\",[]]Aa[\"{AOInBGIh}\",[]]Bd[\"{{{AHO`{AILf}}}{{A@d{AILf}}}}\",[]]Bc[\"{{{AHO`{CNj}}}{{A@d{AILf}}}}\",[]]Bc[\"{{{AHO`{IDh}}}{{A@d{AILf}}}}\",[]]Bc[\"{{{AHO`{NKl}}}{{A@d{AILf}}}}\",[]]Bd[\"{{{AHO`{AILh}}}{{A@d{AILh}}}}\",[]]Bc[\"{{{AHO`{CNl}}}{{A@d{AILh}}}}\",[]]Bc[\"{{{AHO`{IDj}}}{{A@d{AILh}}}}\",[]]Bc[\"{{{AHO`{NKn}}}{{A@d{AILh}}}}\",[]]A`[\"{ABDnCNb}\",[]]An[\"{{{AHO`{CGbAILd}}}AHBb}\",[]]A`[\"{ABE`CNb}\",[]]C`[\"{{{AHO`{CGbB@Mf}}}{{AHO`{CGb{K@j{Oh}}}}}}\",[]]C`[\"{{{AHO`{CGbB@Mh}}}{{AHO`{CGb{K@j{Oh}}}}}}\",[]]Ah[\"{Oh{{KFl{B@N`}}}}\",[]]Ah[\"{Oh{{KFl{B@Nb}}}}\",[]]Ah[\"{Oh{{KFl{B@Nd}}}}\",[]]A`[\"{ABCdDAl}\",[]]A`[\"{ABCfDAn}\",[]]Bi[\"{AIKn{{KGd{{KC`{AILbBD@f}}AIKn}}}}\",[]]Aa[\"{{}c{}}\",[\"I\"]]o[\"{DAlDC`}\",[]]o[\"{DAnDC`}\",[]]Aj[\"{{{AHO`{AILd}}}DC`}\",[]]21Aj[\"{{{AHO`{ABA`}}}DC`}\",[]]Aj[\"{{{AHO`{ABAb}}}DC`}\",[]]Bn[\"{{{AHO`{CGbBABd}}DC`}{{AHO`{CGbBABd}}}}\",[]]Bn[\"{{{AHO`{CGbBABh}}DC`}{{AHO`{CGbBABh}}}}\",[]]Bm[\"{{{AHO`{NMd}}NK`}{{ABb{{A@d{AIJd}}}}}}\",[]]Bn[\"{{{AHO`{BACb}}NK`}{{ABb{{A@d{AIJd}}}}}}\",[]]Bn[\"{{{AHO`{AOMl}}NK`}{{ABb{{A@d{AIJd}}}}}}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]Ak[\"{{{AHO`{AILb}}}ABBf}\",[]]Bo[\"{{{AHO`{AILb}}cAINj}ABBh{{D@b{AHCb}}}}\",[\"\"]]Ce[\"{{{AHO`{CGbAILd}}AHCdAINlNK`}{{KGd{KG`AHEb}}}}\",[]]43Bm[\"{{{AHO`{CGbAILd}}KG`}{{KGd{ICjAIKj}}}}\",[]]E`[\"{{{ABb{{AHO`{CGbBGIb}}}}{AHO`{CGbNIn}}}{{DAh{{KFl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABb{{AHO`{CGbBEFb}}}}{AHO`{CGbNIn}}}{{DAh{{KFl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABb{{AHO`{CGbAINh}}}}{AHO`{CGbNIn}}}{{DAh{{KFl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABb{{AHO`{CGbBGA`}}}}{AHO`{CGbNIn}}}{{DAh{{KFl{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{ABb{{AHO`{CGbAIOd}}}}{AHO`{CGbNIn}}{AHO`{CGbNLn}}}{{DAh{{KGf{ICj}}}}}}\",[]]Ec[\"{{{AHO`{CGbAIOd}}{AHO`{CGbNIn}}{AHO`{CGb{K@j{Oh}}}}}{{DAh{{KGd{KCbAHFb}}}}}}\",[]]Ef[\"{{{ABb{{AHO`{CGbAIOd}}}}{AHO`{CGbNIn}}{AHO`{CGb{K@j{Oh}}}}}{{DAh{{KGf{KCb}}}}}}\",[]]Fg[\"{{{AHO`{CGbBBL`}}{AHO`{CGbNIn}}{AHO`{CGb{K@j{AIMh}}}}{AHO`{CGb{K@j{ABC`}}}}}{{DAh{{KGf{KCb}}}}}}\",[]]Dn[\"{{{ABb{{AHO`{CGbAHFj}}}}{AHO`{ABDl}}{AHO`{CGbNIn}}}{{DAh{{KGf{ICj}}}}}}\",[]]>=Ce[\"{{{AHO`{CGbBBMh}}{A@b{NJj}}}{{AHO`{CGbBBMh}}}}\",[]]Ce[\"{{{AHO`{CGbBBMj}}{A@b{NJl}}}{{AHO`{CGbBBMj}}}}\",[]]Cj[\"{{{AHO`{BABf}}CN`B@Mf{AHO`{{K@j{Oh}}}}}{{IIh{Oh}}}}\",[]]Cj[\"{{{AHO`{BEJf}}CN`B@Mf{AHO`{{K@j{Oh}}}}}{{IIh{Oh}}}}\",[]]Cj[\"{{{AHO`{BABj}}CN`B@Mh{AHO`{{K@j{Oh}}}}}{{IIh{Oh}}}}\",[]]Cj[\"{{{AHO`{BEJh}}CN`B@Mh{AHO`{{K@j{Oh}}}}}{{IIh{Oh}}}}\",[]]Cj[\"{{{AHO`{BHKd}}CN`B@Md{AHO`{{K@j{Oh}}}}}{{IIh{Oh}}}}\",[]]Bb[\"{KCb{{KC`{KCb{KFl{KCb}}}}}}\",[]]000Aj[\"{{{AHO`{AILb}}}KCb}\",[]]A`[\"{AHO`KHl}\",[]]0000000000000000000000000000000000000000000000000000000000000000000Cf[\"{{{AHO`{CGbBABd}}{A@b{BFBn}}}{{AHO`{CGbBABd}}}}\",[]]Cf[\"{{{AHO`{CGbBABh}}{A@b{BFC`}}}{{AHO`{CGbBABh}}}}\",[]]A`[\"{BHE`IGf}\",[]]A`[\"{BABdA@b}\",[]]A`[\"{BABhA@b}\",[]]Aj[\"{{{AHO`{AB@h}}}ICj}\",[]]Ck[\"{{{AHO`{CGbAJ@`}}{AHO`{{K@j{Oh}}}}}{{KGd{ICjAJAl}}}}\",[]]n[\"{{}KCb}\",[]]Ak[\"{{{AHO`{AILb}}}AH@h}\",[]]Ba[\"{{{AHO`{CGbABDn}}ABDn}ICj}\",[]]Ba[\"{{{AHO`{CGbAILn}}AILn}ICj}\",[]]Ba[\"{{{AHO`{CGbBCMf}}AHEl}ICj}\",[]]Ba[\"{{{AHO`{CGbABE`}}ABE`}ICj}\",[]]Ba[\"{{{AHO`{CGbAIM`}}AIM`}ICj}\",[]]Ba[\"{{{AHO`{CGbBCMh}}AHEn}ICj}\",[]]Bn[\"{{{AHO`{CGbBCOl}}DC`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}DC`}{{AHO`{CGbBCOn}}}}\",[]]Bi[\"{{{AHO`{CGb}}}{{AHO`{CGbc}}}{}}\",[\"T\"]]000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Bg[\"{{{AHO`{AIOd}}}{{KGd{CNbB@Ld}}}}\",[]]Bg[\"{{{AHO`{AIOf}}}{{KGd{CNbB@Ll}}}}\",[]]Bc[\"{{AHO`{AHO`{CGbc}}}ICj{}}\",[\"T\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cc[\"{{{AHO`{CGbAILd}}NK`KG`KIb}{{KGd{ICjBBLf}}}}\",[]]A`[\"{AHAlDC`}\",[]]B`[\"{{AHO`{AHO`{c}}}DC`{}}\",[\"K\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Aa[\"{BCLhBFKn}\",[]]A`[\"{BEFfKI`}\",[]]Aa[\"{BCLlBFL`}\",[]]A`[\"{BEFjKIb}\",[]]A`[\"{BFAnKIb}\",[]]A`[\"{BCNnKIb}\",[]]o[\"{NMjKIb}\",[]]Aa[\"{BCLhAIMj}\",[]]Aa[\"{BCLlAIMl}\",[]]o[\"{KI`CNb}\",[]]o[\"{KIbCNb}\",[]]Aj[\"{{{AHO`{AILd}}}DC`}\",[]]Aj[\"{{{AHO`{BAOd}}}DC`}\",[]]Bd[\"{{{AHO`{BBL`}}}{{KGf{AJ@f}}}}\",[]]Bd[\"{{{AHO`{AB@h}}}{{KGf{AJ@f}}}}\",[]]A`[\"{AHElCNb}\",[]]A`[\"{BCMfCNb}\",[]]A`[\"{AHEnCNb}\",[]]A`[\"{BCMhCNb}\",[]]Cc[\"{{{AHO`{BCN`}}{AHO`{AIOn}}CNb}{{A@b{AONh}}}}\",[]]Bn[\"{{{AHO`{CGbBCOl}}DC`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}DC`}{{AHO`{CGbBCOn}}}}\",[]]Bg[\"{{{AHO`{AILb}}KFn}{{KFl{AHEl}}}}\",[]]o[\"{KEdABh}\",[]]A`[\"{AHBfA@d}\",[]]Bj[\"{{{AHO`{CGbAILd}}KG`}{{KFl{AHEn}}}}\",[]]A`[\"{AHBhA@d}\",[]]Db[\"{{{ABb{{AHO`{CGbAJ@`}}}}{AHO`{CGbNIn}}}{{DAh{{KGf{ICj}}}}}}\",[]]00Eg[\"{{{ABb{{AHO`{CGbAJ@`}}}}{AHO`{CGbNIn}}{AHO`{{K@j{Oh}}}}}{{DAh{{KGd{KCbAJAl}}}}}}\",[]]Ec[\"{{{ABb{{AHO`{CGbAJ@`}}}}{AHO`{CGbNIn}}{AHO`{{K@j{Oh}}}}}{{DAh{{KGf{KCb}}}}}}\",[]]0Bm[\"{{{AHO`{IHl}}{AHO`{D@n}}}{{KGf{ICj}}}}\",[]]Cd[\"{{{AHO`{CGbAIOd}}KCb}{{KGd{{KFl{ICn}}AHFb}}}}\",[]]Cd[\"{{{AHO`{CGbBGB`}}KCb}{{KGd{{KFl{ID`}}AHFb}}}}\",[]]Cn[\"{{{AHO`{CGbAIOd}}{AHO`{CGb{K@j{Oh}}}}}{{KGd{ICjBC@f}}}}\",[]]Bk[\"{{{AHO`{DA`}}AINj}{{KGd{AINjBFKd}}}}\",[]]Bn[\"{{{AHO`{CGbAINn}}NK`}{{AHO`{CGbAINn}}}}\",[]]Bn[\"{{{AHO`{CGbAIO`}}NK`}{{AHO`{CGbAIO`}}}}\",[]]A`[\"{BC@lAG`}\",[]]o[\"{IHjCNb}\",[]]o[\"{{}AJ@f}\",[]]0o[\"{{}AHO`}\",[]]Bk[\"{{ABA`{A@b{BABd}}}{{KGd{AIKnBCLn}}}}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]Bh[\"{{{AHO`{AHAl}}AJ@f}{{KGh{AHFl}}}}\",[]]A`[\"{AHElCMn}\",[]]Aj[\"{{{AHO`{AILd}}}CMn}\",[]]A`[\"{AHEnCMn}\",[]]Ce[\"{{{AHO`{CGbAINn}}{KFl{KHl}}}{{AHO`{CGbAINn}}}}\",[]]Ce[\"{{{AHO`{CGbAIO`}}{KFl{KHl}}}{{AHO`{CGbAIO`}}}}\",[]]Aa[\"{ABDhAJ@f}\",[]]Aa[\"{ABDjAJ@f}\",[]]Aa[\"{ABDlAJ@f}\",[]]Aa[\"{AJ@fAHCb}\",[]]Aa[\"{AJ@fAHCd}\",[]]o[\"{AAjCMn}\",[]]Bn[\"{{{AHO`{CGbBCOl}}CMn}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}CMn}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{AHAlCMn}\",[]]Bo[\"{{{AHO`{CGbBCOl}}AB@d}{{AHO`{CGbBCOl}}}}\",[]]Bo[\"{{{AHO`{CGbBCOn}}AB@d}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{AHAlKFl}\",[]]k[\"{{}}\",[]]0000000000Bh[\"{ABA`c{}}\",[\"IntoFuture::IntoFuture\"]]11Aj[\"{{{AHO`{BAOd}}}DC`}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]o[\"{AAjKCb}\",[]]Ao[\"{{AHO`c}{{NKf{c}}}{}}\",[\"S\"]]0C`[\"{{{AHO`{CGbAILf}}NK`CNbDC`{KFl{CNb}}}ICj}\",[]]Bo[\"{{{AHO`{CGbCNj}}NK`CNbDC`{KFl{CNb}}}ICj}\",[]]C`[\"{{{AHO`{CGbAILh}}NK`CNbDC`{KFl{CNb}}}ICj}\",[]]Bo[\"{{{AHO`{CGbCNl}}NK`CNbDC`{KFl{CNb}}}ICj}\",[]]Bk[\"{{{AHO`{CGbIHl}}NK`{AHO`{BCN`}}}ICj}\",[]]A`[\"{BFBjKFl}\",[]]A`[\"{BFBlKFl}\",[]]Bn[\"{{{AHO`{KI`}}{AHO`{KI`}}}{{KFl{ABBj}}}}\",[]]Bn[\"{{{AHO`{KFn}}{AHO`{KFn}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{AOL`}}{AHO`{AOL`}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{B@Mf}}{AHO`{B@Mf}}}{{KFl{ABBj}}}}\",[]]Bn[\"{{{AHO`{DAl}}{AHO`{DAl}}}{{KFl{ABBj}}}}\",[]]Bn[\"{{{AHO`{A@j}}{AHO`{A@j}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{ABCd}}{AHO`{ABCd}}}{{KFl{ABBj}}}}\",[]]Bn[\"{{{AHO`{KIb}}{AHO`{KIb}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{BFDf}}{AHO`{BFDf}}}{{KFl{ABBj}}}}\",[]]Bn[\"{{{AHO`{KG`}}{AHO`{KG`}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{AHFd}}{AHO`{AHFd}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{BBBl}}{AHO`{BBBl}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{AJAn}}{AHO`{AJAn}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{AOLb}}{AHO`{AOLb}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{BEGb}}{AHO`{BEGb}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{BFCl}}{AHO`{BFCl}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{B@Mh}}{AHO`{B@Mh}}}{{KFl{ABBj}}}}\",[]]Bn[\"{{{AHO`{DAn}}{AHO`{DAn}}}{{KFl{ABBj}}}}\",[]]Bn[\"{{{AHO`{A@l}}{AHO`{A@l}}}{{KFl{ABBj}}}}\",[]]C`[\"{{{AHO`{ABCf}}{AHO`{ABCf}}}{{KFl{ABBj}}}}\",[]]Ak[\"{{{AHO`{AILb}}}AINh}\",[]]Bk[\"{{{AHO`{AILd}}KG`}{{KGd{AINlAIKj}}}}\",[]]Cc[\"{{{AHO`{CGbAIOd}}KCb}{{KGd{{ABh{Oh}}BC@h}}}}\",[]]Bb[\"{{{AHO`{CGbAILd}}ABCf}AIOf}\",[]]Aj[\"{{{AHO`{AHCl}}}KCb}\",[]]Aj[\"{{{AHO`{AHCn}}}KCb}\",[]]Bb[\"{{{AHO`{CGbAILd}}ABCf}AJ@b}\",[]]Bn[\"{{{AHO`{CGbBCOl}}CNb}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}CNb}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{AHAlKFl}\",[]]A`[\"{BAOjKFl}\",[]]A`[\"{BAOlKFl}\",[]]Cf[\"{{{AHO`{CGbBABd}}{A@b{AJ@n}}}{{AHO`{CGbBABd}}}}\",[]]Cf[\"{{{AHO`{CGbBABh}}{A@b{AJA`}}}{{AHO`{CGbBABh}}}}\",[]]Cf[\"{{{AHO`{CGbB@Kh}}{A@b{AJAf}}}{{AHO`{CGbB@Kh}}}}\",[]]Cf[\"{{{AHO`{CGbB@Kl}}{A@b{AJAh}}}{{AHO`{CGbB@Kl}}}}\",[]]o[\"{AAjCNb}\",[]]Bn[\"{{{AHO`{CGbBFIf}}CMn}{{AHO`{CGbBFIf}}}}\",[]]Bn[\"{{{AHO`{CGbBFIh}}CMn}{{AHO`{CGbBFIh}}}}\",[]]Ak[\"{{{AHO`{AILb}}}BGJ`}\",[]]Aj[\"{{{AHO`{DA`}}}BCAf}\",[]]Aj[\"{{{A@b{BABf}}}BABd}\",[]]Aj[\"{{{A@b{BABj}}}BABh}\",[]]Aj[\"{{BCNjc}BCNjABCn}\",[\"\"]]Aj[\"{{BCNlc}BCNlABCn}\",[\"\"]]Bm[\"{{{AHO`{CGbAJ@`}}ICn}{{KGd{ICjAJAl}}}}\",[]]n[\"{{}KFb}\",[]]o[\"{NIlNIl}\",[]]0000Bd[\"{{{AHO`{BGOj}}}{{KFl{AB@d}}}}\",[]]Bd[\"{{{AHO`{BGOl}}}{{KFl{AB@d}}}}\",[]]Bd[\"{{{AHO`{BIAb}}}{{KFl{AB@d}}}}\",[]]Bd[\"{{{AHO`{BIAl}}}{{KFl{AB@d}}}}\",[]]Bd[\"{{{AHO`{AILb}}}{{KFl{BCLn}}}}\",[]]Ck[\"{{{AHO`{AB@h}}B@KhAJ@f{AHO`{CMl}}}{{KGd{AIKnB@M`}}}}\",[]]Ak[\"{{{AHO`{BAAl}}}AB@d}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]An[\"{{AIObDC`}{{KGh{KCb}}}}\",[]]Cl[\"{{{AHO`{NMf}}}{{KFl{{KC`{{A@d{AHCl}}{A@d{AHDj}}}}}}}}\",[]]Cm[\"{{{AHO`{AJAb}}}{{KFl{{KC`{{A@d{AHCl}}{A@d{AHDj}}}}}}}}\",[]]Cl[\"{{{AHO`{NMh}}}{{KFl{{KC`{{A@d{AHCn}}{A@d{AHDl}}}}}}}}\",[]]Cm[\"{{{AHO`{AJAd}}}{{KFl{{KC`{{A@d{AHCn}}{A@d{AHDl}}}}}}}}\",[]]A`[\"{BGGnABh}\",[]]A`[\"{BIEbABh}\",[]]A`[\"{BGH`ABh}\",[]]A`[\"{BIEdABh}\",[]]An[\"{{{AHO`{CGbBGOj}}}B@Mf}\",[]]An[\"{{{AHO`{CGbBGOl}}}B@Mh}\",[]]An[\"{{{AHO`{CGbBIAb}}}B@Mh}\",[]]An[\"{{{AHO`{CGbBIAl}}}B@Mh}\",[]]Ak[\"{{{AHO`{BCAd}}}B@Oj}\",[]]Ba[\"{{{AHO`{CGbAILd}}BCMd}ICj}\",[]]Bo[\"{{{AHO`{CGbAB@j}}BEGbBAOd}{{KFl{BCMd}}}}\",[]]B`[\"{{{AHO`{NMf}}B@MfDAl}D@d}\",[]]Bn[\"{{{AHO`{BABf}}CN`B@Mf}{{KGd{D@dBFLf}}}}\",[]]Ba[\"{{{AHO`{AJAb}}B@MfDAl}D@d}\",[]]Bn[\"{{{AHO`{BEJf}}CN`B@Mf}{{KGd{D@dBFLf}}}}\",[]]B`[\"{{{AHO`{NMh}}B@MhDAn}D@f}\",[]]Bn[\"{{{AHO`{BABj}}CN`B@Mh}{{KGd{D@fBFLl}}}}\",[]]Ba[\"{{{AHO`{AJAd}}B@MhDAn}D@f}\",[]]Bn[\"{{{AHO`{BEJh}}CN`B@Mh}{{KGd{D@fBFLl}}}}\",[]]Bn[\"{{{AHO`{BHKd}}CN`B@Md}{{KGd{D@fBFLl}}}}\",[]]Aj[\"{{}{{NM`{c}}}{}}\",[\"T\"]]0A`[\"{AHElCNb}\",[]]A`[\"{BCMfCNb}\",[]]A`[\"{AHEnCNb}\",[]]A`[\"{BCMhCNb}\",[]]Bn[\"{{{AHO`{CGbBABd}}KCb}{{AHO`{CGbBABd}}}}\",[]]Bn[\"{{{AHO`{CGbBABh}}KCb}{{AHO`{CGbBABh}}}}\",[]]Aj[\"{{{AHO`{BBL`}}}DC`}\",[]]0Aj[\"{{{AHO`{BCAd}}}DC`}\",[]]Bg[\"{{{AHO`{DA`}}}{{KGd{AHCbAIKd}}}}\",[]]Bk[\"{{{AHO`{AILd}}KG`}{{KGd{AHCdAIKj}}}}\",[]]Cc[\"{{{AHO`{BCN`}}{AHO`{AIOb}}CNb}{{A@b{AONh}}}}\",[]]Ak[\"{{{AHO`{ABA`}}}B@Mf}\",[]]Ak[\"{{{AHO`{ABAb}}}B@Mh}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]Bf[\"{{{AHO`{CGbAILd}}}{{KFl{NK`}}}}\",[]]Cf[\"{{{AHO`{CGbBCOl}}{A@b{AOLn}}}{{AHO`{CGbBCOl}}}}\",[]]Cf[\"{{{AHO`{CGbBCOn}}{A@b{AOM`}}}{{AHO`{CGbBCOn}}}}\",[]]43A`[\"{AAjAOMf}\",[]]A`[\"{ABDhKFl}\",[]]A`[\"{ABDjKFl}\",[]]A`[\"{ABDlKFl}\",[]]A`[\"{BFBjKFl}\",[]]A`[\"{BFBlKFl}\",[]]Ai[\"{{{AHO`{NNf}}}KCb}\",[]]Bo[\"{{{AHO`{CGbBIAb}}AB@d}{{AHO`{CGbBIAb}}}}\",[]]Bo[\"{{{AHO`{CGbBIAl}}AB@d}{{AHO`{CGbBIAl}}}}\",[]]Bj[\"{{{AHO`{AJ@`}}CEl}{{KGd{ICjB@Ld}}}}\",[]]Bm[\"{{{AHO`{CGbAJ@b}}CEl}{{KGd{ICjB@Ll}}}}\",[]]?>A`[\"{IHjAONf}\",[]]A`[\"{AOJ`IHj}\",[]]Bj[\"{{{A@b{B@Kf}}IHn}{{KGd{BEJbBGHn}}}}\",[]]Bj[\"{{{A@b{BABb}}IHn}{{KGd{BEJfBGHn}}}}\",[]]Bj[\"{{{A@b{B@Kf}}IHn}{{KGd{BEJdBGI`}}}}\",[]]Bj[\"{{{A@b{BABb}}IHn}{{KGd{BEJhBGI`}}}}\",[]]Dj[\"{{{AHO`{CGbAJ@b}}{AHO`{CGb{AHO`{CGb{K@j{ICn}}}}}}}{{KGd{KCbAJAn}}}}\",[]]Aj[\"{{{AHO`{AILd}}}DC`}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]A`[\"{AHAlDC`}\",[]]Cd[\"{{{AHO`{CGbBBMh}}{A@b{A`}}}{{AHO`{CGbBBMh}}}}\",[]]Cd[\"{{{AHO`{CGbBBMj}}{A@b{A`}}}{{AHO`{CGbBBMj}}}}\",[]]Bk[\"{{{AHO`{BBL`}}}{{ABb{{A@d{AHFj}}}}}}\",[]]o[\"{AAjCNb}\",[]]Aa[\"{AONfAHD`}\",[]]76Aa[\"{AIOjABA`}\",[]]Aa[\"{AIOlABAb}\",[]]Cf[\"{{{AHO`{CGbBFGh}}{KFl{AB@d}}}{{AHO`{CGbBFGh}}}}\",[]]Cf[\"{{{AHO`{CGbBFGj}}{KFl{AB@d}}}{{AHO`{CGbBFGj}}}}\",[]]9;:An[\"{{{AHO`{AONh}}AB@d}ICj}\",[]]B`[\"{{{AHO`{CGbAILf}}CMn}ICj}\",[]]Ao[\"{{{AHO`{CGbCNj}}CMn}ICj}\",[]]Ao[\"{{{AHO`{CGbIDh}}CMn}ICj}\",[]]Ao[\"{{{AHO`{CGbNKl}}CMn}ICj}\",[]]B`[\"{{{AHO`{CGbAILh}}CMn}ICj}\",[]]Ao[\"{{{AHO`{CGbCNl}}CMn}ICj}\",[]]Ao[\"{{{AHO`{CGbIDj}}CMn}ICj}\",[]]Ao[\"{{{AHO`{CGbNKn}}CMn}ICj}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]10Bj[\"{{{AHO`{AILb}}}{{KFl{{A@d{A@`}}}}}}\",[]]Bi[\"{{{AHO`{NMf}}}{{KFl{{A@d{A@`}}}}}}\",[]]Bj[\"{{{AHO`{AJAb}}}{{KFl{{A@d{A@`}}}}}}\",[]]Bi[\"{{{AHO`{NMh}}}{{KFl{{A@d{A@`}}}}}}\",[]]Bj[\"{{{AHO`{AJAd}}}{{KFl{{A@d{A@`}}}}}}\",[]]Db[\"{{{ABb{{AHO`{CGbAJ@`}}}}{AHO`{CGbNIn}}}{{DAh{{KGf{ICj}}}}}}\",[]]Db[\"{{{AHO`{CGbAILd}}NK`B@Oj{AHO`{CGb{ABh{Oh}}}}}{{KFl{ABDj}}}}\",[]]Cf[\"{{{AHO`{CGbBCOl}}{AHO`{CMl}}}{{AHO`{CGbBCOl}}}}\",[]]Cf[\"{{{AHO`{CGbBCOn}}{AHO`{CMl}}}{{AHO`{CGbBCOn}}}}\",[]]Ak[\"{{{AHO`{AILb}}}BAA`}\",[]]Bj[\"{{{AHO`{AILb}}ICn}{{KGd{ICjBFDd}}}}\",[]]Bn[\"{{{AHO`{CGbBCOl}}DC`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}DC`}{{AHO`{CGbBCOn}}}}\",[]]Ai[\"{{{AHO`{NJj}}}KCb}\",[]]Ai[\"{{{AHO`{NJl}}}KCb}\",[]]Dh[\"{{{AHO`{B@Kj}}CN`{AHO`{CMl}}{AHO`{BGAn}}}{{KGd{{A@d{NMf}}B@M`}}}}\",[]]Cb[\"{{{AHO`{BABf}}CN`{AHO`{BGAn}}}{{A@d{NMf}}}}\",[]]Dh[\"{{{AHO`{BEJb}}CN`{AHO`{CMl}}{AHO`{BGAn}}}{{KGd{{A@d{NMf}}B@M`}}}}\",[]]Cb[\"{{{AHO`{BEJf}}CN`{AHO`{BGAn}}}{{A@d{NMf}}}}\",[]]Dh[\"{{{AHO`{B@Kn}}CN`{AHO`{CMl}}{AHO`{BGAn}}}{{KGd{{A@d{NMh}}B@Mb}}}}\",[]]Cb[\"{{{AHO`{BABj}}CN`{AHO`{BGAn}}}{{A@d{NMh}}}}\",[]]Dh[\"{{{AHO`{BEJd}}CN`{AHO`{CMl}}{AHO`{BGAn}}}{{KGd{{A@d{NMh}}B@Mb}}}}\",[]]Cb[\"{{{AHO`{BEJh}}CN`{AHO`{BGAn}}}{{A@d{NMh}}}}\",[]]Dh[\"{{{AHO`{BHKb}}CN`{AHO`{CMl}}{AHO`{BGAn}}}{{KGd{{A@d{NMh}}B@Ln}}}}\",[]]Cb[\"{{{AHO`{BHKd}}CN`{AHO`{BGAn}}}{{A@d{NMh}}}}\",[]]Dd[\"{{{ABb{{AHO`{CGbc}}}}{AHO`{CGbNIn}}}{{DAh{{KFl{KGd}}}}}{}}\",[\"S\"]]0Bn[\"{{AJ@fBBLbAAj}{{KGh{{KC`{AB@fAIL`}}}}}}\",[]]Bd[\"{{{AHO`{AILd}}}{{AHO`{NMh}}}}\",[]]A`[\"{AOJ`IHj}\",[]]Cb[\"{{{AHO`{AOLn}}DAlAJ@fB@MfNK`}{{KFl{AINn}}}}\",[]]Cb[\"{{{AHO`{BCNj}}DAlAJ@fB@MfNK`}{{KFl{AINn}}}}\",[]]Cb[\"{{{AHO`{AOM`}}DAnAJ@fB@MhNK`}{{KFl{AIO`}}}}\",[]]Cb[\"{{{AHO`{BCNl}}DAnAJ@fB@MhNK`}{{KFl{AIO`}}}}\",[]]B`[\"{{{AHO`{CGbAILd}}NK`}ICj}\",[]]Ca[\"{{{AHO`{CGbAIKn}}}{{KGd{{A@d{A@`}}BCLn}}}}\",[]]Bj[\"{{{AHO`{AILb}}}{{KFl{{A@d{A@`}}}}}}\",[]]Bi[\"{{{AHO`{NMf}}}{{KFl{{A@d{A@`}}}}}}\",[]]Bj[\"{{{AHO`{AJAb}}}{{KFl{{A@d{A@`}}}}}}\",[]]Bi[\"{{{AHO`{NMh}}}{{KFl{{A@d{A@`}}}}}}\",[]]Bj[\"{{{AHO`{AJAd}}}{{KFl{{A@d{A@`}}}}}}\",[]]o[\"{AILnOh}\",[]]o[\"{AIM`Oh}\",[]]Ai[\"{{{AHO`{CMl}}}DC`}\",[]]Aj[\"{{{AHO`{AILf}}}CNb}\",[]]Ai[\"{{{AHO`{CNj}}}CNb}\",[]]Bn[\"{{{AHO`{CGbAIJl}}CNb}{{AHO`{CGbAIJl}}}}\",[]]Ai[\"{{{AHO`{IDh}}}CNb}\",[]]Bn[\"{{{AHO`{CGbAOJn}}CNb}{{AHO`{CGbAOJn}}}}\",[]]Ai[\"{{{AHO`{NKl}}}CNb}\",[]]Bn[\"{{{AHO`{CGbBBAl}}CNb}{{AHO`{CGbBBAl}}}}\",[]]Aj[\"{{{AHO`{AILh}}}CNb}\",[]]Ai[\"{{{AHO`{CNl}}}CNb}\",[]]Bn[\"{{{AHO`{CGbAIJn}}CNb}{{AHO`{CGbAIJn}}}}\",[]]Ai[\"{{{AHO`{IDj}}}CNb}\",[]]Bn[\"{{{AHO`{CGbAOK`}}CNb}{{AHO`{CGbAOK`}}}}\",[]]Ai[\"{{{AHO`{NKn}}}CNb}\",[]]Bn[\"{{{AHO`{CGbBBAn}}CNb}{{AHO`{CGbBBAn}}}}\",[]]Aa[\"{AIOdBGB`}\",[]]Ai[\"{{{AHO`{NMf}}}DC`}\",[]]Aj[\"{{{AHO`{AJAb}}}DC`}\",[]]Aj[\"{{{AHO`{AILd}}}DC`}\",[]]Ai[\"{{{AHO`{NMh}}}DC`}\",[]]Aj[\"{{{AHO`{AJAd}}}DC`}\",[]]Co[\"{{{AHO`{NMf}}B@Mf{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}DC`}\",[]]D`[\"{{{AHO`{AJAb}}B@Mf{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}DC`}\",[]]Co[\"{{{AHO`{NMh}}B@Mh{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}DC`}\",[]]D`[\"{{{AHO`{AJAd}}B@Mh{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}DC`}\",[]]Bn[\"{{{AHO`{CGbBFIf}}CMn}{{AHO`{CGbBFIf}}}}\",[]]Bn[\"{{{AHO`{CGbBFIh}}CMn}{{AHO`{CGbBFIh}}}}\",[]]Cd[\"{{{AHO`{CGbNMf}}}{{KFl{{NKb{{A@d{AHDj}}}}}}}}\",[]]Ce[\"{{{AHO`{CGbAJAb}}}{{KFl{{NKb{{A@d{AHDj}}}}}}}}\",[]]Cd[\"{{{AHO`{CGbNMh}}}{{KFl{{NKd{{A@d{AHDl}}}}}}}}\",[]]Ce[\"{{{AHO`{CGbAJAd}}}{{KFl{{NKd{{A@d{AHDl}}}}}}}}\",[]]Bf[\"{{{AHO`{CGbAILf}}CMnCNbNK`}ICj}\",[]]Be[\"{{{AHO`{CGbCNj}}CMnCNbNK`}ICj}\",[]]Bf[\"{{{AHO`{CGbAILh}}CMnCNbNK`}ICj}\",[]]Be[\"{{{AHO`{CGbCNl}}CMnCNbNK`}ICj}\",[]]Bf[\"{{{AHO`{CGbAILf}}NK`CMnCNb}ICj}\",[]]Be[\"{{{AHO`{CGbCNj}}NK`CMnCNb}ICj}\",[]]Bf[\"{{{AHO`{CGbAILh}}NK`CMnCNb}ICj}\",[]]Be[\"{{{AHO`{CGbCNl}}NK`CMnCNb}ICj}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]De[\"{{{AHO`{CGbBCOl}}c{AHO`{CMl}}}{{AHO`{CGbBCOl}}}{{ICl{D@n}}}}\",[\"\"]]De[\"{{{AHO`{CGbBCOn}}c{AHO`{CMl}}}{{AHO`{CGbBCOn}}}{{ICl{D@n}}}}\",[\"\"]]Ci[\"{{{AHO`{CGbNMf}}{AHO`{{K@j{Oh}}}}}{{KGd{DC`IEj}}}}\",[]]Cj[\"{{{AHO`{CGbAJAb}}{AHO`{{K@j{Oh}}}}}{{KGd{DC`IEj}}}}\",[]]Cj[\"{{{AHO`{CGbNMh}}{AHO`{{K@j{Oh}}}}}{{KGd{DC`BCAb}}}}\",[]]Ck[\"{{{AHO`{CGbAJAd}}{AHO`{{K@j{Oh}}}}}{{KGd{DC`BCAb}}}}\",[]]o[\"{AAjDC`}\",[]]Bn[\"{{{AHO`{CGbBCOl}}KI`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}KIb}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{AHAlKFl}\",[]]Ca[\"{{{AHO`{CGbAIOd}}}{{KGd{{KFl{KI`}}AIOh}}}}\",[]]Ca[\"{{{AHO`{CGbBGB`}}}{{KGd{{KFl{KI`}}AIOh}}}}\",[]]Ca[\"{{{AHO`{CGbAIOf}}}{{KGd{{KFl{KIb}}B@Ll}}}}\",[]]Ak[\"{{{AHO`{AIKn}}}AJ@f}\",[]]Ak[\"{{{AHO`{ABA`}}}AJ@f}\",[]]Bg[\"{{{AHO`{DA`}}}{{KGd{AJ@fAIKd}}}}\",[]]Ak[\"{{{AHO`{ABAb}}}AJ@f}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]B`[\"{{KI`c}KI`{{D@b{KI`}}}}\",[\"\"]]B`[\"{{KFnc}KFn{{D@b{KFn}}}}\",[\"\"]]B`[\"{{KIbc}KIb{{D@b{KIb}}}}\",[\"\"]]B`[\"{{KG`c}KG`{{D@b{KG`}}}}\",[\"\"]]20Ae[\"{{AB@dCNb}AG`}\",[]]Bn[\"{{{AHO`{CGbBCOl}}AFn}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}AFn}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{IHjAB@d}\",[]]B`[\"{{}{{AHO`{{K@j{BG@n}}}}}}\",[]]An[\"{{AHO`{ONf{CGbOh}}}ICj}\",[]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000An[\"{{}{{KFl{{A@b{NMd}}}}}}\",[]]Bn[\"{{{AHO`{CGbBBMh}}DC`}{{AHO`{CGbBBMh}}}}\",[]]Bn[\"{{{AHO`{CGbBBMj}}DC`}{{AHO`{CGbBBMj}}}}\",[]]Aj[\"{{{AHO`{AHDj}}}CNb}\",[]]Aj[\"{{{AHO`{AHDl}}}CNb}\",[]]A`[\"{ABC`KFl}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]10Bf[\"{{{AHO`{CMl}}}{{KGh{CNbBBB`}}}}\",[]]Bn[\"{{{AHO`{AB@h}}{A@d{BBL`}}}{{KGf{ICj}}}}\",[]]Ai[\"{AIL`{{KGh{ICj}}}}\",[]]Cj[\"{{{AHO`{NMb}}BBLbBBBj{AHO`{AAj}}}{{KC`{AJ@fAB@f}}}}\",[]]Cb[\"{{{AHO`{CGbAILd}}KG`AINl}{{KGd{AINlBFKf}}}}\",[]]Am[\"{{{AHO`{AILb}}CNb}ICj}\",[]]B`[\"{{{AHO`{CGbAILd}}CNb}ICj}\",[]]A`[\"{BC@jDC`}\",[]]B`[\"{{{AHO`{CGbIHj}}BC@l}ICj}\",[]]Aa[\"{AONfAHD`}\",[]]Bn[\"{{{AHO`{NMd}}AHFl}{{KGf{{A@d{BBL`}}}}}}\",[]]Bo[\"{{{AHO`{BACb}}AHFl}{{KGf{{A@d{BBL`}}}}}}\",[]]Bo[\"{{{AHO`{AOMl}}AHFl}{{KGf{{A@d{BBL`}}}}}}\",[]]Ci[\"{{{AHO`{CGbNMf}}{AHO`{CGb{ABh{Oh}}}}}{{KFl{D@d}}}}\",[]]Cj[\"{{{AHO`{CGbAJAb}}{AHO`{CGb{ABh{Oh}}}}}{{KFl{D@d}}}}\",[]]Ci[\"{{{AHO`{CGbNMh}}{AHO`{CGb{ABh{Oh}}}}}{{KFl{D@f}}}}\",[]]Cj[\"{{{AHO`{CGbAJAd}}{AHO`{CGb{ABh{Oh}}}}}{{KFl{D@f}}}}\",[]]Cb[\"{{{AHO`{ABD`}}IChAJ@lAB@d}{{KGd{ICjBCOf}}}}\",[]]Cb[\"{{{AHO`{BANd}}IChAJ@lAB@d}{{KGd{ICjBCOf}}}}\",[]]Cb[\"{{{AHO`{B@Ol}}IChAJ@lAB@d}{{KGd{ICjBCOf}}}}\",[]]Cb[\"{{{AHO`{ABDb}}IChAJ@lAB@d}{{KGd{ICjBCOh}}}}\",[]]Cb[\"{{{AHO`{BANf}}IChAJ@lAB@d}{{KGd{ICjBCOh}}}}\",[]]Cb[\"{{{AHO`{B@On}}IChAJ@lAB@d}{{KGd{ICjBCOh}}}}\",[]]Al[\"{{}{{KGd{B@KdBGI`}}}}\",[]]Bn[\"{{{AHO`{AILb}}KFn}{{KFl{{A@d{AILf}}}}}}\",[]]Bo[\"{{{AHO`{AILd}}KG`}{{KFl{{AHO`{AILh}}}}}}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]Aj[\"{{{AHO`{AILb}}}ICj}\",[]]Am[\"{{{AHO`{CGbAILd}}}ICj}\",[]]Bf[\"{{{AHO`{AIJb}}}{{KGd{AAjIEn}}}}\",[]]Bf[\"{{{AHO`{AIJb}}}{{KGd{A@hIEn}}}}\",[]]Bg[\"{{{AHO`{AIJb}}}{{KGd{AAOfIEn}}}}\",[]]Bg[\"{{{AHO`{AIJb}}}{{KGd{AHAlIEn}}}}\",[]]Bf[\"{{{AHO`{AIJb}}}{{KGd{AAlIEn}}}}\",[]]Bf[\"{{{AHO`{AIJb}}}{{KGd{AAnIEn}}}}\",[]]Ak[\"{{{AHO`{BCAd}}}B@Oj}\",[]]Cf[\"{{{AHO`{CGbBCOl}}{KFl{AOL`}}}{{AHO`{CGbBCOl}}}}\",[]]Cf[\"{{{AHO`{CGbBCOn}}{KFl{AOLb}}}{{AHO`{CGbBCOn}}}}\",[]]<;Aj[\"{{{AHO`{AB@h}}}KCb}\",[]]Aj[\"{{{AHO`{AB@j}}}KCb}\",[]]Bo[\"{{{AHO`{AILb}}cAINj}ABBh{{D@b{AHCb}}}}\",[\"\"]]Co[\"{{{AHO`{CGbAILd}}AHCdAINlNK`}{{KGd{{KC`{KG`DC`}}AHEb}}}}\",[]]Bn[\"{{{AHO`{CGbBCOl}}CN`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}CN`}{{AHO`{CGbBCOn}}}}\",[]]Df[\"{{{AHO`{CGbAIOd}}{AHO`{CGb{K@j{ICn}}}}}{{KGd{{KFl{KCb}}AHFb}}}}\",[]]Ai[\"{AIL`{{KGh{ICj}}}}\",[]]Bg[\"{{{AHO`{BCAd}}AJAj}{{KGf{KCb}}}}\",[]]A`[\"{AHAlCNb}\",[]]10Aj[\"{{{AHO`{AAj}}}BCOj}\",[]]Cf[\"{{{AHO`{CGbBABd}}{A@b{BCOl}}}{{AHO`{CGbBABd}}}}\",[]]Cf[\"{{{AHO`{CGbB@Kh}}{A@b{BCOl}}}{{AHO`{CGbB@Kh}}}}\",[]]Cf[\"{{{AHO`{CGbBABh}}{A@b{BCOn}}}{{AHO`{CGbBABh}}}}\",[]]Cf[\"{{{AHO`{CGbB@Kl}}{A@b{BCOn}}}{{AHO`{CGbB@Kl}}}}\",[]]Aa[\"{BABdBH@l}\",[]]Aa[\"{BABhBH@n}\",[]]Bj[\"{{{ABh{BBLb}}BBBj}{{KGd{BABdIFb}}}}\",[]]Bj[\"{{{ABh{BBLb}}BBBj}{{KGd{BABhIFd}}}}\",[]]Co[\"{{{AHO`{CGbAJ@`}}{AHO`{CGb{K@j{ICn}}}}}{{KGd{ICjAJAl}}}}\",[]]A`[\"{AHElCNb}\",[]]A`[\"{AHEnCNb}\",[]]A`[\"{BFBjCNb}\",[]]A`[\"{BFBlCNb}\",[]]Bc[\"{{{AHO`{AILb}}}{{KFl{KCb}}}}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]10Aj[\"{{{AHO`{AHBb}}}KCb}\",[]]Be[\"{{{AHO`{AB@h}}{KFl{BABd}}}ICj}\",[]]Bo[\"{{{AHO`{CGbAB@j}}{KFl{{A@b{BABh}}}}}ICj}\",[]]Bd[\"{{{AHO`{BG@n}}}{{KFl{BBBh}}}}\",[]]Aj[\"{{{AHO`{AB@h}}}ICj}\",[]]Dj[\"{{{AHO`{CGbAJ@`}}{AHO`{CGb{AHO`{CGb{K@j{ICn}}}}}}}{{KGd{KCbAJAl}}}}\",[]]o[\"{{}AHO`}\",[]]n[\"{{}NIl}\",[]]0000Bn[\"{{{AHO`{CGbBCOl}}KCb}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}KCb}{{AHO`{CGbBCOn}}}}\",[]]o[\"{CNbKI`}\",[]]o[\"{CNbKIb}\",[]]A`[\"{BAOfCNb}\",[]]Bf[\"{{{AHO`{BGJ`}}{AHO`{BGJ`}}}DC`}\",[]]Bo[\"{{{AHO`{CGbBBMh}}AB@d}{{AHO`{CGbBBMh}}}}\",[]]Bo[\"{{{AHO`{CGbBBMj}}AB@d}{{AHO`{CGbBBMj}}}}\",[]]Ae[\"{{KCbCNb}BANd}\",[]]Ae[\"{{KCbCNb}BANf}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]7Bg[\"{{{AHO`{AILd}}KG`}{{KFl{AINl}}}}\",[]]An[\"{{{AHO`{AILb}}ICn}BAAn}\",[]]Bm[\"{{{AHO`{CGbBEJb}}{ABh{{ABh{Oh}}}}}ICj}\",[]]Bm[\"{{{AHO`{CGbBEJf}}{ABh{{ABh{Oh}}}}}ICj}\",[]]Bm[\"{{{AHO`{CGbBEJd}}{ABh{{ABh{Oh}}}}}ICj}\",[]]Bm[\"{{{AHO`{CGbBEJh}}{ABh{{ABh{Oh}}}}}ICj}\",[]]Am[\"{{{AHO`{AILb}}KI`}ICj}\",[]]B`[\"{{{AHO`{CGbAILd}}KIb}ICj}\",[]]Ce[\"{{{AHO`{CGbBBMh}}{ABh{CN`}}}{{AHO`{CGbBBMh}}}}\",[]]Ce[\"{{{AHO`{CGbBBMj}}{ABh{CN`}}}{{AHO`{CGbBBMj}}}}\",[]]Fg[\"{{{AHO`{BHEb}}{AHO`{BBLb}}{AHO`{{K@j{BBLb}}}}{AHO`{AJ@d}}{AHO`{{K@j{Oh}}}}ABEb}{{KGd{BFK`IEl}}}}\",[]]Af[\"{{BCNjNK`}BCNj}\",[]]Af[\"{{BCNlNK`}BCNl}\",[]]A`[\"{BAOfCNb}\",[]]Al[\"{BACd{{KGd{DAlIF`}}}}\",[]]Al[\"{BACd{{KGd{A@jIF`}}}}\",[]]Ai[\"{BACd{{KGj{DAn}}}}\",[]]Ai[\"{BACd{{KGj{A@l}}}}\",[]]o[\"{NIlNIl}\",[]]Bo[\"{{{AHO`{CGbBFIf}}AB@d}{{AHO`{CGbBFIf}}}}\",[]]Bo[\"{{{AHO`{CGbBFIh}}AB@d}{{AHO`{CGbBFIh}}}}\",[]]Bb[\"{{{AHO`{NMf}}}{{KFl{DC`}}}}\",[]]Bc[\"{{{AHO`{AJAb}}}{{KFl{DC`}}}}\",[]]Bb[\"{{{AHO`{NMh}}}{{KFl{DC`}}}}\",[]]Bc[\"{{{AHO`{AJAd}}}{{KFl{DC`}}}}\",[]]Bg[\"{{{AHO`{AILb}}}{{KGd{ICjBCLn}}}}\",[]]Ba[\"{{{AHO`{BFCf}}KFnAHCb}DC`}\",[]]Ba[\"{{{AHO`{BFCh}}KG`AHCd}DC`}\",[]]Cf[\"{{{AHO`{CGbBCOl}}{KFl{AB@d}}}{{AHO`{CGbBCOl}}}}\",[]]Cf[\"{{{AHO`{CGbBCOn}}{KFl{AB@d}}}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{AHElCNb}\",[]]A`[\"{AHEnCNb}\",[]]Bo[\"{{{AHO`{CGbAILf}}NK`NK`DC`DC`CNbCNb}ICj}\",[]]Bn[\"{{{AHO`{CGbCNj}}NK`NK`DC`DC`CNbCNb}ICj}\",[]]Bn[\"{{{AHO`{CGbIDh}}NK`NK`DC`DC`CNbCNb}ICj}\",[]]Bn[\"{{{AHO`{CGbNKl}}NK`NK`DC`DC`CNbCNb}ICj}\",[]]Bo[\"{{{AHO`{CGbAILh}}NK`NK`DC`DC`CNbCNb}ICj}\",[]]Bn[\"{{{AHO`{CGbCNl}}NK`NK`DC`DC`CNbCNb}ICj}\",[]]Bn[\"{{{AHO`{CGbIDj}}NK`NK`DC`DC`CNbCNb}ICj}\",[]]Bn[\"{{{AHO`{CGbNKn}}NK`NK`DC`DC`CNbCNb}ICj}\",[]]A`[\"{BAOfCNb}\",[]]Al[\"{{{AHO`{NNf}}A@l}CNb}\",[]]Bi[\"{{{AHO`{CGbAIOn}}CNb}{{KGh{ICj}}}}\",[]]<;A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]10Cf[\"{{{AHO`{CGbBCOl}}{KFl{BFGh}}}{{AHO`{CGbBCOl}}}}\",[]]Cf[\"{{{AHO`{CGbBCOn}}{KFl{BFGj}}}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{AHElCNb}\",[]]A`[\"{AHEnCNb}\",[]]Bi[\"{{{AHO`{CGbAIJb}}}{{KGd{AAjIEn}}}}\",[]]Bi[\"{{{AHO`{CGbAIJb}}}{{KGd{A@hIEn}}}}\",[]]Bj[\"{{{AHO`{CGbAIJb}}}{{KGd{AAOfIEn}}}}\",[]]Bj[\"{{{AHO`{CGbAIJb}}}{{KGd{AHAlIEn}}}}\",[]]Bi[\"{{{AHO`{CGbAIJb}}}{{KGd{AAlIEn}}}}\",[]]Bi[\"{{{AHO`{CGbAIJb}}}{{KGd{AAnIEn}}}}\",[]]Cd[\"{{{A@b{AIL`}}CNbDC`}{{KGh{{KC`{BC@lBC@l}}}}}}\",[]]Bn[\"{{{AHO`{CGbBABd}}CNb}{{AHO`{CGbBABd}}}}\",[]]Bn[\"{{{AHO`{CGbBABh}}CNb}{{AHO`{CGbBABh}}}}\",[]]Aj[\"{{{AHO`{AILb}}}DC`}\",[]]Ak[\"{{{AHO`{BBL`}}}B@Oj}\",[]]0Ci[\"{{{AHO`{CGbBBMh}}CMn}{{KGd{{AHO`{CGbBBMh}}AOJd}}}}\",[]]Ci[\"{{{AHO`{CGbBBMj}}CMn}{{KGd{{AHO`{CGbBBMj}}AOJf}}}}\",[]]A`[\"{AHAlCMn}\",[]]Cf[\"{{{AHO`{CGbBCOl}}{KFl{BFIf}}}{{AHO`{CGbBCOl}}}}\",[]]Cf[\"{{{AHO`{CGbBCOn}}{KFl{BFIh}}}{{AHO`{CGbBCOn}}}}\",[]]Bg[\"{{{AHO`{CGbAILd}}}{{KFl{BAOd}}}}\",[]]Cf[\"{{{AHO`{CGbBABd}}{KFl{BABl}}}{{AHO`{CGbBABd}}}}\",[]]Cf[\"{{{AHO`{CGbBABh}}{KFl{BABl}}}{{AHO`{CGbBABh}}}}\",[]]Cf[\"{{{AHO`{CGbBABd}}{KFl{BABn}}}{{AHO`{CGbBABd}}}}\",[]]Cf[\"{{{AHO`{CGbBABh}}{KFl{BABn}}}{{AHO`{CGbBABh}}}}\",[]]Cd[\"{{}{{KGd{{KC`{{ABh{BBLb}}BBBj}}{A@d{IEd}}}}}}\",[]]Bn[\"{{{AHO`{CGbBFGh}}KI`}{{AHO`{CGbBFGh}}}}\",[]]Bn[\"{{{AHO`{CGbBFGj}}KIb}{{AHO`{CGbBFGj}}}}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]Bo[\"{{{AHO`{CGbBABd}}AB@d}{{AHO`{CGbBABd}}}}\",[]]Bo[\"{{{AHO`{CGbBABh}}AB@d}{{AHO`{CGbBABh}}}}\",[]]Ci[\"{{{AHO`{DA`}}{KFl{AB@d}}}{{KGd{{KFl{AB@d}}AIKd}}}}\",[]]Bj[\"{{{AHO`{BCAd}}AJAjKCb}{{KGf{ICj}}}}\",[]]054Bm[\"{{{AHO`{NMf}}}{{KGd{{KFl{BGAn}}IEj}}}}\",[]]Bn[\"{{{AHO`{AJAb}}}{{KGd{{KFl{BGAn}}IEj}}}}\",[]]Bn[\"{{{AHO`{NMh}}}{{KGd{{KFl{BGAn}}BCAb}}}}\",[]]Bo[\"{{{AHO`{AJAd}}}{{KGd{{KFl{BGAn}}BCAb}}}}\",[]]Aj[\"{{{AHO`{AHDj}}}CNb}\",[]]Aj[\"{{{AHO`{AHDl}}}CNb}\",[]]Bl[\"{{{AHO`{AB@h}}{KFl{{A@b{BFCf}}}}}ICj}\",[]]Cc[\"{{{AHO`{CGbAILd}}{KFl{{AHO`{BFCh}}}}NK`}ICj}\",[]]Aj[\"{{{AHO`{AB@j}}}CNb}\",[]]Bn[\"{{{AHO`{CGbBBAl}}AFn}{{AHO`{CGbBBAl}}}}\",[]]Bn[\"{{{AHO`{CGbBBAn}}AFn}{{AHO`{CGbBBAn}}}}\",[]]Ak[\"{{{AHO`{AHFj}}}B@Oj}\",[]]0Ak[\"{{{AHO`{AILb}}}BGA`}\",[]]Cb[\"{{{AHO`{AILd}}KG`}{{KGd{{KFl{AJ@f}}AIKj}}}}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]Bn[\"{{{AHO`{CGbBCOl}}KI`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}KIb}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{AHAlKFl}\",[]]Bo[\"{{{AHO`{AHAl}}{AHO`{CMl}}}{{KGf{BCOj}}}}\",[]]Em[\"{{{AHO`{AILb}}{AHO`{CGb{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}{{KGd{ICjBHLn}}}}\",[]]El[\"{{{AHO`{NMf}}{AHO`{CGb{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}{{KGd{ICjBHLn}}}}\",[]]Em[\"{{{AHO`{AJAb}}{AHO`{CGb{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}{{KGd{ICjBHLn}}}}\",[]]El[\"{{{AHO`{NMh}}{AHO`{CGb{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}{{KGd{ICjBHM`}}}}\",[]]Em[\"{{{AHO`{AJAd}}{AHO`{CGb{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}{AHO`{{K@j{Oh}}}}}{{KGd{ICjBHM`}}}}\",[]]Am[\"{{{AHO`{CGbAHB`}}}ICj}\",[]]0Am[\"{{{AHO`{AILd}}A@l}CNb}\",[]]Ak[\"{{{AHO`{AILb}}}BGIb}\",[]]Bg[\"{{{AHO`{DA`}}}{{KGd{BEFbAIKd}}}}\",[]]>=E`[\"{{{AHO`{BHEb}}{AHO`{{K@j{Oh}}}}{AHO`{BBLb}}{AHO`{BGOn}}}{{KGd{BHGhIEl}}}}\",[]]0Bg[\"{{{A@b{BBCb}}}{{KGd{B@KhBGIn}}}}\",[]]Bg[\"{{{A@b{BBCb}}}{{KGd{B@KlBGIn}}}}\",[]]Bn[\"{{{AHO`{CGbBFGh}}KI`}{{AHO`{CGbBFGh}}}}\",[]]Bn[\"{{{AHO`{CGbBFGj}}KIb}{{AHO`{CGbBFGj}}}}\",[]]o[\"{NIlNIl}\",[]]0000Aj[\"{{{AHO`{AILd}}}DC`}\",[]]0Bd[\"{{{AHO`{CGbAILf}}CNbAB@d}ICj}\",[]]Bc[\"{{{AHO`{CGbCNj}}CNbAB@d}ICj}\",[]]Bd[\"{{{AHO`{CGbAILh}}CNbAB@d}ICj}\",[]]Bc[\"{{{AHO`{CGbCNl}}CNbAB@d}ICj}\",[]]Ci[\"{{{AHO`{DA`}}{KFl{AB@d}}}{{KGd{{KFl{AB@d}}AIKd}}}}\",[]]Cd[\"{{{AHO`{CGbAAj}}{AHO`{AIJb}}}{{KGd{ICjIEn}}}}\",[]]Cd[\"{{{AHO`{CGbA@h}}{AHO`{AIJb}}}{{KGd{ICjIEn}}}}\",[]]Ce[\"{{{AHO`{CGbAAOf}}{AHO`{AIJb}}}{{KGd{ICjIEn}}}}\",[]]Ce[\"{{{AHO`{CGbAHAl}}{AHO`{AIJb}}}{{KGd{ICjIEn}}}}\",[]]Cd[\"{{{AHO`{CGbAAl}}{AHO`{AIJb}}}{{KGd{ICjIEn}}}}\",[]]Cd[\"{{{AHO`{CGbAAn}}{AHO`{AIJb}}}{{KGd{ICjIEn}}}}\",[]]Bo[\"{{{AHO`{CGbBABd}}BH@l}{{AHO`{CGbBABd}}}}\",[]]Bo[\"{{{AHO`{CGbBABh}}BH@n}{{AHO`{CGbBABh}}}}\",[]]Aj[\"{{{AHO`{BBMh}}}CNb}\",[]]Aj[\"{{{AHO`{BBMj}}}CNb}\",[]]Cd[\"{{{AHO`{CGbB@Kh}}{A@b{A`}}}{{AHO`{CGbB@Kh}}}}\",[]]Cd[\"{{{AHO`{CGbB@Kl}}{A@b{A`}}}{{AHO`{CGbB@Kl}}}}\",[]]Cl[\"{{BBMh{KFl{BABd}}{A@d{BBL`}}{A@b{NMd}}}{{KGf{AB@h}}}}\",[]]Aj[\"{{{AHO`{ABA`}}}DC`}\",[]]Aj[\"{{{AHO`{ABAb}}}DC`}\",[]]Ca[\"{{{AHO`{CGbAILd}}NK`AIMn}{{KGd{ICjBHDj}}}}\",[]]Bd[\"{{{AHO`{BHEb}}}{{ABh{BCOb}}}}\",[]]Bj[\"{{{AHO`{AILb}}AJ@f}{{KGd{ICjIFh}}}}\",[]]Bm[\"{{{AHO`{CGbAILd}}AJ@f}{{KGd{ICjIFh}}}}\",[]]Bn[\"{{{AHO`{CGbBCOl}}KCb}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}KCb}{{AHO`{CGbBCOn}}}}\",[]]Bm[\"{{}{{KGd{{KC`{BBLbBFJb}}{A@d{IEd}}}}}}\",[]]Ai[\"{AIL`{{KGh{ICj}}}}\",[]]A`[\"{AILnCNb}\",[]]A`[\"{AIM`CNb}\",[]]An[\"{{{AHO`{AB@h}}B@Kh}ICj}\",[]]Dc[\"{{{AHO`{CGbAILd}}NK`KG`{KFl{AB@d}}}{{KGd{{KFl{AB@d}}AIKj}}}}\",[]]o[\"{{}AHO`}\",[]]Aj[\"{{{AHO`{AILb}}}KCb}\",[]]Bn[\"{{{AHO`{CGbBABd}}CNb}{{AHO`{CGbBABd}}}}\",[]]Bn[\"{{{AHO`{CGbBABh}}CNb}{{AHO`{CGbBABh}}}}\",[]]Bn[\"{{{AHO`{CGbBCOl}}KI`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}KIb}{{AHO`{CGbBCOn}}}}\",[]]:Bn[\"{{{AHO`{CGbBCOl}}DC`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}DC`}{{AHO`{CGbBCOn}}}}\",[]]Bc[\"{{{AHO`{CGbAILd}}A@lKIb}ICj}\",[]]A`[\"{AHElCNb}\",[]]A`[\"{AHEnCNb}\",[]]Ak[\"{{}{{KGd{B@KhIFb}}}}\",[]]Ak[\"{{}{{KGd{B@KlIFd}}}}\",[]]6587Cg[\"{{{AHO`{CGbAAj}}{AHO`{CGbAIJb}}}{{KGd{ICjIEn}}}}\",[]]Cg[\"{{{AHO`{CGbA@h}}{AHO`{CGbAIJb}}}{{KGd{ICjIEn}}}}\",[]]Ch[\"{{{AHO`{CGbAAOf}}{AHO`{CGbAIJb}}}{{KGd{ICjIEn}}}}\",[]]Ch[\"{{{AHO`{CGbAHAl}}{AHO`{CGbAIJb}}}{{KGd{ICjIEn}}}}\",[]]Cg[\"{{{AHO`{CGbAAl}}{AHO`{CGbAIJb}}}{{KGd{ICjIEn}}}}\",[]]Cg[\"{{{AHO`{CGbAAn}}{AHO`{CGbAIJb}}}{{KGd{ICjIEn}}}}\",[]]n[\"{{}ICj}\",[]]Ce[\"{{{AHO`{CGbBCOl}}{KFl{KCb}}}{{AHO`{CGbBCOl}}}}\",[]]Ce[\"{{{AHO`{CGbBCOn}}{KFl{KCb}}}{{AHO`{CGbBCOn}}}}\",[]]Bn[\"{{{AHO`{AILb}}}{{KGd{{ABh{AJ@f}}IFh}}}}\",[]]Cd[\"{{{AHO`{CGbAILd}}NK`}{{KGd{{ABh{AJ@f}}IFh}}}}\",[]]Am[\"{{{AHO`{CGbAILf}}}ICj}\",[]]Al[\"{{{AHO`{CGbCNj}}}ICj}\",[]]Al[\"{{{AHO`{CGbIDh}}}ICj}\",[]]Am[\"{{{AHO`{CGbAILh}}}ICj}\",[]]Al[\"{{{AHO`{CGbCNl}}}ICj}\",[]]Al[\"{{{AHO`{CGbIDj}}}ICj}\",[]]Ai[\"{AIL`{{KGh{ICj}}}}\",[]]Bj[\"{{{AHO`{AILb}}AJ@f}{{KGd{ICjIFh}}}}\",[]]Bm[\"{{{AHO`{CGbAILd}}AJ@f}{{KGd{ICjIFh}}}}\",[]]D`[\"{{{AHO`{CGbAILd}}KG`{KFl{AB@d}}}{{KGd{{KFl{AB@d}}AIKj}}}}\",[]]Cf[\"{{{AHO`{CGbBCOl}}{A@b{BFBf}}}{{AHO`{CGbBCOl}}}}\",[]]Cf[\"{{{AHO`{CGbBCOn}}{A@b{BFBh}}}{{AHO`{CGbBCOn}}}}\",[]]Cf[\"{{{AHO`{CGbBCOl}}{KFl{AB@d}}}{{AHO`{CGbBCOl}}}}\",[]]Cf[\"{{{AHO`{CGbBCOn}}{KFl{AB@d}}}{{AHO`{CGbBCOn}}}}\",[]]Ce[\"{{{AHO`{CGbBCOl}}{KFl{CNb}}}{{AHO`{CGbBCOl}}}}\",[]]Ce[\"{{{AHO`{CGbBCOn}}{KFl{CNb}}}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{BAOjKFl}\",[]]A`[\"{BAOlKFl}\",[]];Bn[\"{{{AHO`{CGbBCOl}}DC`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}DC`}{{AHO`{CGbBCOn}}}}\",[]]Am[\"{{{AHO`{AILb}}KI`}ICj}\",[]]o[\"{NIlNIl}\",[]]Bn[\"{{{AHO`{CGbBCOl}}CN`}{{AHO`{CGbBCOl}}}}\",[]]Bn[\"{{{AHO`{CGbBCOn}}CN`}{{AHO`{CGbBCOn}}}}\",[]]310;:54Bn[\"{{{AHO`{AILb}}}{{KGd{{ABh{AJ@f}}IFh}}}}\",[]]Bn[\"{{{AHO`{AILd}}}{{KGd{{ABh{AJ@f}}IFh}}}}\",[]]10Bm[\"{{{AHO`{CGbBCOl}}Oh}{{AHO`{CGbBCOl}}}}\",[]]Bm[\"{{{AHO`{CGbBCOn}}Oh}{{AHO`{CGbBCOn}}}}\",[]]A`[\"{BGAnKFl}\",[]]") \ No newline at end of file diff --git a/pr/451/docs/search.index/function/bcf4aac8ded5.js b/pr/451/docs/search.index/function/bcf4aac8ded5.js deleted file mode 100644 index 00c74febe..000000000 --- a/pr/451/docs/search.index/function/bcf4aac8ded5.js +++ /dev/null @@ -1 +0,0 @@ -rd_("Bf[\"{{{AHLl{AOFn}}{AHLl{AOFn}}}DBh}\",[]]Bf[\"{{{AHLl{BF@l}}{AHLl{BF@l}}}DBh}\",[]]Bd[\"{{{AHLl{KE`}}{AHLl{KE`}}}DBh}\",[]]Bd[\"{{{AHLl{D@h}}{AHLl{D@h}}}DBh}\",[]]Bf[\"{{{AHLl{BBN`}}{AHLl{BBN`}}}DBh}\",[]]Bf[\"{{{AHLl{BBMb}}{AHLl{BBMb}}}DBh}\",[]]Bf[\"{{{AHLl{AHCn}}{AHLl{AHCn}}}DBh}\",[]]Bf[\"{{{AHLl{AIMb}}{AHLl{AIMb}}}DBh}\",[]]Bf[\"{{{AHLl{BBM`}}{AHLl{BBM`}}}DBh}\",[]]Bf[\"{{{AHLl{AIOf}}{AHLl{AIOf}}}DBh}\",[]]Bf[\"{{{AHLl{B@Ol}}{AHLl{B@Ol}}}DBh}\",[]]Bd[\"{{{AHLl{KGl}}{AHLl{KGl}}}DBh}\",[]]Bf[\"{{{AHLl{BGF`}}{AHLl{BGF`}}}DBh}\",[]]Bd[\"{{{AHLl{IBn}}{AHLl{IBn}}}DBh}\",[]]Bd[\"{{{AHLl{KEj}}{AHLl{KEj}}}DBh}\",[]]Bf[\"{{{AHLl{AILd}}{AHLl{AILd}}}DBh}\",[]]Bf[\"{{{AHLl{AHCd}}{AHLl{AHCd}}}DBh}\",[]]Bf[\"{{{AHLl{BFGj}}{AHLl{BFGj}}}DBh}\",[]]Bf[\"{{{AHLl{AIHn}}{AHLl{AIHn}}}DBh}\",[]]Bf[\"{{{AHLl{ABCd}}{AHLl{ABCd}}}DBh}\",[]]Bf[\"{{{AHLl{AIJh}}{AHLl{AIJh}}}DBh}\",[]]Bf[\"{{{AHLl{AHCh}}{AHLl{AHCh}}}DBh}\",[]]Bf[\"{{{AHLl{B@I`}}{AHLl{B@I`}}}DBh}\",[]]Bf[\"{{{AHLl{BCIh}}{AHLl{BCIh}}}DBh}\",[]]Bf[\"{{{AHLl{AHBl}}{AHLl{AHBl}}}DBh}\",[]]Bf[\"{{{AHLl{BBHn}}{AHLl{BBHn}}}DBh}\",[]]Bf[\"{{{AHLl{AOI`}}{AHLl{AOI`}}}DBh}\",[]]Bf[\"{{{AHLl{AOGd}}{AHLl{AOGd}}}DBh}\",[]]Bf[\"{{{AHLl{AHAb}}{AHLl{AHAb}}}DBh}\",[]]Bf[\"{{{AHLl{BCIb}}{AHLl{BCIb}}}DBh}\",[]]Bf[\"{{{AHLl{BEBn}}{AHLl{BEBn}}}DBh}\",[]]Bf[\"{{{AHLl{B@Il}}{AHLl{B@Il}}}DBh}\",[]]Bf[\"{{{AHLl{B@Jb}}{AHLl{B@Jb}}}DBh}\",[]]Bf[\"{{{AHLl{B@Jl}}{AHLl{B@Jl}}}DBh}\",[]]Bf[\"{{{AHLl{BFHd}}{AHLl{BFHd}}}DBh}\",[]]Bd[\"{{{AHLl{DAd}}{AHLl{DAd}}}DBh}\",[]]Bd[\"{{{AHLl{A@h}}{AHLl{A@h}}}DBh}\",[]]Bf[\"{{{AHLl{ABAj}}{AHLl{ABAj}}}DBh}\",[]]Bf[\"{{{AHLl{BHIh}}{AHLl{BHIh}}}DBh}\",[]]Bd[\"{{{AHLl{IE`}}{AHLl{IE`}}}DBh}\",[]]Bd[\"{{{AHLl{KGn}}{AHLl{KGn}}}DBh}\",[]]Bf[\"{{{AHLl{BGFb}}{AHLl{BGFb}}}DBh}\",[]]Bd[\"{{{AHLl{IC`}}{AHLl{IC`}}}DBh}\",[]]Bf[\"{{{AHLl{BF@n}}{AHLl{BF@n}}}DBh}\",[]]Bd[\"{{{AHLl{KEl}}{AHLl{KEl}}}DBh}\",[]]Bf[\"{{{AHLl{AILf}}{AHLl{AILf}}}DBh}\",[]]Bf[\"{{{AHLl{AHCf}}{AHLl{AHCf}}}DBh}\",[]]Bf[\"{{{AHLl{BF@j}}{AHLl{BF@j}}}DBh}\",[]]Bf[\"{{{AHLl{BFGl}}{AHLl{BFGl}}}DBh}\",[]]Bf[\"{{{AHLl{AIId}}{AHLl{AIId}}}DBh}\",[]]Bf[\"{{{AHLl{ABCf}}{AHLl{ABCf}}}DBh}\",[]]Bf[\"{{{AHLl{AIJj}}{AHLl{AIJj}}}DBh}\",[]]Bf[\"{{{AHLl{AHCj}}{AHLl{AHCj}}}DBh}\",[]]Bf[\"{{{AHLl{AHD`}}{AHLl{AHD`}}}DBh}\",[]]Bf[\"{{{AHLl{BAOf}}{AHLl{BAOf}}}DBh}\",[]]Bf[\"{{{AHLl{AIOh}}{AHLl{AIOh}}}DBh}\",[]]Bf[\"{{{AHLl{AOHn}}{AHLl{AOHn}}}DBh}\",[]]Bf[\"{{{AHLl{AOKd}}{AHLl{AOKd}}}DBh}\",[]]Bf[\"{{{AHLl{BBMd}}{AHLl{BBMd}}}DBh}\",[]]Bf[\"{{{AHLl{B@Ih}}{AHLl{B@Ih}}}DBh}\",[]]Bf[\"{{{AHLl{BCIj}}{AHLl{BCIj}}}DBh}\",[]]Bf[\"{{{AHLl{AHBn}}{AHLl{AHBn}}}DBh}\",[]]Bf[\"{{{AHLl{BBI`}}{AHLl{BBI`}}}DBh}\",[]]Bf[\"{{{AHLl{AOIb}}{AHLl{AOIb}}}DBh}\",[]]Bf[\"{{{AHLl{AOGf}}{AHLl{AOGf}}}DBh}\",[]]Bf[\"{{{AHLl{AHAd}}{AHLl{AHAd}}}DBh}\",[]]Bf[\"{{{AHLl{BBJn}}{AHLl{BBJn}}}DBh}\",[]]Bf[\"{{{AHLl{AIKf}}{AHLl{AIKf}}}DBh}\",[]]Bf[\"{{{AHLl{AINb}}{AHLl{AINb}}}DBh}\",[]]Bf[\"{{{AHLl{B@Jf}}{AHLl{B@Jf}}}DBh}\",[]]Bf[\"{{{AHLl{BCIf}}{AHLl{BCIf}}}DBh}\",[]]Bf[\"{{{AHLl{BECb}}{AHLl{BECb}}}DBh}\",[]]Bf[\"{{{AHLl{BECj}}{AHLl{BECj}}}DBh}\",[]]Bf[\"{{{AHLl{B@In}}{AHLl{B@In}}}DBh}\",[]]Bf[\"{{{AHLl{AAOn}}{AHLl{AAOn}}}DBh}\",[]]Bf[\"{{{AHLl{BF@d}}{AHLl{BF@d}}}DBh}\",[]]Bf[\"{{{AHLl{B@Jd}}{AHLl{B@Jd}}}DBh}\",[]]Bf[\"{{{AHLl{B@Jn}}{AHLl{B@Jn}}}DBh}\",[]]Bf[\"{{{AHLl{BBMl}}{AHLl{BBMl}}}DBh}\",[]]Bf[\"{{{AHLl{BFHf}}{AHLl{BFHf}}}DBh}\",[]]Bd[\"{{{AHLl{DAf}}{AHLl{DAf}}}DBh}\",[]]Bd[\"{{{AHLl{A@j}}{AHLl{A@j}}}DBh}\",[]]Bf[\"{{{AHLl{ABAl}}{AHLl{ABAl}}}DBh}\",[]]Bf[\"{{{AHLl{AHA`}}{AHLl{AHA`}}}DBh}\",[]]Bf[\"{{{AHLl{BB@b}}{AHLl{BB@b}}}DBh}\",[]]Bf[\"{{{AHLl{BFNd}}{AHLl{BFNd}}}DBh}\",[]]Bd[\"{{{AHLl{IEd}}{AHLl{IEd}}}DBh}\",[]]Bf[\"{{{AHLl{BHIj}}{AHLl{BHIj}}}DBh}\",[]]Bd[\"{{{AHLl{IEb}}{AHLl{IEb}}}DBh}\",[]]Bf[\"{{{AHLl{B@K`}}{AHLl{B@K`}}}DBh}\",[]]Ai[\"{{{AHLl{D@h}}}KEj}\",[]]Aj[\"{{{AHLl{BBN`}}}KEj}\",[]]Ak[\"{{{AHLl{AILn}}}ABAj}\",[]]Ak[\"{{{AHLl{BFNf}}}ABAj}\",[]]Ak[\"{{{AHLl{AIMj}}}ABAj}\",[]]A`[\"{AOHfKEj}\",[]]A`[\"{AGMnKEj}\",[]]A`[\"{AH@dKEj}\",[]]A`[\"{B@MnKEj}\",[]]A`[\"{B@LlKEj}\",[]]A`[\"{AOHhKEl}\",[]]A`[\"{AGN`KEl}\",[]]A`[\"{AH@fKEl}\",[]]A`[\"{B@N`KEl}\",[]]A`[\"{B@LnKEl}\",[]]Aa[\"{ABAbABAl}\",[]]Aa[\"{ABCjABAl}\",[]]Aa[\"{AAObABAl}\",[]]A`[\"{NLbABAl}\",[]]A`[\"{AOJfNKj}\",[]]A`[\"{D@`AAOn}\",[]]Ac[\"{{ABCdABCd}}\",[]]Ac[\"{{AIJhAIJh}}\",[]]Ac[\"{{BCJ`AHCh}}\",[]]Bc[\"{{ABCfABCf}c{}}\",[\"Add::Output\"]]Bc[\"{{AIJjAIJj}c{}}\",[\"Add::Output\"]]Bc[\"{{BCJbAHCj}c{}}\",[\"Add::Output\"]]Be[\"{{{AHLl{KGl}}{AHLl{KGl}}}ABA`}\",[]]Be[\"{{{AHLl{KEj}}{AHLl{KEj}}}ABA`}\",[]]Bg[\"{{{AHLl{AOI`}}{AHLl{AOI`}}}ABA`}\",[]]Bg[\"{{{AHLl{B@Jb}}{AHLl{B@Jb}}}ABA`}\",[]]Be[\"{{{AHLl{DAd}}{AHLl{DAd}}}ABA`}\",[]]Be[\"{{{AHLl{A@h}}{AHLl{A@h}}}ABA`}\",[]]Bg[\"{{{AHLl{ABAj}}{AHLl{ABAj}}}ABA`}\",[]]Be[\"{{{AHLl{KGn}}{AHLl{KGn}}}ABA`}\",[]]Bg[\"{{{AHLl{BF@n}}{AHLl{BF@n}}}ABA`}\",[]]Be[\"{{{AHLl{KEl}}{AHLl{KEl}}}ABA`}\",[]]Bg[\"{{{AHLl{AHD`}}{AHLl{AHD`}}}ABA`}\",[]]Bg[\"{{{AHLl{BAOf}}{AHLl{BAOf}}}ABA`}\",[]]Bg[\"{{{AHLl{AIOh}}{AHLl{AIOh}}}ABA`}\",[]]Bg[\"{{{AHLl{AOIb}}{AHLl{AOIb}}}ABA`}\",[]]Bg[\"{{{AHLl{BECj}}{AHLl{BECj}}}ABA`}\",[]]Bg[\"{{{AHLl{BF@d}}{AHLl{BF@d}}}ABA`}\",[]]Bg[\"{{{AHLl{B@Jd}}{AHLl{B@Jd}}}ABA`}\",[]]Be[\"{{{AHLl{DAf}}{AHLl{DAf}}}ABA`}\",[]]Be[\"{{{AHLl{A@j}}{AHLl{A@j}}}ABA`}\",[]]Bg[\"{{{AHLl{ABAl}}{AHLl{ABAl}}}ABA`}\",[]]A`[\"{ABAjA@h}\",[]]A`[\"{ABAlA@j}\",[]]o[\"{KEdA@j}\",[]]A`[\"{AGO`A@j}\",[]]A`[\"{ABBnKEh}\",[]]A`[\"{ABC`KEh}\",[]]A`[\"{ABAfKEh}\",[]]A`[\"{ABCbKEh}\",[]]Bh[\"{{{AHLl{AAh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AOJf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AOFn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BBMf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BHAl}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BGEh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AIIh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AIIl}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{KD`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BGFf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BF@l}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AANn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BAL`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{KE`}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AILb}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BFMf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AAOf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AIMd}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BBJl}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{D@h}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BBN`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AILn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BFNf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BBMb}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AHCn}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AIMb}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BBM`}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{B@On}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AOJl}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AIMj}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AIOf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{B@Ol}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bh[\"{{{AHLl{NLf}}{AHLl{CFlAH@n}}}KEn}\",[]]Cd[\"{{{AHLl{KGl}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{BGF`}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Cd[\"{{{AHLl{IBn}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Cd[\"{{{AHLl{KEj}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{AILd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{AHCd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BFGj}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{AIHn}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{ABCd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{AIJh}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{AHCh}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BCJ`}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{B@I`}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{BCIh}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{AHBl}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{BBHn}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{BCKd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BCLf}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BFCn}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BFEl}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{AOI`}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BBJb}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{B@O`}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BGMb}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{B@Hd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{AOGd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{AHAb}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{BCIb}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{BEBn}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{B@Il}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{B@Jb}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{B@Jl}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BFHd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{AIJn}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BEGb}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Cd[\"{{{AHLl{DAd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Cd[\"{{{AHLl{A@h}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{ABAj}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{ABBn}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Cd[\"{{{AHLl{CNb}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{AIHf}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Cd[\"{{{AHLl{ICf}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{AOGn}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Cd[\"{{{AHLl{NJd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BANf}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BHIh}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{AOGj}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Ce[\"{{{AHLl{BFHl}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]Cd[\"{{{AHLl{IE`}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Ce[\"{{{AHLl{BGEd}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Bi[\"{{{AHLl{AIIn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AAO`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{KGn}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BGFb}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bh[\"{{{AHLl{IC`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BF@n}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bh[\"{{{AHLl{KEl}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{B@Nh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AILf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AHCf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BF@j}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BFGl}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AIId}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{ABCf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AIJj}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AHCj}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BCJb}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AHD`}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BAOf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AIOh}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AOHn}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AOKd}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BBMd}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{B@Ih}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BCIj}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AHBn}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BBI`}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BHAb}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bh[\"{{{AHLl{IEj}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BCKf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BCLh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BFD`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BFEn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AOIb}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BBJd}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{B@Od}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BGMd}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{B@Hh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AOGf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AHAd}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BBJn}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AIKf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AINb}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{B@Jf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BCIf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BECb}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AANb}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BECj}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AAOh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{B@In}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AOFj}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AIMf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BANl}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BCKb}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BHAj}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AAOn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BF@d}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BCIn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BAKn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{B@Jd}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{B@Jn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BBMl}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BFHf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{AIK`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BHNb}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BEGd}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{DAf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{A@j}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{ABAl}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{ABC`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AHA`}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BB@b}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BFNd}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{IEd}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bi[\"{{{AHLl{BHIj}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AOGl}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BFIb}}{AHLl{CFlAH@n}}}KEn}\",[]]Cd[\"{{{AHLl{IEb}}{AHLl{CFlAH@n}}}{{KF`{IBhID`}}}}\",[]]0Bi[\"{{{AHLl{BGEf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bh[\"{{{AHLl{CNd}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AIHh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{ICh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{AOH`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{NJf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BANh}}{AHLl{CFlAH@n}}}KEn}\",[]]Bh[\"{{{AHLl{IEf}}{AHLl{CFlAH@n}}}KEn}\",[]]0Bh[\"{{{AHLl{IEl}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{BBMn}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{ABAf}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{ABCb}}{AHLl{CFlAH@n}}}KEn}\",[]]Bi[\"{{{AHLl{B@K`}}{AHLl{CFlAH@n}}}KEn}\",[]]Bd[\"{{{AHLl{BGEh}}}{{KEh{AIN`}}}}\",[]]Ak[\"{{{AHLl{B@Nh}}}AANj}\",[]]A`[\"{ABCdCMj}\",[]]A`[\"{ABCfCMj}\",[]]Aj[\"{{{AHLl{BANl}}}KAn}\",[]]A`[\"{BHAjCMj}\",[]]o[\"{D@`CMj}\",[]]A`[\"{ABAfKAn}\",[]]Cf[\"{{{AHLl{CFlBGMb}}{A@`{ABBf}}}{{AHLl{CFlBGMb}}}}\",[]]Cf[\"{{{AHLl{CFlBGMd}}{A@`{ABBh}}}{{AHLl{CFlBGMd}}}}\",[]]8Af[\"{{AANjCMj}BBMf}\",[]]Ah[\"{{}{{A@`{BHAl}}}}\",[]]Ae[\"{{KAnCMh}BAJn}\",[]]Ce[\"{{BBJb{KEh{B@O`}}AHDh{A@`{NKl}}}{{KFb{AANn}}}}\",[]]Ai[\"{{{A@b{IH`}}}AILh}\",[]]A`[\"{NKdBCKd}\",[]]Ai[\"{{{A@`{NIb}}}BBJb}\",[]]Be[\"{{{A@`{B@Ob}}{A@`{BEOf}}}B@O`}\",[]]Aj[\"{{{A@`{B@Hf}}}B@Hd}\",[]]B`[\"{{{AHLl{{JOf{Of}}}}}B@Jb}\",[]]Ae[\"{{CMhKAn}BEGb}\",[]]Ah[\"{{DAdA@hCMj}ABAj}\",[]]Ao[\"{{{A@`{AOGn}}NIhCMf}ICf}\",[]]Ao[\"{{{A@`{BANf}}NIhCMf}NJd}\",[]]Ae[\"{{KAnCMh}BAK`}\",[]]Ai[\"{{{A@b{IH`}}}AILj}\",[]]Bo[\"{{{A@`{BBJd}}{KEh{{A@`{B@Od}}}}DBh}AAO`}\",[]]A`[\"{KAnBIAh}\",[]]o[\"{{}BHMh}\",[]]A`[\"{NKdBCKf}\",[]]Ai[\"{{{A@`{NId}}}BBJd}\",[]]Be[\"{{{A@`{B@Of}}{A@`{BEOh}}}B@Od}\",[]]Aj[\"{{{A@`{B@Hj}}}B@Hh}\",[]]Ek[\"{{AAMh{AHLl{c}}{AHLl{{JOf{CMh}}}}DBh}{{KF`{{KAl{BANl{KEh{AAMh}}}}BF@d}}}{BFDjIGf}}\",[\"\"]]B`[\"{{{AHLl{{JOf{Of}}}}}B@Jd}\",[]]Af[\"{{BFHfKGh}BBMl}\",[]]A`[\"{KAnBHNb}\",[]]Ae[\"{{CMhKAn}BEGd}\",[]]Ah[\"{{DAfA@jCMj}ABAl}\",[]]Am[\"{{AIN`{KEh{KDn}}}AHA`}\",[]]Ao[\"{{{A@`{AOH`}}NIhCMf}ICh}\",[]]Ao[\"{{{A@`{BANh}}NIhCMf}NJf}\",[]]Aj[\"{AIOd{{KFb{BBMn}}}}\",[]]Aa[\"{BEFlBHGl}\",[]]Aa[\"{BEG`BHGn}\",[]]o[\"{DAdDAd}\",[]]o[\"{DAfDAf}\",[]]Ak[\"{{{AHLl{AINh}}}AINf}\",[]]Ai[\"{{{AHLl{NKl}}}NIh}\",[]]0Aj[\"{{{AHLl{B@On}}}NIh}\",[]]Ak[\"{{{AHLl{BAOn}}}AINf}\",[]]Ak[\"{{{AHLl{AINj}}}AINf}\",[]]Ak[\"{{{AHLl{BB@`}}}AINf}\",[]]Bg[\"{{{AHLl{AIIl}}KEj}{{KEh{AANj}}}}\",[]]Aa[\"{AHChAANj}\",[]]Bg[\"{{{AHLl{AIIn}}KEl}{{KEh{AANj}}}}\",[]]Aa[\"{AHCjAANj}\",[]]Ah[\"{AAj{{KFd{IBh}}}}\",[]]Ah[\"{AAl{{KFd{IBh}}}}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]Aa[\"{B@LlAIN`}\",[]]Aa[\"{B@LnAIN`}\",[]]Aa[\"{ABAfAIN`}\",[]]Aa[\"{BBMlBFHf}\",[]]A`[\"{AHChCMj}\",[]]A`[\"{AHCjCMj}\",[]]A`[\"{AANbIBl}\",[]]Am[\"{{{AHLl{CFlAB@l}}}IBh}\",[]]Am[\"{{{AHLl{CFlAAOf}}}IBh}\",[]]Al[\"{{{AHLl{CFlD@h}}}IBh}\",[]]Am[\"{{{AHLl{CFlBBN`}}}IBh}\",[]]Am[\"{{{AHLl{CFlAILn}}}IBh}\",[]]Am[\"{{{AHLl{CFlAIMj}}}IBh}\",[]]Al[\"{{{AHLl{CFlKCh}}}IBh}\",[]]A`[\"{cc{}}\",[\"T\"]]00000000000000000000000Aa[\"{BCIhBF@l}\",[]]111111111111111Aa[\"{AHCnBBMb}\",[]]Aa[\"{BCIhAHCn}\",[]]Aa[\"{AIMbAHCn}\",[]]4Aa[\"{BCIhAIMb}\",[]]5Aa[\"{AHCnBBM`}\",[]]6666Aa[\"{BCIhAIOf}\",[]]Aa[\"{B@I`AIOf}\",[]]8Aa[\"{B@OlAIOf}\",[]]9Aa[\"{BCIhB@Ol}\",[]]:::::::o[\"{CMhKGl}\",[]]A`[\"{ABAjKGl}\",[]]>>>>A`[\"{IEdBBMl}\",[]]?A`[\"{KGnBFHf}\",[]]A`[\"{cc{}}\",[\"T\"]]00000A`[\"{KGnABAl}\",[]]11111A`[\"{BB@bIEd}\",[]]2222222A`[\"{BHD`IEb}\",[]]A`[\"{BECdIEb}\",[]]A`[\"{BEDjIEb}\",[]]A`[\"{AIKjIEb}\",[]]A`[\"{BCJdIEb}\",[]]A`[\"{BHIfIEb}\",[]]A`[\"{BHJhIEb}\",[]]A`[\"{BEFdIEb}\",[]]A`[\"{BBK`IEb}\",[]]A`[\"{BCKnIEb}\",[]]A`[\"{BBLbIEb}\",[]]=============An[\"{{{AHLl{c}}}AIOdCN`}\",[\"S\"]]>>>>>>>>>>>>>>Bm[\"{{{AHLl{KGl}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bk[\"{{{AHLl{KEj}}{AHLl{CFlc}}}IBhKDl}\",[\"H\"]]Bn[\"{{{AHLl{AOI`}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{B@Jb}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bm[\"{{{AHLl{DAd}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bm[\"{{{AHLl{A@h}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{ABAj}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bm[\"{{{AHLl{KGn}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{BF@n}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bk[\"{{{AHLl{KEl}}{AHLl{CFlc}}}IBhKDl}\",[\"H\"]]Bn[\"{{{AHLl{AHD`}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{BAOf}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{AIOh}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{AOIb}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{BECj}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{BF@d}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{B@Jd}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bm[\"{{{AHLl{DAf}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bm[\"{{{AHLl{A@j}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{ABAl}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Bn[\"{{{AHLl{AHA`}}{AHLl{CFlc}}}IBhKDl}\",[\"__H\"]]Aa[\"{{}c{}}\",[\"U\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n[\"{{}IBh}\",[]]0000Cc[\"{{{AHLl{CFlKCh}}KAn}{{KF`{{KEh{IC`}}AHD`}}}}\",[]]Cf[\"{{{AHLl{CFlBAMd}}}{{KEh{c}}}{}}\",[\"Iterator::Item\"]]Dl[\"{{{AHLl{BEOf}}IBf{AHLl{CFl{JOf{Of}}}}}{{KF`{{AHLl{{JOf{Of}}}}AOGj}}}}\",[]]Bi[\"{{{AHLl{CFlNLn}}A@j}{{KEh{ABAl}}}}\",[]]Dl[\"{{{AHLl{BEOh}}IBf{AHLl{CFl{JOf{Of}}}}}{{KF`{{AHLl{{JOf{Of}}}}AOGl}}}}\",[]]Bf[\"{{{AHLl{AIIl}}KEj}{{KEh{D@h}}}}\",[]]Bf[\"{{{AHLl{D@h}}}{{KF`{IBhAIHn}}}}\",[]]A`[\"{AIJhCMj}\",[]]Am[\"{{{AHLl{CFlAIIn}}}IBh}\",[]]A`[\"{AIJjCMj}\",[]]Ck[\"{{{AB`{{AHLl{CFlAIGn}}}}{AHLl{CFlNHf}}}{{DA`{IBh}}}}\",[]]Dk[\"{{{AB`{{AHLl{CFlBCM`}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AB`{{AHLl{CFlAB@l}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AB`{{AHLl{CFlAB@n}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AB`{{AHLl{CFlAIIh}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{AB`{{AHLl{CFlNJj}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{AB`{{AHLl{CFlKEb}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AB`{{AHLl{CFlAGNf}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AB`{{AHLl{CFlAAMf}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AB`{{AHLl{CFlB@Ml}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AB`{{AHLl{CFlB@Nj}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{AB`{{AHLl{CFlKBf}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dk[\"{{{AB`{{AHLl{CFlBBJl}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Dj[\"{{{AB`{{AHLl{CFlNLf}}}}{AHLl{CFlNHf}}}{{DA`{c}}}{}}\",[\"Future::Output\"]]Bf[\"{{{AHLl{CFlAIIn}}}{{KEh{IEj}}}}\",[]]De[\"{{{AHLl{CFlAILn}}{AHLl{CFl{JOf{Of}}}}}{{KF`{{KEh{KAn}}AHCn}}}}\",[]]Bm[\"{{{AHLl{CFlAIM`}}DBh}{{KF`{KChBAOf}}}}\",[]]C`[\"{{DAf{AHLl{CFlc}}}{{KF`{BFNdIEd}}}A@d}\",[\"R\"]]Bf[\"{{{AHLl{CFlAH@`}}}{{KEh{IBl}}}}\",[]]Ec[\"{{{AHLl{BBMn}}AIOd{AHLl{CFl{JOf{AIKb}}}}{AHLl{CFl{JOf{ABAf}}}}}{{KFb{KAn}}}}\",[]]Cn[\"{{{AHLl{BEOf}}IBf{AHLl{CFl{ABf{Of}}}}}{{KF`{IBhAOGj}}}}\",[]]Cn[\"{{{AHLl{BEOh}}IBf{AHLl{CFl{ABf{Of}}}}}{{KF`{IBhAOGl}}}}\",[]]C`[\"{{{AHLl{CFlAH@`}}IBlDBh}{{KF`{IBhBF@n}}}}\",[]]Cc[\"{{{AHLl{BBMn}}AIOd{AHLl{ABCb}}}{{KFb{IBh}}}}\",[]]Bn[\"{{{AHLl{CFlBGMb}}CMh}{{AHLl{CFlBGMb}}}}\",[]]Bn[\"{{{AHLl{CFlBGMd}}CMh}{{AHLl{CFlBGMd}}}}\",[]]Aj[\"{{{AHLl{AIIl}}}DAd}\",[]]Aj[\"{{{AHLl{AIIn}}}DAf}\",[]]Cn[\"{{{AHLl{NIb}}{AHLl{{JOf{Of}}}}{AHLl{CFl{JOf{Of}}}}}IBh}\",[]]Cn[\"{{{AHLl{NId}}{AHLl{{JOf{Of}}}}{AHLl{CFl{JOf{Of}}}}}IBh}\",[]]A`[\"{BBMfCMj}\",[]]A`[\"{ABBnKAn}\",[]]A`[\"{ABC`KAn}\",[]]o[\"{IGbDBh}\",[]]Bm[\"{{{AHLl{CFlAILn}}KGl}{{KF`{IBhB@I`}}}}\",[]]Bm[\"{{{AHLl{CFlBFNf}}KGl}{{KF`{IBhB@I`}}}}\",[]]Bm[\"{{{AHLl{CFlAIM`}}KGn}{{KF`{IBhB@Ih}}}}\",[]]Bn[\"{{{AHLl{AIO`}}{AHLl{CMd}}}{{KEh{IBl}}}}\",[]]Bn[\"{{{AHLl{BBKn}}{AHLl{CMd}}}{{KEh{IBl}}}}\",[]]Bn[\"{{{AHLl{BEGb}}{AHLl{CMd}}}{{KEh{IBl}}}}\",[]]Bn[\"{{{AHLl{AIOb}}{AHLl{CMd}}}{{KEh{IBl}}}}\",[]]Bn[\"{{{AHLl{BBL`}}{AHLl{CMd}}}{{KEh{IBl}}}}\",[]]Bn[\"{{{AHLl{BEGd}}{AHLl{CMd}}}{{KEh{IBl}}}}\",[]]Bd[\"{{{AHLl{BECn}}}{{KEh{BAMb}}}}\",[]]Bd[\"{{{AHLl{BED`}}}{{KEh{BAMd}}}}\",[]]Bi[\"{{{A@`{BENn}}NIhCMf}{{A@b{AIJ`}}}}\",[]]Bi[\"{{{A@`{AIHf}}NIhCMf}{{A@b{AIJ`}}}}\",[]]Bi[\"{{{A@`{AOGn}}NIhCMf}{{A@b{AIJ`}}}}\",[]]Bi[\"{{{A@`{BANf}}NIhCMf}{{A@b{AIJ`}}}}\",[]]Bi[\"{{{A@`{BEO`}}NIhCMf}{{A@b{AIJb}}}}\",[]]Bi[\"{{{A@`{AIHh}}NIhCMf}{{A@b{AIJb}}}}\",[]]Bi[\"{{{A@`{AOH`}}NIhCMf}{{A@b{AIJb}}}}\",[]]Bi[\"{{{A@`{BANh}}NIhCMf}{{A@b{AIJb}}}}\",[]]Aj[\"{BFMd{{A@`{BEO`}}}}\",[]]o[\"{IBnIBl}\",[]]A`[\"{ABCdCMj}\",[]]o[\"{IC`IBl}\",[]]A`[\"{ABCfCMj}\",[]]Aa[\"{AOFjBCIj}\",[]]Ai[\"{{{AHLl{AAh}}}AAh}\",[]]Ak[\"{{{AHLl{AOJf}}}AOJf}\",[]]Ak[\"{{{AHLl{AOFn}}}AOFn}\",[]]Ak[\"{{{AHLl{AIIl}}}AIIl}\",[]]Ai[\"{{{AHLl{KD`}}}KD`}\",[]]Ak[\"{{{AHLl{BGFf}}}BGFf}\",[]]Ak[\"{{{AHLl{BF@l}}}BF@l}\",[]]Ak[\"{{{AHLl{AANn}}}AANn}\",[]]Ak[\"{{{AHLl{BAL`}}}BAL`}\",[]]Ai[\"{{{AHLl{KE`}}}KE`}\",[]]Ai[\"{{{AHLl{D@h}}}D@h}\",[]]Ak[\"{{{AHLl{BBN`}}}BBN`}\",[]]Ak[\"{{{AHLl{BBMb}}}BBMb}\",[]]Ak[\"{{{AHLl{AHCn}}}AHCn}\",[]]Ak[\"{{{AHLl{AIMb}}}AIMb}\",[]]Ak[\"{{{AHLl{BBM`}}}BBM`}\",[]]Ak[\"{{{AHLl{AIOf}}}AIOf}\",[]]Ak[\"{{{AHLl{B@Ol}}}B@Ol}\",[]]Ai[\"{{{AHLl{KGl}}}KGl}\",[]]Ak[\"{{{AHLl{BGF`}}}BGF`}\",[]]Ai[\"{{{AHLl{KEj}}}KEj}\",[]]Ak[\"{{{AHLl{AILd}}}AILd}\",[]]Ak[\"{{{AHLl{AHCd}}}AHCd}\",[]]Ak[\"{{{AHLl{BFGj}}}BFGj}\",[]]Ak[\"{{{AHLl{AIHn}}}AIHn}\",[]]Ak[\"{{{AHLl{ABCd}}}ABCd}\",[]]Ak[\"{{{AHLl{AIJh}}}AIJh}\",[]]Ak[\"{{{AHLl{AHCh}}}AHCh}\",[]]Ak[\"{{{AHLl{BCJ`}}}BCJ`}\",[]]Ak[\"{{{AHLl{B@I`}}}B@I`}\",[]]Ak[\"{{{AHLl{BCIh}}}BCIh}\",[]]Ak[\"{{{AHLl{AHBl}}}AHBl}\",[]]Ak[\"{{{AHLl{BBHn}}}BBHn}\",[]]Ak[\"{{{AHLl{BCLf}}}BCLf}\",[]]Ak[\"{{{AHLl{BFCn}}}BFCn}\",[]]Ak[\"{{{AHLl{BFEl}}}BFEl}\",[]]Ak[\"{{{AHLl{AOI`}}}AOI`}\",[]]Ak[\"{{{AHLl{BBJb}}}BBJb}\",[]]Ak[\"{{{AHLl{B@O`}}}B@O`}\",[]]Ak[\"{{{AHLl{BGMb}}}BGMb}\",[]]Ak[\"{{{AHLl{B@Hd}}}B@Hd}\",[]]Ak[\"{{{AHLl{AOGd}}}AOGd}\",[]]Ak[\"{{{AHLl{AHAb}}}AHAb}\",[]]Ak[\"{{{AHLl{BCIb}}}BCIb}\",[]]Ak[\"{{{AHLl{BEBn}}}BEBn}\",[]]Ak[\"{{{AHLl{B@Il}}}B@Il}\",[]]Ak[\"{{{AHLl{B@Jb}}}B@Jb}\",[]]Ak[\"{{{AHLl{B@Jl}}}B@Jl}\",[]]Ak[\"{{{AHLl{BFHd}}}BFHd}\",[]]Ak[\"{{{AHLl{AIJn}}}AIJn}\",[]]Ai[\"{{{AHLl{DAd}}}DAd}\",[]]Ai[\"{{{AHLl{A@h}}}A@h}\",[]]Ak[\"{{{AHLl{ABAj}}}ABAj}\",[]]Ai[\"{{{AHLl{CNb}}}CNb}\",[]]Ak[\"{{{AHLl{AIHf}}}AIHf}\",[]]Ai[\"{{{AHLl{ICf}}}ICf}\",[]]Ak[\"{{{AHLl{AOGn}}}AOGn}\",[]]Ai[\"{{{AHLl{NJd}}}NJd}\",[]]Ak[\"{{{AHLl{BANf}}}BANf}\",[]]Ai[\"{{{AHLl{IE`}}}IE`}\",[]]Ak[\"{{{AHLl{BEFj}}}BEFj}\",[]]Ak[\"{{{AHLl{BGEd}}}BGEd}\",[]]Ak[\"{{{AHLl{BEFn}}}BEFn}\",[]]Ai[\"{{{AHLl{KGn}}}KGn}\",[]]Ak[\"{{{AHLl{BGFb}}}BGFb}\",[]]Ak[\"{{{AHLl{BF@n}}}BF@n}\",[]]Ai[\"{{{AHLl{KEl}}}KEl}\",[]]Ak[\"{{{AHLl{B@Nh}}}B@Nh}\",[]]Ak[\"{{{AHLl{AILf}}}AILf}\",[]]Ak[\"{{{AHLl{AHCf}}}AHCf}\",[]]Ak[\"{{{AHLl{BF@j}}}BF@j}\",[]]Ak[\"{{{AHLl{BFGl}}}BFGl}\",[]]Ak[\"{{{AHLl{AIId}}}AIId}\",[]]Ak[\"{{{AHLl{ABCf}}}ABCf}\",[]]Ak[\"{{{AHLl{AIJj}}}AIJj}\",[]]Ak[\"{{{AHLl{AHCj}}}AHCj}\",[]]Ak[\"{{{AHLl{BCJb}}}BCJb}\",[]]Ak[\"{{{AHLl{AHD`}}}AHD`}\",[]]Ak[\"{{{AHLl{BAOf}}}BAOf}\",[]]Ak[\"{{{AHLl{AIOh}}}AIOh}\",[]]Ak[\"{{{AHLl{AOHn}}}AOHn}\",[]]Ak[\"{{{AHLl{BBMd}}}BBMd}\",[]]Ak[\"{{{AHLl{B@Ih}}}B@Ih}\",[]]Ak[\"{{{AHLl{BCIj}}}BCIj}\",[]]Ak[\"{{{AHLl{AHBn}}}AHBn}\",[]]Ak[\"{{{AHLl{BBI`}}}BBI`}\",[]]Ak[\"{{{AHLl{BHAb}}}BHAb}\",[]]Ak[\"{{{AHLl{BCLh}}}BCLh}\",[]]Ak[\"{{{AHLl{BFD`}}}BFD`}\",[]]Ak[\"{{{AHLl{BFEn}}}BFEn}\",[]]Ak[\"{{{AHLl{AOIb}}}AOIb}\",[]]Ak[\"{{{AHLl{BBJd}}}BBJd}\",[]]Ak[\"{{{AHLl{B@Od}}}B@Od}\",[]]Ak[\"{{{AHLl{BGMd}}}BGMd}\",[]]Ak[\"{{{AHLl{B@Hh}}}B@Hh}\",[]]Ak[\"{{{AHLl{AOGf}}}AOGf}\",[]]Ak[\"{{{AHLl{AHAd}}}AHAd}\",[]]Ak[\"{{{AHLl{AIKf}}}AIKf}\",[]]Ak[\"{{{AHLl{AINb}}}AINb}\",[]]Ak[\"{{{AHLl{B@Jf}}}B@Jf}\",[]]Ak[\"{{{AHLl{BCIf}}}BCIf}\",[]]Ak[\"{{{AHLl{BECb}}}BECb}\",[]]Ak[\"{{{AHLl{AANb}}}AANb}\",[]]Ak[\"{{{AHLl{BECj}}}BECj}\",[]]Ak[\"{{{AHLl{B@In}}}B@In}\",[]]Ak[\"{{{AHLl{BCKb}}}BCKb}\",[]]Ak[\"{{{AHLl{BHAj}}}BHAj}\",[]]Ak[\"{{{AHLl{AAOn}}}AAOn}\",[]]Ak[\"{{{AHLl{BF@d}}}BF@d}\",[]]Ak[\"{{{AHLl{B@Jd}}}B@Jd}\",[]]Ak[\"{{{AHLl{B@Jn}}}B@Jn}\",[]]Ak[\"{{{AHLl{BBMl}}}BBMl}\",[]]Ak[\"{{{AHLl{BFHf}}}BFHf}\",[]]Ak[\"{{{AHLl{AIK`}}}AIK`}\",[]]Ak[\"{{{AHLl{BHNb}}}BHNb}\",[]]Ai[\"{{{AHLl{DAf}}}DAf}\",[]]Ai[\"{{{AHLl{A@j}}}A@j}\",[]]Ak[\"{{{AHLl{ABAl}}}ABAl}\",[]]Ak[\"{{{AHLl{AHA`}}}AHA`}\",[]]Ak[\"{{{AHLl{BB@b}}}BB@b}\",[]]Ak[\"{{{AHLl{BFNd}}}BFNd}\",[]]Ai[\"{{{AHLl{IEd}}}IEd}\",[]]Ai[\"{{{AHLl{IEb}}}IEb}\",[]]Ak[\"{{{AHLl{BEFl}}}BEFl}\",[]]Ak[\"{{{AHLl{BGEf}}}BGEf}\",[]]Ak[\"{{{AHLl{BEG`}}}BEG`}\",[]]Ai[\"{{{AHLl{CNd}}}CNd}\",[]]Ak[\"{{{AHLl{AIHh}}}AIHh}\",[]]Ai[\"{{{AHLl{ICh}}}ICh}\",[]]Ak[\"{{{AHLl{AOH`}}}AOH`}\",[]]Ai[\"{{{AHLl{NJf}}}NJf}\",[]]Ak[\"{{{AHLl{BANh}}}BANh}\",[]]Ai[\"{{{AHLl{IEl}}}IEl}\",[]]Ak[\"{{{AHLl{ABAf}}}ABAf}\",[]]Ak[\"{{{AHLl{ABCb}}}ABCb}\",[]]Ak[\"{{{AHLl{B@K`}}}B@K`}\",[]]Ak[\"{{{AHLl{BFMd}}}BFMd}\",[]]Ak[\"{{{AHLl{BCJj}}}BCJj}\",[]]Ak[\"{{{AHLl{BHGl}}}BHGl}\",[]]Ak[\"{{{AHLl{BHGn}}}BHGn}\",[]]Bn[\"{{{AHLl{AIIl}}KGl{AHLl{{JOf{Of}}}}}IBh}\",[]]Bn[\"{{{AHLl{AANn}}KGl{AHLl{{JOf{Of}}}}}IBh}\",[]]Bf[\"{{{AHLl{D@h}}}{{KF`{IBhBBHn}}}}\",[]]Bf[\"{{{AHLl{CFlAIIn}}NIhKGnIBl}IBh}\",[]]Bj[\"{{{AHLl{B@Jb}}}{{AHLl{{JOf{Of}}}}}}\",[]]Bj[\"{{{AHLl{B@Jd}}}{{AHLl{{JOf{Of}}}}}}\",[]]Aa[\"{BBMlAIKf}\",[]]A`[\"{ABAjCMj}\",[]]A`[\"{ABAlCMj}\",[]]A`[\"{B@JlDBh}\",[]]A`[\"{B@JnDBh}\",[]]l[\"{NIj}\",[]]l[\"{NIl}\",[]]Cf[\"{{{AHLl{BFDj}}{AHLl{CFlA@d}}}{{KF`{B@JdBF@d}}}}\",[]]Cf[\"{{{AHLl{BIAh}}{AHLl{CFlA@d}}}{{KF`{B@JdBF@d}}}}\",[]]Bc[\"{{{AHLl{AIIn}}}{{ABf{KEl}}}}\",[]]Bd[\"{{{AHLl{IGh}}{AHLl{CMd}}}IBh}\",[]]Am[\"{{{AHLl{AOG`}}KAn}IBh}\",[]]Ai[\"{{{AHLl{IGj}}}IBh}\",[]]Bg[\"{{{AB`{{AHLl{CFlAIGn}}}}NIh}IBh}\",[]]Bm[\"{{{AHLl{CFlAIMj}}KGl}{{KF`{IBhB@I`}}}}\",[]]Bm[\"{{{AHLl{CFlAIMl}}KGn}{{KF`{IBhB@Ih}}}}\",[]]Am[\"{AAOf{{KF`{IBhAIMd}}}}\",[]]Dc[\"{{{AHLl{CFlAAO`}}AAOh{AHLl{CFl{ABf{Of}}}}}{{KF`{ABC`AIMf}}}}\",[]]Bj[\"{{{AHLl{NKl}}{AB`{{A@b{KDj}}}}}IBh}\",[]]Bk[\"{{{AHLl{B@On}}{AB`{{A@b{KDj}}}}}IBh}\",[]]Bk[\"{{{AHLl{AOJl}}{AB`{{A@b{KDj}}}}}IBh}\",[]]o[\"{AAhDBh}\",[]]Ak[\"{{{AHLl{AIIl}}}BCJ`}\",[]]Ak[\"{{{AHLl{AANn}}}BAL`}\",[]]Aj[\"{{{AHLl{D@h}}}AHCh}\",[]]A`[\"{KD`BCJ`}\",[]]An[\"{{{AHLl{CFlAIIn}}}BCJb}\",[]]Ce[\"{{{AHLl{CFlAILh}}{KEh{KGh}}}{{AHLl{CFlAILh}}}}\",[]]Ce[\"{{{AHLl{CFlAILj}}{KEh{KGh}}}{{AHLl{CFlAILj}}}}\",[]]Ck[\"{{{AHLl{CFlAIMj}}{AHLl{{JOf{Of}}}}}{{KF`{KAnAIOf}}}}\",[]]Ck[\"{{{AHLl{CFlAIMl}}{AHLl{{JOf{Of}}}}}{{KF`{KAnAIOh}}}}\",[]]Bl[\"{{{AHLl{BFNd}}{AHLl{CFlc}}}IBhKCf}\",[\"W\"]]Aj[\"{{{AHLl{AANn}}}KBf}\",[]]An[\"{AAOf{{KF`{AIIhBCIh}}}}\",[]]Bi[\"{{{AHLl{CFlNLn}}A@j}{{KEh{ABAl}}}}\",[]]Fj[\"{{{AHLl{CFlAAO`}}AAOhNIh{AHLl{CFl{ABf{Of}}}}{KEh{{A@`{B@Od}}}}}{{KF`{{KAl{BECjAIIn}}{A@b{AOFj}}}}}}\",[]]Am[\"{AHLl{{AHLl{c}}}{}}\",[\"T\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A`[\"{AAhAOFn}\",[]]Bc[\"{{AIN`BBHlAAh}{{KFd{AOG`}}}}\",[]]Aj[\"{AIN`{{KFb{AANn}}}}\",[]]Ba[\"{{}{{KF`{IBh{A@b{IDb}}}}}}\",[]]Ak[\"{{{AHLl{AIIl}}}BCIh}\",[]]Be[\"{{{AHLl{AAO`}}}{{AHLl{BBJd}}}}\",[]]o[\"{OfBFHd}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{B@O`A@`}\",[]]o[\"{OfBFHf}\",[]]A`[\"{AIJjCMj}\",[]]A`[\"{B@OdA@`}\",[]]A`[\"{BBMlKEh}\",[]]Bm[\"{{{AHLl{CFlc}}}{{KF`{KGlBB@b}}}A@d}\",[\"B\"]]Bm[\"{{{AHLl{CFlc}}}{{KF`{KEjBB@b}}}A@d}\",[\"B\"]]Bn[\"{{{AHLl{CFlc}}}{{KF`{AHAbBB@b}}}A@d}\",[\"B\"]]Bn[\"{{{AHLl{CFlc}}}{{KF`{BFHdBB@b}}}A@d}\",[\"B\"]]Bn[\"{{{AHLl{CFlc}}}{{KF`{ABAjBB@b}}}A@d}\",[\"B\"]]Bi[\"{{{AHLl{CFlc}}}{{KFh{KGn}}}A@d}\",[\"B\"]]Bi[\"{{{AHLl{CFlc}}}{{KFh{KEl}}}A@d}\",[\"B\"]]Bj[\"{{{AHLl{CFlc}}}{{KFh{AHAd}}}A@d}\",[\"B\"]]Bj[\"{{{AHLl{CFlc}}}{{KFh{AIKf}}}A@d}\",[\"B\"]]Ek[\"{{{AHLl{CFl{KDh{AAMh}}}}{AHLl{c}}{AHLl{{JOf{CMh}}}}DBh}{{KF`{BCKbBF@d}}}{BFDjIGf}}\",[\"\"]]Bj[\"{{{AHLl{CFlc}}}{{KFh{BFHf}}}A@d}\",[\"B\"]]Bj[\"{{{AHLl{CFlc}}}{{KFh{ABAl}}}A@d}\",[\"B\"]]Bj[\"{{{AHLl{CFlc}}}{{KFh{AH@b}}}A@d}\",[\"B\"]]A`[\"{ABAfKEh}\",[]]Bk[\"{{{AHLl{KGl}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bk[\"{{{AHLl{KEj}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bl[\"{{{AHLl{AHAb}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bl[\"{{{AHLl{BFHd}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bl[\"{{{AHLl{ABAj}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bk[\"{{{AHLl{KGn}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bk[\"{{{AHLl{KEl}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bl[\"{{{AHLl{AHAd}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bl[\"{{{AHLl{AIKf}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bl[\"{{{AHLl{AANb}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bl[\"{{{AHLl{BFHf}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bl[\"{{{AHLl{ABAl}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bl[\"{{{AHLl{AH@l}}{AHLl{CFlc}}}IBhKCf}\",[\"B\"]]Bj[\"{{{AHLl{CFlAIMj}}}{{KF`{IBhB@I`}}}}\",[]]Bj[\"{{{AHLl{CFlAIMl}}}{{KF`{IBhAOHn}}}}\",[]]An[\"{{{AHLl{AOKh}}AANj}IBh}\",[]]Ea[\"{{{AHLl{CFlAAO`}}NIhAHA`{KEh{B@Jn}}AAMh{AHLl{CFl{ABf{Of}}}}}{{KEh{BAKl}}}}\",[]]o[\"{COlNIj}\",[]]o[\"{COnNIl}\",[]]A`[\"{AAOfIBh}\",[]]Ba[\"{{{AHLl{CFlAAO`}}AAOh}IBh}\",[]]Bh[\"{{{AHLl{AIO`}}{AHLl{CMd}}IBl}IBh}\",[]]Bh[\"{{{AHLl{BBKn}}{AHLl{CMd}}IBl}IBh}\",[]]Bh[\"{{{AHLl{BEGb}}{AHLl{CMd}}IBl}IBh}\",[]]Bh[\"{{{AHLl{AIOb}}{AHLl{CMd}}IBl}IBh}\",[]]Bh[\"{{{AHLl{BBL`}}{AHLl{CMd}}IBl}IBh}\",[]]Bh[\"{{{AHLl{BEGd}}{AHLl{CMd}}IBl}IBh}\",[]]A`[\"{AGOjDBh}\",[]]o[\"{IBnCMj}\",[]]o[\"{IC`CMj}\",[]]Ch[\"{{{AHLl{CFlAIJ`}}NIhNIhCMjCMjDBh{AHLl{B@Nf}}}IBh}\",[]]Cg[\"{{{AHLl{CFlCNb}}NIhNIhCMjCMjDBh{AHLl{B@Nf}}}IBh}\",[]]Cg[\"{{{AHLl{CFlICf}}NIhNIhCMjCMjDBh{AHLl{B@Nf}}}IBh}\",[]]Cg[\"{{{AHLl{CFlNJd}}NIhNIhCMjCMjDBh{AHLl{B@Nf}}}IBh}\",[]]Ch[\"{{{AHLl{CFlAIJb}}NIhNIhCMjCMjDBh{AHLl{B@Nh}}}IBh}\",[]]Cg[\"{{{AHLl{CFlCNd}}NIhNIhCMjCMjDBh{AHLl{B@Nh}}}IBh}\",[]]Cg[\"{{{AHLl{CFlICh}}NIhNIhCMjCMjDBh{AHLl{B@Nh}}}IBh}\",[]]Cg[\"{{{AHLl{CFlNJf}}NIhNIhCMjCMjDBh{AHLl{B@Nh}}}IBh}\",[]]o[\"{COlNIj}\",[]]o[\"{COnNIl}\",[]]o[\"{BEGnOf}\",[]]A`[\"{KD`BCIh}\",[]]A`[\"{BCIbIBl}\",[]]A`[\"{BEBnIBl}\",[]]Aa[\"{AGMnBF@h}\",[]]A`[\"{BCIfIBl}\",[]]A`[\"{BECbIBl}\",[]]A`[\"{BBMlKGh}\",[]]Aa[\"{BBIdBCIj}\",[]]Aa[\"{AGN`BF@j}\",[]]Bg[\"{{{AHLl{AANn}}AHDh}{{KFb{IBh}}}}\",[]]A`[\"{AAOfIBh}\",[]]Cf[\"{{{AHLl{CFlAAO`}}AAOh{AHLl{CFl{ABf{Of}}}}}ABC`}\",[]]l[\"{NIj}\",[]]Ak[\"{{{AHLl{AHA`}}}AIN`}\",[]]l[\"{NIl}\",[]]An[\"{{AANlAAh}{{KFd{IBh}}}}\",[]]B`[\"{{B@O`AIN`}{{KFb{AANn}}}}\",[]]Bc[\"{B@Nn{{KF`{IBh{A@b{IDb}}}}}}\",[]]Bk[\"{{{AHLl{BF@l}}}{{KEh{{AHLl{IDb}}}}}}\",[]]Bk[\"{{{AHLl{BBMb}}}{{KEh{{AHLl{IDb}}}}}}\",[]]Bk[\"{{{AHLl{AHCn}}}{{KEh{{AHLl{IDb}}}}}}\",[]]Bk[\"{{{AHLl{AIMb}}}{{KEh{{AHLl{IDb}}}}}}\",[]]Bk[\"{{{AHLl{BBM`}}}{{KEh{{AHLl{IDb}}}}}}\",[]]Bk[\"{{{AHLl{AIOf}}}{{KEh{{AHLl{IDb}}}}}}\",[]]Bk[\"{{{AHLl{B@Ol}}}{{KEh{{AHLl{IDb}}}}}}\",[]]Bk[\"{{{AHLl{BCIh}}}{{KEh{{AHLl{IDb}}}}}}\",[]]Bk[\"{{{AHLl{BCIj}}}{{KEh{{AHLl{IDb}}}}}}\",[]]A`[\"{ABBnKEh}\",[]]A`[\"{ABC`KEh}\",[]]A`[\"{ABCbKEh}\",[]]Bg[\"{{{AHLl{D@h}}}{{KF`{AILdAIHn}}}}\",[]]Aa[\"{B@MnAILd}\",[]]Aa[\"{B@N`AILf}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]A`[\"{ABAfKAn}\",[]]Aa[\"{AHChABCd}\",[]]Aa[\"{BCJ`ABCd}\",[]]Aa[\"{AHCjABCf}\",[]]Aa[\"{BCJbABCf}\",[]]3210Dh[\"{{{AHLl{NIb}}{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}{{KF`{IBhAOGj}}}}\",[]]Dh[\"{{{AHLl{NId}}{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}{{KF`{IBhAOGl}}}}\",[]]Aj[\"{{{AHLl{AIJ`}}}CMj}\",[]]Ai[\"{{{AHLl{CNb}}}CMj}\",[]]Ai[\"{{{AHLl{ICf}}}CMj}\",[]]Ai[\"{{{AHLl{NJd}}}CMj}\",[]]Aj[\"{{{AHLl{AIJb}}}CMj}\",[]]Ai[\"{{{AHLl{CNd}}}CMj}\",[]]Ai[\"{{{AHLl{ICh}}}CMj}\",[]]Ai[\"{{{AHLl{NJf}}}CMj}\",[]]Aj[\"{{{AHLl{BGL`}}}KAn}\",[]]Aj[\"{{{AHLl{BGLb}}}KAn}\",[]]Aj[\"{{{AHLl{BHMh}}}KAn}\",[]]Aj[\"{{{AHLl{BHNb}}}KAn}\",[]]o[\"{AAhKAn}\",[]]n[\"{{}NHd}\",[]]0A`[\"{A@fAAMl}\",[]]111Ba[\"{{AHLl{AHLl{c}}}ABA`{}}\",[\"K\"]]0000000000000000000Cg[\"{{{AHLl{AANn}}AIN`{AHLl{CMd}}}{{KF`{AIIhB@Il}}}}\",[]]Dl[\"{{{AHLl{CFlAAO`}}NIhB@HhAIN`{AHLl{CMd}}}{{KF`{{KAl{BECjAIIn}}B@In}}}}\",[]]Bd[\"{{{AHLl{AAOf}}}{{KEh{BECn}}}}\",[]]Dm[\"{{{AHLl{AHBf}}KEjCMj{AHLl{{JOf{Of}}}}{AHLl{CFlAAMh}}}{{KF`{IBhAOGj}}}}\",[]]Ca[\"{{{AHLl{AHAh}}KAn{AHLl{CFl{JOf{Of}}}}}IBh}\",[]]Bd[\"{{{AHLl{AAOh}}}{{KEh{BED`}}}}\",[]]Dm[\"{{{AHLl{AHBh}}KElCMj{AHLl{{JOf{Of}}}}{AHLl{CFlAAMh}}}{{KF`{IBhAOGl}}}}\",[]]Ca[\"{{{AHLl{AHAj}}KAn{AHLl{CFl{JOf{Of}}}}}IBh}\",[]]o[\"{{}AOKf}\",[]]n[\"{{}IGh}\",[]]o[\"{{}AOG`}\",[]]o[\"{{}BAJn}\",[]]o[\"{{}BAL`}\",[]]n[\"{{}KGl}\",[]]n[\"{{}KEj}\",[]]o[\"{{}AILd}\",[]]o[\"{{}AIHn}\",[]]o[\"{{}ABCd}\",[]]o[\"{{}AIJh}\",[]]o[\"{{}AHCh}\",[]]o[\"{{}BCJ`}\",[]]o[\"{{}B@I`}\",[]]o[\"{{}BCLf}\",[]]o[\"{{}BFCn}\",[]]o[\"{{}BFEl}\",[]]o[\"{{}AOI`}\",[]]o[\"{{}BBJb}\",[]]o[\"{{}BGMb}\",[]]o[\"{{}BEGb}\",[]]o[\"{{}AIHf}\",[]]o[\"{{}AOGn}\",[]]o[\"{{}BANf}\",[]]o[\"{{}BEOb}\",[]]o[\"{{}BAK`}\",[]]o[\"{{}BHMh}\",[]]n[\"{{}KGn}\",[]]n[\"{{}KEl}\",[]]o[\"{{}AILf}\",[]]o[\"{{}AIId}\",[]]o[\"{{}ABCf}\",[]]o[\"{{}AIJj}\",[]]o[\"{{}AHCj}\",[]]o[\"{{}BCJb}\",[]]o[\"{{}BBMd}\",[]]o[\"{{}B@Ih}\",[]]o[\"{{}BCLh}\",[]]o[\"{{}BFD`}\",[]]o[\"{{}BFEn}\",[]]o[\"{{}AOIb}\",[]]o[\"{{}BBJd}\",[]]o[\"{{}BGMd}\",[]]o[\"{{}BHNb}\",[]]o[\"{{}BEGd}\",[]]o[\"{{}AIHh}\",[]]o[\"{{}AOH`}\",[]]o[\"{{}BANh}\",[]]o[\"{{}BEOd}\",[]]o[\"{{}ABAf}\",[]]n[\"{{}IGj}\",[]]o[\"{{}BCJj}\",[]]o[\"{{}BAKn}\",[]]Ak[\"{{{AHLl{BANl}}}B@Jd}\",[]]Ak[\"{{{AHLl{BCKb}}}B@Jd}\",[]]Aa[\"{BHAjB@Jd}\",[]]Aa[\"{BFHhB@Jd}\",[]]A`[\"{D@`B@Jd}\",[]]A`[\"{IFnB@Jd}\",[]]A`[\"{IGbB@Jd}\",[]]Aa[\"{BEGnB@Jd}\",[]]Cg[\"{{{AHLl{AHBf}}KEjCMj{AHLl{CFl{JOf{Of}}}}KAn}IBh}\",[]]Ca[\"{{{AHLl{AHAh}}KAn{AHLl{CFl{JOf{Of}}}}}IBh}\",[]]Cg[\"{{{AHLl{AHBh}}KElCMj{AHLl{CFl{JOf{Of}}}}KAn}IBh}\",[]]Ca[\"{{{AHLl{AHAj}}KAn{AHLl{CFl{JOf{Of}}}}}IBh}\",[]]Aj[\"{{{AHLl{AILn}}}DBh}\",[]]Aj[\"{{{AHLl{BFNf}}}DBh}\",[]]Ak[\"{{{AHLl{AIJ`}}}BEOb}\",[]]Aj[\"{{{AHLl{CNb}}}BEOb}\",[]]Aj[\"{{{AHLl{ICf}}}BEOb}\",[]]Aj[\"{{{AHLl{NJd}}}BEOb}\",[]]Ak[\"{{{AHLl{AIJb}}}BEOd}\",[]]Aj[\"{{{AHLl{CNd}}}BEOd}\",[]]Aj[\"{{{AHLl{ICh}}}BEOd}\",[]]Aj[\"{{{AHLl{NJf}}}BEOd}\",[]]Bn[\"{{{AHLl{CFlBCLf}}CMf}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}CMf}{{AHLl{CFlBCLh}}}}\",[]]Bf[\"{{{AHLl{CFlAIJ`}}NIhCMjCMj}IBh}\",[]]Bf[\"{{{AHLl{CFlAIJb}}NIhCMjCMj}IBh}\",[]]Aj[\"{{{AHLl{AIIl}}}KEb}\",[]]Bc[\"{{{AHLl{AB@n}}}{{KEh{KEj}}}}\",[]]Aa[\"{BHAjB@Jd}\",[]]Aa[\"{BFHhB@Jd}\",[]]A`[\"{D@`B@Jd}\",[]]A`[\"{IFnB@Jd}\",[]]Aa[\"{BEGnB@Jd}\",[]]Aj[\"{{{AHLl{AIMj}}}NLf}\",[]]Bn[\"{{{AHLl{AIMl}}}{{KF`{{KEh{KGn}}B@Ih}}}}\",[]]o[\"{AAhKAn}\",[]]o[\"{IGhKAn}\",[]]Am[\"{{{AHLl{CFlAIIn}}}NLn}\",[]]Aj[\"{{{AHLl{AHBf}}}KAn}\",[]]Aj[\"{{{AHLl{AHBh}}}KAn}\",[]]A`[\"{AHLlKGj}\",[]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Bd[\"{{{AHLl{BGFf}}}{{KEh{AIIl}}}}\",[]]Bc[\"{{{AHLl{BBN`}}}{{KEh{D@h}}}}\",[]]Bn[\"{{{AHLl{CFlB@Hd}}CMh}{{AHLl{CFlB@Hd}}}}\",[]]Bn[\"{{{AHLl{CFlB@Hh}}CMh}{{AHLl{CFlB@Hh}}}}\",[]]A`[\"{BHAjCMh}\",[]]A`[\"{BFHhCMh}\",[]]o[\"{D@`CMh}\",[]]o[\"{IFnCMh}\",[]]A`[\"{AGOjKEh}\",[]]Aa[\"{ABCbAHLl}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]Aa[\"{BBMfAANj}\",[]]A`[\"{KChBBMd}\",[]]Aa[\"{AHChAIJh}\",[]]Aa[\"{BCJ`AIJh}\",[]]Aa[\"{AHCjAIJj}\",[]]Aa[\"{BCJbAIJj}\",[]]3210Bh[\"{{{AHLl{CFlc}}KAn}B@Jb{A@dIGf}}\",[\"\"]]Bh[\"{{{AHLl{CFlc}}KAn}B@Jd{A@dIGf}}\",[\"\"]]o[\"{{}BCKd}\",[]]o[\"{{}BCKf}\",[]]A`[\"{CMjBHMh}\",[]]Cd[\"{{{AHLl{CMd}}}{{KFd{AOJfc}}}{}}\",[\"FromStr::Err\"]]Cd[\"{{{AHLl{CMd}}}{{KFd{AOFnc}}}{}}\",[\"FromStr::Err\"]]o[\"{CMhKGl}\",[]]o[\"{CMhKGn}\",[]]Al[\"{CMj{{KF`{KGlBGF`}}}}\",[]]Al[\"{CMj{{KF`{KGnBGFb}}}}\",[]]Aa[\"{BGDdAOGh}\",[]]Aa[\"{BIAjBALl}\",[]]Aa[\"{BGDfAOGh}\",[]]Aa[\"{BIAlBALl}\",[]]Af[\"{{}{{KEh{Ab}}}}\",[]]0000Aj[\"{{{AHLl{AIIn}}}DBh}\",[]]Bo[\"{{{AHLl{CFlBFEl}}AANj}{{AHLl{CFlBFEl}}}}\",[]]Bo[\"{{{AHLl{CFlBFEn}}AANj}{{AHLl{CFlBFEn}}}}\",[]]Ba[\"{{{A@b{AIJ`}}}{{A@b{On}}}}\",[]]B`[\"{{{A@b{CNb}}}{{A@b{On}}}}\",[]]B`[\"{{{A@b{ICf}}}{{A@b{On}}}}\",[]]B`[\"{{{A@b{NJd}}}{{A@b{On}}}}\",[]]Ba[\"{{{A@b{AIJb}}}{{A@b{On}}}}\",[]]B`[\"{{{A@b{CNd}}}{{A@b{On}}}}\",[]]B`[\"{{{A@b{ICh}}}{{A@b{On}}}}\",[]]B`[\"{{{A@b{NJf}}}{{A@b{On}}}}\",[]]Aj[\"{{{AHLl{BGFf}}}DBh}\",[]]Bo[\"{{{AHLl{CFlBGMb}}AANj}{{AHLl{CFlBGMb}}}}\",[]]Bo[\"{{{AHLl{CFlBGMd}}AANj}{{AHLl{CFlBGMd}}}}\",[]]Bc[\"{{{AHLl{AIIh}}}{{KEh{KDn}}}}\",[]]Bc[\"{{{AHLl{AAOf}}}{{KEh{KDn}}}}\",[]]Bm[\"{{{AHLl{D@h}}}{{KF`{{KEh{KDn}}AIHn}}}}\",[]]Bc[\"{{{AHLl{AAOh}}}{{KEh{KDn}}}}\",[]]Bc[\"{{{AHLl{AHA`}}}{{KEh{KDn}}}}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]Bc[\"{{{AHLl{AH@`}}}{{KEh{KAn}}}}\",[]]Am[\"{{{AHLl{AOKh}}KAn}IBh}\",[]]Aj[\"{{{AHLl{AIIl}}}NJj}\",[]]Bg[\"{{{AHLl{AIMj}}}{{KF`{CEfB@I`}}}}\",[]]Bg[\"{{{AHLl{AIMl}}}{{KF`{CEfB@Ih}}}}\",[]]A`[\"{BALdKEh}\",[]]A`[\"{BALfKEh}\",[]]A`[\"{AGOjKEh}\",[]]A`[\"{AOFjKEh}\",[]]Ck[\"{{{AHLl{CFlBBJb}}{KEh{{IHf{Of}}}}}{{AHLl{CFlBBJb}}}}\",[]]Ck[\"{{{AHLl{CFlBBJd}}{KEh{{IHf{Of}}}}}{{AHLl{CFlBBJd}}}}\",[]]A`[\"{BEObKEh}\",[]]A`[\"{BEOdKEh}\",[]]Ac[\"{AHLlc{}}\",[\"T\"]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Ao[\"{c{{KF`{e}}}{}{}}\",[\"U\",\"T\"]]00000000000000000000000000000000000000000000000000000Al[\"{KAn{{KF`{KGlBGF`}}}}\",[]]Al[\"{IBf{{KF`{KGlBGF`}}}}\",[]]2Al[\"{CMj{{KF`{KGlBGF`}}}}\",[]]3333333333333333333Aj[\"{AANj{{KF`{AOI`}}}}\",[]]444444Ai[\"{CMj{{KF`{AHAb}}}}\",[]]55555555555555555555555555555Aj[\"{B@H`{{KF`{BEFj}}}}\",[]]Bc[\"{{{A@`{B@H`}}}{{KF`{BEFj}}}}\",[]]7Bc[\"{{{A@`{B@Nl}}}{{KF`{BEFn}}}}\",[]]Aj[\"{B@Nl{{KF`{BEFn}}}}\",[]]9999999999999999999999Al[\"{CMj{{KF`{KGnBGFb}}}}\",[]]Al[\"{KAn{{KF`{KGnBGFb}}}}\",[]]Al[\"{IBf{{KF`{KGnBGFb}}}}\",[]]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Cf[\"{{{A@`{B@H`}}}{{KF`{BEFlc}}}{}}\",[\"TryFrom::Error\"]]?Bm[\"{B@H`{{KF`{BEFlc}}}{}}\",[\"TryFrom::Error\"]]Ao[\"{c{{KF`{e}}}{}{}}\",[\"U\",\"T\"]]0Bm[\"{B@Nl{{KF`{BEG`c}}}{}}\",[\"TryFrom::Error\"]]Cf[\"{{{A@`{B@Nl}}}{{KF`{BEG`c}}}{}}\",[\"TryFrom::Error\"]]22222222222222222222222Aj[\"{{}{{KF`{c}}}{}}\",[\"U\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cd[\"{{{AB`{{AHLl{CFlc}}}}{AHLl{CFlNHf}}}DA`{}}\",[\"F\"]]000000000Cc[\"{{{AHLl{BBMn}}AIOd{AHLl{ABCb}}}{{KFb{IBh}}}}\",[]]Bk[\"{{{AHLl{BGL`}}B@Jb}{{KF`{IBhAIJn}}}}\",[]]Bk[\"{{{AHLl{BGLb}}B@Jd}{{KF`{IBhAIK`}}}}\",[]]0Bk[\"{{{AHLl{BHMh}}B@Jd}{{KF`{IBhAIK`}}}}\",[]]Ak[\"{{{AHLl{AIIl}}}AAMf}\",[]]Bi[\"{{{AHLl{CFlBA@`}}}{{KF`{KGlIDn}}}}\",[]]Bi[\"{{{AHLl{CFlBA@`}}}{{KF`{DAdIDn}}}}\",[]]Bi[\"{{{AHLl{CFlBA@`}}}{{KF`{A@hIDn}}}}\",[]]Bj[\"{{{AHLl{CFlBA@`}}}{{KF`{ABAjIDn}}}}\",[]]Bf[\"{{{AHLl{CFlBA@`}}}{{KFf{KGn}}}}\",[]]Bf[\"{{{AHLl{CFlBA@`}}}{{KFf{DAf}}}}\",[]]Bf[\"{{{AHLl{CFlBA@`}}}{{KFf{A@j}}}}\",[]]Bg[\"{{{AHLl{CFlBA@`}}}{{KFf{ABAl}}}}\",[]]Aa[\"{AOFnBGEn}\",[]]Bd[\"{{{AHLl{AIJ`}}}{{A@b{AIJ`}}}}\",[]]Bc[\"{{{AHLl{CNb}}}{{A@b{AIJ`}}}}\",[]]Bc[\"{{{AHLl{ICf}}}{{A@b{AIJ`}}}}\",[]]Bc[\"{{{AHLl{NJd}}}{{A@b{AIJ`}}}}\",[]]Bd[\"{{{AHLl{AIJb}}}{{A@b{AIJb}}}}\",[]]Bc[\"{{{AHLl{CNd}}}{{A@b{AIJb}}}}\",[]]Bc[\"{{{AHLl{ICh}}}{{A@b{AIJb}}}}\",[]]Bc[\"{{{AHLl{NJf}}}{{A@b{AIJb}}}}\",[]]A`[\"{ABCdCMj}\",[]]An[\"{{{AHLl{CFlAIIn}}}AH@`}\",[]]A`[\"{ABCfCMj}\",[]]C`[\"{{{AHLl{CFlB@Jb}}}{{AHLl{CFl{JOf{Of}}}}}}\",[]]C`[\"{{{AHLl{CFlB@Jd}}}{{AHLl{CFl{JOf{Of}}}}}}\",[]]Ah[\"{Of{{KEh{B@Jl}}}}\",[]]Ah[\"{Of{{KEh{B@Jn}}}}\",[]]Ah[\"{Of{{KEh{B@K`}}}}\",[]]A`[\"{ABAjDAd}\",[]]A`[\"{ABAlDAf}\",[]]Bi[\"{AIIh{{KF`{{KAl{AIIlBCM`}}AIIh}}}}\",[]]Aa[\"{{}c{}}\",[\"I\"]]o[\"{DAdDBh}\",[]]o[\"{DAfDBh}\",[]]Aj[\"{{{AHLl{AIIn}}}DBh}\",[]]21Aj[\"{{{AHLl{AAOf}}}DBh}\",[]]Aj[\"{{{AHLl{AAOh}}}DBh}\",[]]Bn[\"{{{AHLl{CFlB@O`}}DBh}{{AHLl{CFlB@O`}}}}\",[]]Bn[\"{{{AHLl{CFlB@Od}}DBh}{{AHLl{CFlB@Od}}}}\",[]]Bm[\"{{{AHLl{NKl}}NIh}{{AB`{{A@b{AIGn}}}}}}\",[]]Bn[\"{{{AHLl{B@On}}NIh}{{AB`{{A@b{AIGn}}}}}}\",[]]Bn[\"{{{AHLl{AOJl}}NIh}{{AB`{{A@b{AIGn}}}}}}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]Ak[\"{{{AHLl{AIIl}}}AB@l}\",[]]Bc[\"{{{AHLl{AIIl}}AIN`AILd}AB@n}\",[]]Ce[\"{{{AHLl{CFlAIIn}}AHA`AILfNIh}{{KF`{KElAHBn}}}}\",[]]43Bm[\"{{{AHLl{CFlAIIn}}KEl}{{KF`{IBhAIId}}}}\",[]]E`[\"{{{AB`{{AHLl{CFlBGEh}}}}{AHLl{CFlNHf}}}{{DA`{{KEh{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{AB`{{AHLl{CFlBEBj}}}}{AHLl{CFlNHf}}}{{DA`{{KEh{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{AB`{{AHLl{CFlAILb}}}}{AHLl{CFlNHf}}}{{DA`{{KEh{c}}}}}{}}\",[\"Stream::Item\"]]E`[\"{{{AB`{{AHLl{CFlBFMf}}}}{AHLl{CFlNHf}}}{{DA`{{KEh{c}}}}}{}}\",[\"Stream::Item\"]]Ec[\"{{{AHLl{CFlAILn}}{AHLl{CFlNHf}}{AHLl{CFl{JOf{Of}}}}}{{DA`{{KF`{KAnAHCn}}}}}}\",[]]Ef[\"{{{AB`{{AHLl{CFlAILn}}}}{AHLl{CFlNHf}}{AHLl{CFl{JOf{Of}}}}}{{DA`{{KFb{KAn}}}}}}\",[]]E`[\"{{{AB`{{AHLl{CFlAILn}}}}{AHLl{CFlNHf}}{AHLl{CFlNKf}}}{{DA`{{KFb{IBh}}}}}}\",[]]Fg[\"{{{AHLl{CFlBBHj}}{AHLl{CFlNHf}}{AHLl{CFl{JOf{AIKb}}}}{AHLl{CFl{JOf{ABAf}}}}}{{DA`{{KFb{KAn}}}}}}\",[]]Dn[\"{{{AB`{{AHLl{CFlAHDf}}}}{AHLl{ABCb}}{AHLl{CFlNHf}}}{{DA`{{KFb{IBh}}}}}}\",[]]>=Ce[\"{{{AHLl{CFlBBJb}}{A@`{NIb}}}{{AHLl{CFlBBJb}}}}\",[]]Ce[\"{{{AHLl{CFlBBJd}}{A@`{NId}}}{{AHLl{CFlBBJd}}}}\",[]]Cj[\"{{{AHLl{B@Ob}}CMhB@Jb{AHLl{{JOf{Of}}}}}{{IHf{Of}}}}\",[]]Cj[\"{{{AHLl{BEFn}}CMhB@Jb{AHLl{{JOf{Of}}}}}{{IHf{Of}}}}\",[]]Cj[\"{{{AHLl{B@Of}}CMhB@Jd{AHLl{{JOf{Of}}}}}{{IHf{Of}}}}\",[]]Cj[\"{{{AHLl{BEG`}}CMhB@Jd{AHLl{{JOf{Of}}}}}{{IHf{Of}}}}\",[]]Cj[\"{{{AHLl{BHGn}}CMhB@J`{AHLl{{JOf{Of}}}}}{{IHf{Of}}}}\",[]]Bb[\"{KAn{{KAl{KAn{KEh{KAn}}}}}}\",[]]000Aj[\"{{{AHLl{AIIl}}}KAn}\",[]]A`[\"{AHLlKGh}\",[]]000000000000000000000000000000000000000000000000000000000000000000Cf[\"{{{AHLl{CFlB@O`}}{A@`{BEOf}}}{{AHLl{CFlB@O`}}}}\",[]]Cf[\"{{{AHLl{CFlB@Od}}{A@`{BEOh}}}{{AHLl{CFlB@Od}}}}\",[]]A`[\"{BHAjIFd}\",[]]A`[\"{B@O`A@`}\",[]]A`[\"{B@OdA@`}\",[]]Aj[\"{{{AHLl{AANn}}}IBh}\",[]]Ck[\"{{{AHLl{CFlAIMj}}{AHLl{{JOf{Of}}}}}{{KF`{IBhAIOf}}}}\",[]]n[\"{{}KAn}\",[]]Ak[\"{{{AHLl{AIIl}}}AGNf}\",[]]Ba[\"{{{AHLl{CFlABCd}}ABCd}IBh}\",[]]Ba[\"{{{AHLl{CFlAIJh}}AIJh}IBh}\",[]]Ba[\"{{{AHLl{CFlBCJ`}}AHCh}IBh}\",[]]Ba[\"{{{AHLl{CFlABCf}}ABCf}IBh}\",[]]Ba[\"{{{AHLl{CFlAIJj}}AIJj}IBh}\",[]]Ba[\"{{{AHLl{CFlBCJb}}AHCj}IBh}\",[]]Bn[\"{{{AHLl{CFlBCLf}}DBh}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}DBh}{{AHLl{CFlBCLh}}}}\",[]]Bi[\"{{{AHLl{CFl}}}{{AHLl{CFlc}}}{}}\",[\"T\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Bg[\"{{{AHLl{AILn}}}{{KF`{CMjB@I`}}}}\",[]]Bg[\"{{{AHLl{AIM`}}}{{KF`{CMjB@Ih}}}}\",[]]Bc[\"{{AHLl{AHLl{CFlc}}}IBh{}}\",[\"T\"]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Cc[\"{{{AHLl{CFlAIIn}}NIhKElKGn}{{KF`{IBhBBI`}}}}\",[]]A`[\"{AGOjDBh}\",[]]B`[\"{{AHLl{AHLl{c}}}DBh{}}\",[\"K\"]]00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Aa[\"{BCIbBFHd}\",[]]A`[\"{BEBnKGl}\",[]]Aa[\"{BCIfBFHf}\",[]]A`[\"{BECbKGn}\",[]]A`[\"{BENfKGn}\",[]]A`[\"{BCKhKGn}\",[]]o[\"{NLbKGn}\",[]]Aa[\"{BCIbAIKd}\",[]]Aa[\"{BCIfAIKf}\",[]]o[\"{KGlCMj}\",[]]o[\"{KGnCMj}\",[]]Aj[\"{{{AHLl{AIIn}}}DBh}\",[]]Aj[\"{{{AHLl{BAKn}}}DBh}\",[]]Bd[\"{{{AHLl{BBHj}}}{{KFb{AIN`}}}}\",[]]Bd[\"{{{AHLl{AANn}}}{{KFb{AIN`}}}}\",[]]A`[\"{AHChCMj}\",[]]A`[\"{BCJ`CMj}\",[]]A`[\"{AHCjCMj}\",[]]A`[\"{BCJbCMj}\",[]]Cc[\"{{{AHLl{BCJj}}{AHLl{AIMh}}CMj}{{A@`{AOKh}}}}\",[]]Bn[\"{{{AHLl{CFlBCLf}}DBh}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}DBh}{{AHLl{CFlBCLh}}}}\",[]]Bg[\"{{{AHLl{AIIl}}KEj}{{KEh{AHCh}}}}\",[]]o[\"{KD`ABf}\",[]]A`[\"{AH@dA@b}\",[]]Bj[\"{{{AHLl{CFlAIIn}}KEl}{{KEh{AHCj}}}}\",[]]A`[\"{AH@fA@b}\",[]]Db[\"{{{AB`{{AHLl{CFlAIMj}}}}{AHLl{CFlNHf}}}{{DA`{{KFb{IBh}}}}}}\",[]]00Ec[\"{{{AB`{{AHLl{CFlAIMj}}}}{AHLl{CFlNHf}}{AHLl{{JOf{Of}}}}}{{DA`{{KFb{KAn}}}}}}\",[]]Eg[\"{{{AB`{{AHLl{CFlAIMj}}}}{AHLl{CFlNHf}}{AHLl{{JOf{Of}}}}}{{DA`{{KF`{KAnAIOf}}}}}}\",[]]1Bm[\"{{{AHLl{IGj}}{AHLl{D@f}}}{{KFb{IBh}}}}\",[]]Cd[\"{{{AHLl{CFlAILn}}KAn}{{KF`{{KEh{IBl}}AHCn}}}}\",[]]Cd[\"{{{AHLl{CFlBFNf}}KAn}{{KF`{{KEh{IBn}}AHCn}}}}\",[]]Cn[\"{{{AHLl{CFlAILn}}{AHLl{CFl{JOf{Of}}}}}{{KF`{IBhBBM`}}}}\",[]]Bk[\"{{{AHLl{D@h}}AILd}{{KF`{AILdBFGj}}}}\",[]]Bn[\"{{{AHLl{CFlAILh}}NIh}{{AHLl{CFlAILh}}}}\",[]]Bn[\"{{{AHLl{CFlAILj}}NIh}{{AHLl{CFlAILj}}}}\",[]]A`[\"{BBMfAFn}\",[]]o[\"{IGhCMj}\",[]]o[\"{{}AIN`}\",[]]0o[\"{{}AHLl}\",[]]Bk[\"{{AAOf{A@`{B@O`}}}{{KF`{AIIhBCIh}}}}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]Bh[\"{{{AHLl{AGOj}}AIN`}{{KFd{AHDh}}}}\",[]]A`[\"{AHChCMf}\",[]]Aj[\"{{{AHLl{AIIn}}}CMf}\",[]]A`[\"{AHCjCMf}\",[]]Ce[\"{{{AHLl{CFlAILh}}{KEh{KGh}}}{{AHLl{CFlAILh}}}}\",[]]Ce[\"{{{AHLl{CFlAILj}}{KEh{KGh}}}{{AHLl{CFlAILj}}}}\",[]]Aa[\"{ABBnAIN`}\",[]]Aa[\"{ABC`AIN`}\",[]]Aa[\"{ABCbAIN`}\",[]]Aa[\"{AIN`AHA`}\",[]]o[\"{AAhCMf}\",[]]Bn[\"{{{AHLl{CFlBCLf}}CMf}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}CMf}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{AGOjCMf}\",[]]Bo[\"{{{AHLl{CFlBCLf}}AANj}{{AHLl{CFlBCLf}}}}\",[]]Bo[\"{{{AHLl{CFlBCLh}}AANj}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{AGOjKEh}\",[]]k[\"{{}}\",[]]0000000000Bh[\"{AAOfc{}}\",[\"IntoFuture::IntoFuture\"]]11A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]o[\"{AAhKAn}\",[]]Ao[\"{{AHLlc}{{NIn{c}}}{}}\",[\"S\"]]0C`[\"{{{AHLl{CFlAIJ`}}NIhCMjDBh{KEh{CMj}}}IBh}\",[]]Bo[\"{{{AHLl{CFlCNb}}NIhCMjDBh{KEh{CMj}}}IBh}\",[]]C`[\"{{{AHLl{CFlAIJb}}NIhCMjDBh{KEh{CMj}}}IBh}\",[]]Bo[\"{{{AHLl{CFlCNd}}NIhCMjDBh{KEh{CMj}}}IBh}\",[]]Bk[\"{{{AHLl{CFlIGj}}NIh{AHLl{BCJj}}}IBh}\",[]]A`[\"{BEObKEh}\",[]]A`[\"{BEOdKEh}\",[]]Bn[\"{{{AHLl{KGl}}{AHLl{KGl}}}{{KEh{ABA`}}}}\",[]]Bn[\"{{{AHLl{KEj}}{AHLl{KEj}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{AOI`}}{AHLl{AOI`}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{B@Jb}}{AHLl{B@Jb}}}{{KEh{ABA`}}}}\",[]]Bn[\"{{{AHLl{DAd}}{AHLl{DAd}}}{{KEh{ABA`}}}}\",[]]Bn[\"{{{AHLl{A@h}}{AHLl{A@h}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{ABAj}}{AHLl{ABAj}}}{{KEh{ABA`}}}}\",[]]Bn[\"{{{AHLl{KGn}}{AHLl{KGn}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{BF@n}}{AHLl{BF@n}}}{{KEh{ABA`}}}}\",[]]Bn[\"{{{AHLl{KEl}}{AHLl{KEl}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{AHD`}}{AHLl{AHD`}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{BAOf}}{AHLl{BAOf}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{AIOh}}{AHLl{AIOh}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{AOIb}}{AHLl{AOIb}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{BECj}}{AHLl{BECj}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{BF@d}}{AHLl{BF@d}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{B@Jd}}{AHLl{B@Jd}}}{{KEh{ABA`}}}}\",[]]Bn[\"{{{AHLl{DAf}}{AHLl{DAf}}}{{KEh{ABA`}}}}\",[]]Bn[\"{{{AHLl{A@j}}{AHLl{A@j}}}{{KEh{ABA`}}}}\",[]]C`[\"{{{AHLl{ABAl}}{AHLl{ABAl}}}{{KEh{ABA`}}}}\",[]]Ak[\"{{{AHLl{AIIl}}}AILb}\",[]]Bk[\"{{{AHLl{AIIn}}KEl}{{KF`{AILfAIId}}}}\",[]]Cc[\"{{{AHLl{CFlAILn}}KAn}{{KF`{{ABf{Of}}BBMb}}}}\",[]]Bb[\"{{{AHLl{CFlAIIn}}ABAl}AIM`}\",[]]Aj[\"{{{AHLl{AHAh}}}KAn}\",[]]Aj[\"{{{AHLl{AHAj}}}KAn}\",[]]Bb[\"{{{AHLl{CFlAIIn}}ABAl}AIMl}\",[]]Bn[\"{{{AHLl{CFlBCLf}}CMj}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}CMj}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{AGOjKEh}\",[]]A`[\"{BALdKEh}\",[]]A`[\"{BALfKEh}\",[]]Cf[\"{{{AHLl{CFlB@O`}}{A@`{AINh}}}{{AHLl{CFlB@O`}}}}\",[]]Cf[\"{{{AHLl{CFlB@Od}}{A@`{AINj}}}{{AHLl{CFlB@Od}}}}\",[]]Cf[\"{{{AHLl{CFlB@Hd}}{A@`{AIO`}}}{{AHLl{CFlB@Hd}}}}\",[]]Cf[\"{{{AHLl{CFlB@Hh}}{A@`{AIOb}}}{{AHLl{CFlB@Hh}}}}\",[]]o[\"{AAhCMj}\",[]]Bn[\"{{{AHLl{CFlBFEl}}CMf}{{AHLl{CFlBFEl}}}}\",[]]Bn[\"{{{AHLl{CFlBFEn}}CMf}{{AHLl{CFlBFEn}}}}\",[]]Ak[\"{{{AHLl{AIIl}}}BGFf}\",[]]Aj[\"{{{AHLl{D@h}}}BBN`}\",[]]Aj[\"{{{A@`{B@Ob}}}B@O`}\",[]]Aj[\"{{{A@`{B@Of}}}B@Od}\",[]]Aj[\"{{BCKdc}BCKdABBd}\",[\"\"]]Aj[\"{{BCKfc}BCKfABBd}\",[\"\"]]Bm[\"{{{AHLl{CFlAIMj}}IBl}{{KF`{IBhAIOf}}}}\",[]]n[\"{{}KDn}\",[]]o[\"{NHdNHd}\",[]]0000Bd[\"{{{AHLl{BGL`}}}{{KEh{AANj}}}}\",[]]Bd[\"{{{AHLl{BGLb}}}{{KEh{AANj}}}}\",[]]Bd[\"{{{AHLl{BHMh}}}{{KEh{AANj}}}}\",[]]Bd[\"{{{AHLl{BHNb}}}{{KEh{AANj}}}}\",[]]Bd[\"{{{AHLl{AIIl}}}{{KEh{BCIh}}}}\",[]]Ck[\"{{{AHLl{AANn}}B@HdAIN`{AHLl{CMd}}}{{KF`{AIIhB@Il}}}}\",[]]Ak[\"{{{AHLl{B@Nh}}}AANj}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]An[\"{{AILlDBh}{{KFd{KAn}}}}\",[]]Cl[\"{{{AHLl{NKn}}}{{KEh{{KAl{{A@b{AHAh}}{A@b{AHBf}}}}}}}}\",[]]Cm[\"{{{AHLl{AINl}}}{{KEh{{KAl{{A@b{AHAh}}{A@b{AHBf}}}}}}}}\",[]]Cl[\"{{{AHLl{NL`}}}{{KEh{{KAl{{A@b{AHAj}}{A@b{AHBh}}}}}}}}\",[]]Cm[\"{{{AHLl{AINn}}}{{KEh{{KAl{{A@b{AHAj}}{A@b{AHBh}}}}}}}}\",[]]A`[\"{BGDdABf}\",[]]A`[\"{BIAjABf}\",[]]A`[\"{BGDfABf}\",[]]A`[\"{BIAlABf}\",[]]An[\"{{{AHLl{CFlBGL`}}}B@Jb}\",[]]An[\"{{{AHLl{CFlBGLb}}}B@Jd}\",[]]An[\"{{{AHLl{CFlBHMh}}}B@Jd}\",[]]An[\"{{{AHLl{CFlBHNb}}}B@Jd}\",[]]Ak[\"{{{AHLl{BBMn}}}B@Lf}\",[]]Ba[\"{{{AHLl{CFlAIIn}}BCIn}IBh}\",[]]Bo[\"{{{AHLl{CFlAAO`}}BECjBAKn}{{KEh{BCIn}}}}\",[]]B`[\"{{{AHLl{NKn}}B@JbDAd}COl}\",[]]Bn[\"{{{AHLl{B@Ob}}CMhB@Jb}{{KF`{COlBFHl}}}}\",[]]Ba[\"{{{AHLl{AINl}}B@JbDAd}COl}\",[]]Bn[\"{{{AHLl{BEFn}}CMhB@Jb}{{KF`{COlBFHl}}}}\",[]]B`[\"{{{AHLl{NL`}}B@JdDAf}COn}\",[]]Bn[\"{{{AHLl{B@Of}}CMhB@Jd}{{KF`{COnBFIb}}}}\",[]]Ba[\"{{{AHLl{AINn}}B@JdDAf}COn}\",[]]Bn[\"{{{AHLl{BEG`}}CMhB@Jd}{{KF`{COnBFIb}}}}\",[]]Bn[\"{{{AHLl{BHGn}}CMhB@J`}{{KF`{COnBFIb}}}}\",[]]Aj[\"{{}{{NKh{c}}}{}}\",[\"T\"]]0A`[\"{AHChCMj}\",[]]A`[\"{BCJ`CMj}\",[]]A`[\"{AHCjCMj}\",[]]A`[\"{BCJbCMj}\",[]]Bn[\"{{{AHLl{CFlB@O`}}KAn}{{AHLl{CFlB@O`}}}}\",[]]Bn[\"{{{AHLl{CFlB@Od}}KAn}{{AHLl{CFlB@Od}}}}\",[]]Aj[\"{{{AHLl{BBHj}}}DBh}\",[]]0Aj[\"{{{AHLl{BBMn}}}DBh}\",[]]Bk[\"{{{AHLl{AIIn}}KEl}{{KF`{AHA`AIId}}}}\",[]]Cc[\"{{{AHLl{BCJj}}{AHLl{AILl}}CMj}{{A@`{AOKh}}}}\",[]]Ak[\"{{{AHLl{AAOf}}}B@Jb}\",[]]Ak[\"{{{AHLl{AAOh}}}B@Jd}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]Bf[\"{{{AHLl{CFlAIIn}}}{{KEh{NIh}}}}\",[]]Cf[\"{{{AHLl{CFlBCLf}}{A@`{AOIn}}}{{AHLl{CFlBCLf}}}}\",[]]Cf[\"{{{AHLl{CFlBCLh}}{A@`{AOJ`}}}{{AHLl{CFlBCLh}}}}\",[]]43A`[\"{AAhAOJf}\",[]]A`[\"{ABBnKEh}\",[]]A`[\"{ABC`KEh}\",[]]A`[\"{ABCbKEh}\",[]]A`[\"{BEObKEh}\",[]]A`[\"{BEOdKEh}\",[]]Ai[\"{{{AHLl{NLn}}}KAn}\",[]]Bo[\"{{{AHLl{CFlBHMh}}AANj}{{AHLl{CFlBHMh}}}}\",[]]Bo[\"{{{AHLl{CFlBHNb}}AANj}{{AHLl{CFlBHNb}}}}\",[]]Bj[\"{{{AHLl{AIMj}}CEf}{{KF`{IBhB@I`}}}}\",[]]Bm[\"{{{AHLl{CFlAIMl}}CEf}{{KF`{IBhB@Ih}}}}\",[]]?>A`[\"{IGhAOKf}\",[]]A`[\"{AOG`IGh}\",[]]Bj[\"{{{A@`{B@H`}}IGl}{{KF`{BEFjBGEd}}}}\",[]]Bj[\"{{{A@`{B@Nl}}IGl}{{KF`{BEFnBGEd}}}}\",[]]Bj[\"{{{A@`{B@H`}}IGl}{{KF`{BEFlBGEf}}}}\",[]]Bj[\"{{{A@`{B@Nl}}IGl}{{KF`{BEG`BGEf}}}}\",[]]Dj[\"{{{AHLl{CFlAIMl}}{AHLl{CFl{AHLl{CFl{JOf{IBl}}}}}}}{{KF`{KAnAIOh}}}}\",[]]Aj[\"{{{AHLl{AIIn}}}DBh}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]A`[\"{AGOjDBh}\",[]]Cd[\"{{{AHLl{CFlBBJb}}{A@`{A`}}}{{AHLl{CFlBBJb}}}}\",[]]Cd[\"{{{AHLl{CFlBBJd}}{A@`{A`}}}{{AHLl{CFlBBJd}}}}\",[]]Bk[\"{{{AHLl{BBHj}}}{{AB`{{A@b{AHDf}}}}}}\",[]]o[\"{AAhCMj}\",[]]Aa[\"{AOKfAHAl}\",[]]76Aa[\"{AIMdAAOf}\",[]]Aa[\"{AIMfAAOh}\",[]]Cf[\"{{{AHLl{CFlBFCn}}{KEh{AANj}}}{{AHLl{CFlBFCn}}}}\",[]]Cf[\"{{{AHLl{CFlBFD`}}{KEh{AANj}}}{{AHLl{CFlBFD`}}}}\",[]]9;:An[\"{{{AHLl{AOKh}}AANj}IBh}\",[]]B`[\"{{{AHLl{CFlAIJ`}}CMf}IBh}\",[]]Ao[\"{{{AHLl{CFlCNb}}CMf}IBh}\",[]]Ao[\"{{{AHLl{CFlICf}}CMf}IBh}\",[]]Ao[\"{{{AHLl{CFlNJd}}CMf}IBh}\",[]]B`[\"{{{AHLl{CFlAIJb}}CMf}IBh}\",[]]Ao[\"{{{AHLl{CFlCNd}}CMf}IBh}\",[]]Ao[\"{{{AHLl{CFlICh}}CMf}IBh}\",[]]Ao[\"{{{AHLl{CFlNJf}}CMf}IBh}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]10Bi[\"{{{AHLl{AIIl}}}{{KEh{{A@b{On}}}}}}\",[]]Bh[\"{{{AHLl{NKn}}}{{KEh{{A@b{On}}}}}}\",[]]Bi[\"{{{AHLl{AINl}}}{{KEh{{A@b{On}}}}}}\",[]]Bh[\"{{{AHLl{NL`}}}{{KEh{{A@b{On}}}}}}\",[]]Bi[\"{{{AHLl{AINn}}}{{KEh{{A@b{On}}}}}}\",[]]Db[\"{{{AB`{{AHLl{CFlAIMj}}}}{AHLl{CFlNHf}}}{{DA`{{KFb{IBh}}}}}}\",[]]Db[\"{{{AHLl{CFlAIIn}}NIhB@Lf{AHLl{CFl{ABf{Of}}}}}{{KEh{ABC`}}}}\",[]]Cf[\"{{{AHLl{CFlBCLf}}{AHLl{CMd}}}{{AHLl{CFlBCLf}}}}\",[]]Cf[\"{{{AHLl{CFlBCLh}}{AHLl{CMd}}}{{AHLl{CFlBCLh}}}}\",[]]Ak[\"{{{AHLl{AIIl}}}B@Ml}\",[]]Bj[\"{{{AHLl{AIIl}}IBl}{{KF`{IBhBF@l}}}}\",[]]Bn[\"{{{AHLl{CFlBCLf}}DBh}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}DBh}{{AHLl{CFlBCLh}}}}\",[]]Ai[\"{{{AHLl{NIb}}}KAn}\",[]]Ai[\"{{{AHLl{NId}}}KAn}\",[]]Dh[\"{{{AHLl{B@Hf}}CMh{AHLl{CMd}}{AHLl{BFNd}}}{{KF`{{A@b{NKn}}B@Il}}}}\",[]]Cb[\"{{{AHLl{B@Ob}}CMh{AHLl{BFNd}}}{{A@b{NKn}}}}\",[]]Dh[\"{{{AHLl{BEFj}}CMh{AHLl{CMd}}{AHLl{BFNd}}}{{KF`{{A@b{NKn}}B@Il}}}}\",[]]Cb[\"{{{AHLl{BEFn}}CMh{AHLl{BFNd}}}{{A@b{NKn}}}}\",[]]Dh[\"{{{AHLl{B@Hj}}CMh{AHLl{CMd}}{AHLl{BFNd}}}{{KF`{{A@b{NL`}}B@In}}}}\",[]]Cb[\"{{{AHLl{B@Of}}CMh{AHLl{BFNd}}}{{A@b{NL`}}}}\",[]]Dh[\"{{{AHLl{BEFl}}CMh{AHLl{CMd}}{AHLl{BFNd}}}{{KF`{{A@b{NL`}}B@In}}}}\",[]]Cb[\"{{{AHLl{BEG`}}CMh{AHLl{BFNd}}}{{A@b{NL`}}}}\",[]]Dh[\"{{{AHLl{BHGl}}CMh{AHLl{CMd}}{AHLl{BFNd}}}{{KF`{{A@b{NL`}}B@Ij}}}}\",[]]Cb[\"{{{AHLl{BHGn}}CMh{AHLl{BFNd}}}{{A@b{NL`}}}}\",[]]Dd[\"{{{AB`{{AHLl{CFlc}}}}{AHLl{CFlNHf}}}{{DA`{{KEh{KF`}}}}}{}}\",[\"S\"]]0Bn[\"{{AIN`BBHlAAh}{{KFd{{KAl{AANlAIIj}}}}}}\",[]]Bd[\"{{{AHLl{AIIn}}}{{AHLl{NL`}}}}\",[]]A`[\"{AOG`IGh}\",[]]Cb[\"{{{AHLl{AOIn}}DAdAIN`B@JbNIh}{{KEh{AILh}}}}\",[]]Cb[\"{{{AHLl{BCKd}}DAdAIN`B@JbNIh}{{KEh{AILh}}}}\",[]]Cb[\"{{{AHLl{AOJ`}}DAfAIN`B@JdNIh}{{KEh{AILj}}}}\",[]]Cb[\"{{{AHLl{BCKf}}DAfAIN`B@JdNIh}{{KEh{AILj}}}}\",[]]B`[\"{{{AHLl{CFlAIIn}}NIh}IBh}\",[]]C`[\"{{{AHLl{CFlAIIh}}}{{KF`{{A@b{On}}BCIh}}}}\",[]]Bi[\"{{{AHLl{AIIl}}}{{KEh{{A@b{On}}}}}}\",[]]Bh[\"{{{AHLl{NKn}}}{{KEh{{A@b{On}}}}}}\",[]]Bi[\"{{{AHLl{AINl}}}{{KEh{{A@b{On}}}}}}\",[]]Bh[\"{{{AHLl{NL`}}}{{KEh{{A@b{On}}}}}}\",[]]Bi[\"{{{AHLl{AINn}}}{{KEh{{A@b{On}}}}}}\",[]]o[\"{AIJhOf}\",[]]o[\"{AIJjOf}\",[]]Ai[\"{{{AHLl{CMd}}}DBh}\",[]]Aj[\"{{{AHLl{AIJ`}}}CMj}\",[]]Ai[\"{{{AHLl{CNb}}}CMj}\",[]]Bn[\"{{{AHLl{CFlAIHf}}CMj}{{AHLl{CFlAIHf}}}}\",[]]Ai[\"{{{AHLl{ICf}}}CMj}\",[]]Bn[\"{{{AHLl{CFlAOGn}}CMj}{{AHLl{CFlAOGn}}}}\",[]]Ai[\"{{{AHLl{NJd}}}CMj}\",[]]Bn[\"{{{AHLl{CFlBANf}}CMj}{{AHLl{CFlBANf}}}}\",[]]Aj[\"{{{AHLl{AIJb}}}CMj}\",[]]Ai[\"{{{AHLl{CNd}}}CMj}\",[]]Bn[\"{{{AHLl{CFlAIHh}}CMj}{{AHLl{CFlAIHh}}}}\",[]]Ai[\"{{{AHLl{ICh}}}CMj}\",[]]Bn[\"{{{AHLl{CFlAOH`}}CMj}{{AHLl{CFlAOH`}}}}\",[]]Ai[\"{{{AHLl{NJf}}}CMj}\",[]]Bn[\"{{{AHLl{CFlBANh}}CMj}{{AHLl{CFlBANh}}}}\",[]]Aa[\"{AILnBFNf}\",[]]Ai[\"{{{AHLl{NKn}}}DBh}\",[]]Aj[\"{{{AHLl{AINl}}}DBh}\",[]]Aj[\"{{{AHLl{AIIn}}}DBh}\",[]]Ai[\"{{{AHLl{NL`}}}DBh}\",[]]Aj[\"{{{AHLl{AINn}}}DBh}\",[]]Co[\"{{{AHLl{NKn}}B@Jb{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}DBh}\",[]]D`[\"{{{AHLl{AINl}}B@Jb{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}DBh}\",[]]Co[\"{{{AHLl{NL`}}B@Jd{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}DBh}\",[]]D`[\"{{{AHLl{AINn}}B@Jd{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}DBh}\",[]]Bn[\"{{{AHLl{CFlBFEl}}CMf}{{AHLl{CFlBFEl}}}}\",[]]Bn[\"{{{AHLl{CFlBFEn}}CMf}{{AHLl{CFlBFEn}}}}\",[]]Cd[\"{{{AHLl{CFlNKn}}}{{KEh{{NIj{{A@b{AHBf}}}}}}}}\",[]]Ce[\"{{{AHLl{CFlAINl}}}{{KEh{{NIj{{A@b{AHBf}}}}}}}}\",[]]Cd[\"{{{AHLl{CFlNL`}}}{{KEh{{NIl{{A@b{AHBh}}}}}}}}\",[]]Ce[\"{{{AHLl{CFlAINn}}}{{KEh{{NIl{{A@b{AHBh}}}}}}}}\",[]]Bf[\"{{{AHLl{CFlAIJ`}}CMfCMjNIh}IBh}\",[]]Be[\"{{{AHLl{CFlCNb}}CMfCMjNIh}IBh}\",[]]Bf[\"{{{AHLl{CFlAIJb}}CMfCMjNIh}IBh}\",[]]Be[\"{{{AHLl{CFlCNd}}CMfCMjNIh}IBh}\",[]]Bf[\"{{{AHLl{CFlAIJ`}}NIhCMfCMj}IBh}\",[]]Be[\"{{{AHLl{CFlCNb}}NIhCMfCMj}IBh}\",[]]Bf[\"{{{AHLl{CFlAIJb}}NIhCMfCMj}IBh}\",[]]Be[\"{{{AHLl{CFlCNd}}NIhCMfCMj}IBh}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]De[\"{{{AHLl{CFlBCLf}}c{AHLl{CMd}}}{{AHLl{CFlBCLf}}}{{IBj{D@f}}}}\",[\"\"]]De[\"{{{AHLl{CFlBCLh}}c{AHLl{CMd}}}{{AHLl{CFlBCLh}}}{{IBj{D@f}}}}\",[\"\"]]Ci[\"{{{AHLl{CFlNKn}}{AHLl{{JOf{Of}}}}}{{KF`{DBhIDj}}}}\",[]]Cj[\"{{{AHLl{CFlAINl}}{AHLl{{JOf{Of}}}}}{{KF`{DBhIDj}}}}\",[]]Cj[\"{{{AHLl{CFlNL`}}{AHLl{{JOf{Of}}}}}{{KF`{DBhBBMl}}}}\",[]]Ck[\"{{{AHLl{CFlAINn}}{AHLl{{JOf{Of}}}}}{{KF`{DBhBBMl}}}}\",[]]o[\"{AAhDBh}\",[]]Bn[\"{{{AHLl{CFlBCLf}}KGl}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}KGn}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{AGOjKEh}\",[]]Ca[\"{{{AHLl{CFlAILn}}}{{KF`{{KEh{KGl}}AIMb}}}}\",[]]Ca[\"{{{AHLl{CFlBFNf}}}{{KF`{{KEh{KGl}}AIMb}}}}\",[]]Ca[\"{{{AHLl{CFlAIM`}}}{{KF`{{KEh{KGn}}B@Ih}}}}\",[]]Ak[\"{{{AHLl{AIIh}}}AIN`}\",[]]Ak[\"{{{AHLl{AAOf}}}AIN`}\",[]]Bg[\"{{{AHLl{D@h}}}{{KF`{AIN`AIHn}}}}\",[]]Ak[\"{{{AHLl{AAOh}}}AIN`}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]B`[\"{{KGlc}KGl{{COj{KGl}}}}\",[\"\"]]B`[\"{{KEjc}KEj{{COj{KEj}}}}\",[\"\"]]B`[\"{{KGnc}KGn{{COj{KGn}}}}\",[\"\"]]B`[\"{{KElc}KEl{{COj{KEl}}}}\",[\"\"]]20Ae[\"{{AANjCMj}AFn}\",[]]Bn[\"{{{AHLl{CFlBCLf}}AFl}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}AFl}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{IGhAANj}\",[]]B`[\"{{}{{AHLl{{JOf{BFMd}}}}}}\",[]]An[\"{{AHLl{OLn{CFlOf}}}IBh}\",[]]0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000An[\"{{}{{KEh{{A@`{NKl}}}}}}\",[]]Bn[\"{{{AHLl{CFlBBJb}}DBh}{{AHLl{CFlBBJb}}}}\",[]]Bn[\"{{{AHLl{CFlBBJd}}DBh}{{AHLl{CFlBBJd}}}}\",[]]Aj[\"{{{AHLl{AHBf}}}CMj}\",[]]Aj[\"{{{AHLl{AHBh}}}CMj}\",[]]A`[\"{ABAfKEh}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]10Bf[\"{{{AHLl{CMd}}}{{KFd{CMjBANj}}}}\",[]]Bn[\"{{{AHLl{AANn}}{A@b{BBHj}}}{{KFb{IBh}}}}\",[]]Ai[\"{AIIj{{KFd{IBh}}}}\",[]]Cj[\"{{{AHLl{NKj}}BBHlBAOd{AHLl{AAh}}}{{KAl{AIN`AANl}}}}\",[]]Cb[\"{{{AHLl{CFlAIIn}}KElAILf}{{KF`{AILfBFGl}}}}\",[]]Am[\"{{{AHLl{AIIl}}CMj}IBh}\",[]]B`[\"{{{AHLl{CFlAIIn}}CMj}IBh}\",[]]A`[\"{BBMdDBh}\",[]]B`[\"{{{AHLl{CFlIGh}}BBMf}IBh}\",[]]Aa[\"{AOKfAHAl}\",[]]Bn[\"{{{AHLl{NKl}}AHDh}{{KFb{{A@b{BBHj}}}}}}\",[]]Bo[\"{{{AHLl{B@On}}AHDh}{{KFb{{A@b{BBHj}}}}}}\",[]]Bo[\"{{{AHLl{AOJl}}AHDh}{{KFb{{A@b{BBHj}}}}}}\",[]]Ci[\"{{{AHLl{CFlNKn}}{AHLl{CFl{ABf{Of}}}}}{{KEh{COl}}}}\",[]]Cj[\"{{{AHLl{CFlAINl}}{AHLl{CFl{ABf{Of}}}}}{{KEh{COl}}}}\",[]]Ci[\"{{{AHLl{CFlNL`}}{AHLl{CFl{ABf{Of}}}}}{{KEh{COn}}}}\",[]]Cj[\"{{{AHLl{CFlAINn}}{AHLl{CFl{ABf{Of}}}}}{{KEh{COn}}}}\",[]]Cb[\"{{{AHLl{ABBf}}IBfAINfAANj}{{KF`{IBhBCL`}}}}\",[]]Cb[\"{{{AHLl{BAJn}}IBfAINfAANj}{{KF`{IBhBCL`}}}}\",[]]Cb[\"{{{AHLl{B@Lh}}IBfAINfAANj}{{KF`{IBhBCL`}}}}\",[]]Cb[\"{{{AHLl{ABBh}}IBfAINfAANj}{{KF`{IBhBCLb}}}}\",[]]Cb[\"{{{AHLl{BAK`}}IBfAINfAANj}{{KF`{IBhBCLb}}}}\",[]]Cb[\"{{{AHLl{B@Lj}}IBfAINfAANj}{{KF`{IBhBCLb}}}}\",[]]Al[\"{{}{{KF`{B@HbBGEf}}}}\",[]]Bn[\"{{{AHLl{AIIl}}KEj}{{KEh{{A@b{AIJ`}}}}}}\",[]]Bo[\"{{{AHLl{AIIn}}KEl}{{KEh{{AHLl{AIJb}}}}}}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]Aj[\"{{{AHLl{AIIl}}}IBh}\",[]]Am[\"{{{AHLl{CFlAIIn}}}IBh}\",[]]Bf[\"{{{AHLl{AIGl}}}{{KF`{AAhIDl}}}}\",[]]Bf[\"{{{AHLl{AIGl}}}{{KF`{A@fIDl}}}}\",[]]Bg[\"{{{AHLl{AIGl}}}{{KF`{AAMlIDl}}}}\",[]]Bg[\"{{{AHLl{AIGl}}}{{KF`{AGOjIDl}}}}\",[]]Bf[\"{{{AHLl{AIGl}}}{{KF`{AAjIDl}}}}\",[]]Bf[\"{{{AHLl{AIGl}}}{{KF`{AAlIDl}}}}\",[]]Ak[\"{{{AHLl{BBMn}}}B@Lf}\",[]]Cf[\"{{{AHLl{CFlBCLf}}{KEh{AOI`}}}{{AHLl{CFlBCLf}}}}\",[]]Cf[\"{{{AHLl{CFlBCLh}}{KEh{AOIb}}}{{AHLl{CFlBCLh}}}}\",[]]<;Aj[\"{{{AHLl{AANn}}}KAn}\",[]]Aj[\"{{{AHLl{AAO`}}}KAn}\",[]]Bc[\"{{{AHLl{AIIl}}AIN`AILd}AB@n}\",[]]Co[\"{{{AHLl{CFlAIIn}}AHA`AILfNIh}{{KF`{{KAl{KElDBh}}AHBn}}}}\",[]]Bn[\"{{{AHLl{CFlBCLf}}CMh}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}CMh}{{AHLl{CFlBCLh}}}}\",[]]Df[\"{{{AHLl{CFlAILn}}{AHLl{CFl{JOf{IBl}}}}}{{KF`{{KEh{KAn}}AHCn}}}}\",[]]Ai[\"{AIIj{{KFd{IBh}}}}\",[]]Bg[\"{{{AHLl{BBMn}}AIOd}{{KFb{KAn}}}}\",[]]A`[\"{AGOjCMj}\",[]]10Aj[\"{{{AHLl{AAh}}}BCLd}\",[]]Cf[\"{{{AHLl{CFlB@O`}}{A@`{BCLf}}}{{AHLl{CFlB@O`}}}}\",[]]Cf[\"{{{AHLl{CFlB@Hd}}{A@`{BCLf}}}{{AHLl{CFlB@Hd}}}}\",[]]Cf[\"{{{AHLl{CFlB@Od}}{A@`{BCLh}}}{{AHLl{CFlB@Od}}}}\",[]]Cf[\"{{{AHLl{CFlB@Hh}}{A@`{BCLh}}}{{AHLl{CFlB@Hh}}}}\",[]]Aa[\"{B@O`BGMb}\",[]]Aa[\"{B@OdBGMd}\",[]]Bj[\"{{{ABf{BBHl}}BAOd}{{KF`{B@O`IE`}}}}\",[]]Bj[\"{{{ABf{BBHl}}BAOd}{{KF`{B@OdIEb}}}}\",[]]Co[\"{{{AHLl{CFlAIMj}}{AHLl{CFl{JOf{IBl}}}}}{{KF`{IBhAIOf}}}}\",[]]A`[\"{AHChCMj}\",[]]A`[\"{AHCjCMj}\",[]]A`[\"{BEObCMj}\",[]]A`[\"{BEOdCMj}\",[]]Bc[\"{{{AHLl{AIIl}}}{{KEh{KAn}}}}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]10Aj[\"{{{AHLl{AH@`}}}KAn}\",[]]Be[\"{{{AHLl{AANn}}{KEh{B@O`}}}IBh}\",[]]Bo[\"{{{AHLl{CFlAAO`}}{KEh{{A@`{B@Od}}}}}IBh}\",[]]Bd[\"{{{AHLl{BFMd}}}{{KEh{BAOb}}}}\",[]]Dj[\"{{{AHLl{CFlAIMj}}{AHLl{CFl{AHLl{CFl{JOf{IBl}}}}}}}{{KF`{KAnAIOf}}}}\",[]]o[\"{{}AHLl}\",[]]n[\"{{}NHd}\",[]]0000Bn[\"{{{AHLl{CFlBCLf}}KAn}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}KAn}{{AHLl{CFlBCLh}}}}\",[]]o[\"{CMjKGl}\",[]]o[\"{CMjKGn}\",[]]A`[\"{BAL`CMj}\",[]]Bf[\"{{{AHLl{BGFf}}{AHLl{BGFf}}}DBh}\",[]]Bo[\"{{{AHLl{CFlBBJb}}AANj}{{AHLl{CFlBBJb}}}}\",[]]Bo[\"{{{AHLl{CFlBBJd}}AANj}{{AHLl{CFlBBJd}}}}\",[]]Ae[\"{{KAnCMj}BAJn}\",[]]Ae[\"{{KAnCMj}BAK`}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]7Bg[\"{{{AHLl{AIIn}}KEl}{{KEh{AILf}}}}\",[]]An[\"{{{AHLl{AIIl}}IBl}B@Nj}\",[]]Bm[\"{{{AHLl{CFlBEFj}}{ABf{{ABf{Of}}}}}IBh}\",[]]Bm[\"{{{AHLl{CFlBEFn}}{ABf{{ABf{Of}}}}}IBh}\",[]]Bm[\"{{{AHLl{CFlBEFl}}{ABf{{ABf{Of}}}}}IBh}\",[]]Bm[\"{{{AHLl{CFlBEG`}}{ABf{{ABf{Of}}}}}IBh}\",[]]Am[\"{{{AHLl{AIIl}}KGl}IBh}\",[]]B`[\"{{{AHLl{CFlAIIn}}KGn}IBh}\",[]]Ce[\"{{{AHLl{CFlBBJb}}{ABf{CMh}}}{{AHLl{CFlBBJb}}}}\",[]]Ce[\"{{{AHLl{CFlBBJd}}{ABf{CMh}}}{{AHLl{CFlBBJd}}}}\",[]]Fg[\"{{{AHLl{BHAl}}{AHLl{BBHl}}{AHLl{{JOf{BBHl}}}}{AHLl{AIMn}}{AHLl{{JOf{Of}}}}ABCh}{{KF`{BFGfIDf}}}}\",[]]Af[\"{{BCKdNIh}BCKd}\",[]]Af[\"{{BCKfNIh}BCKf}\",[]]A`[\"{BAL`CMj}\",[]]Al[\"{BA@`{{KF`{DAdIDn}}}}\",[]]Al[\"{BA@`{{KF`{A@hIDn}}}}\",[]]Ai[\"{BA@`{{KFf{DAf}}}}\",[]]Ai[\"{BA@`{{KFf{A@j}}}}\",[]]o[\"{NHdNHd}\",[]]Bo[\"{{{AHLl{CFlBFEl}}AANj}{{AHLl{CFlBFEl}}}}\",[]]Bo[\"{{{AHLl{CFlBFEn}}AANj}{{AHLl{CFlBFEn}}}}\",[]]Bb[\"{{{AHLl{NKn}}}{{KEh{DBh}}}}\",[]]Bc[\"{{{AHLl{AINl}}}{{KEh{DBh}}}}\",[]]Bb[\"{{{AHLl{NL`}}}{{KEh{DBh}}}}\",[]]Bc[\"{{{AHLl{AINn}}}{{KEh{DBh}}}}\",[]]Bg[\"{{{AHLl{AIIl}}}{{KF`{IBhBCIh}}}}\",[]]Ba[\"{{{AHLl{BEOn}}KEjAHA`}DBh}\",[]]Ba[\"{{{AHLl{BF@`}}KElAHA`}DBh}\",[]]Cf[\"{{{AHLl{CFlBCLf}}{KEh{AANj}}}{{AHLl{CFlBCLf}}}}\",[]]Cf[\"{{{AHLl{CFlBCLh}}{KEh{AANj}}}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{AHChCMj}\",[]]A`[\"{AHCjCMj}\",[]]Bo[\"{{{AHLl{CFlAIJ`}}NIhNIhDBhDBhCMjCMj}IBh}\",[]]Bn[\"{{{AHLl{CFlCNb}}NIhNIhDBhDBhCMjCMj}IBh}\",[]]Bn[\"{{{AHLl{CFlICf}}NIhNIhDBhDBhCMjCMj}IBh}\",[]]Bn[\"{{{AHLl{CFlNJd}}NIhNIhDBhDBhCMjCMj}IBh}\",[]]Bo[\"{{{AHLl{CFlAIJb}}NIhNIhDBhDBhCMjCMj}IBh}\",[]]Bn[\"{{{AHLl{CFlCNd}}NIhNIhDBhDBhCMjCMj}IBh}\",[]]Bn[\"{{{AHLl{CFlICh}}NIhNIhDBhDBhCMjCMj}IBh}\",[]]Bn[\"{{{AHLl{CFlNJf}}NIhNIhDBhDBhCMjCMj}IBh}\",[]]A`[\"{BAL`CMj}\",[]]Al[\"{{{AHLl{NLn}}A@j}CMj}\",[]]Bi[\"{{{AHLl{CFlAIMh}}CMj}{{KFd{IBh}}}}\",[]]<;A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]10Cf[\"{{{AHLl{CFlBCLf}}{KEh{BFCn}}}{{AHLl{CFlBCLf}}}}\",[]]Cf[\"{{{AHLl{CFlBCLh}}{KEh{BFD`}}}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{AHChCMj}\",[]]A`[\"{AHCjCMj}\",[]]Bi[\"{{{AHLl{CFlAIGl}}}{{KF`{AAhIDl}}}}\",[]]Bi[\"{{{AHLl{CFlAIGl}}}{{KF`{A@fIDl}}}}\",[]]Bj[\"{{{AHLl{CFlAIGl}}}{{KF`{AAMlIDl}}}}\",[]]Bj[\"{{{AHLl{CFlAIGl}}}{{KF`{AGOjIDl}}}}\",[]]Bi[\"{{{AHLl{CFlAIGl}}}{{KF`{AAjIDl}}}}\",[]]Bi[\"{{{AHLl{CFlAIGl}}}{{KF`{AAlIDl}}}}\",[]]Cd[\"{{{A@`{AIIj}}CMjDBh}{{KFd{{KAl{BBMfBBMf}}}}}}\",[]]Bn[\"{{{AHLl{CFlB@O`}}CMj}{{AHLl{CFlB@O`}}}}\",[]]Bn[\"{{{AHLl{CFlB@Od}}CMj}{{AHLl{CFlB@Od}}}}\",[]]Aj[\"{{{AHLl{AIIl}}}DBh}\",[]]Ak[\"{{{AHLl{BBHj}}}B@Lf}\",[]]0Ci[\"{{{AHLl{CFlBBJb}}CMf}{{KF`{{AHLl{CFlBBJb}}AOGd}}}}\",[]]Ci[\"{{{AHLl{CFlBBJd}}CMf}{{KF`{{AHLl{CFlBBJd}}AOGf}}}}\",[]]A`[\"{AGOjCMf}\",[]]Cf[\"{{{AHLl{CFlBCLf}}{KEh{BFEl}}}{{AHLl{CFlBCLf}}}}\",[]]Cf[\"{{{AHLl{CFlBCLh}}{KEh{BFEn}}}{{AHLl{CFlBCLh}}}}\",[]]Bg[\"{{{AHLl{CFlAIIn}}}{{KEh{BAKn}}}}\",[]]Cf[\"{{{AHLl{CFlB@O`}}{KEh{B@Oh}}}{{AHLl{CFlB@O`}}}}\",[]]Cf[\"{{{AHLl{CFlB@Od}}{KEh{B@Oh}}}{{AHLl{CFlB@Od}}}}\",[]]Cf[\"{{{AHLl{CFlB@O`}}{KEh{B@Oj}}}{{AHLl{CFlB@O`}}}}\",[]]Cf[\"{{{AHLl{CFlB@Od}}{KEh{B@Oj}}}{{AHLl{CFlB@Od}}}}\",[]]Cd[\"{{}{{KF`{{KAl{{ABf{BBHl}}BAOd}}{A@b{IDb}}}}}}\",[]]Bn[\"{{{AHLl{CFlBFCn}}KGl}{{AHLl{CFlBFCn}}}}\",[]]Bn[\"{{{AHLl{CFlBFD`}}KGn}{{AHLl{CFlBFD`}}}}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]Bo[\"{{{AHLl{CFlB@O`}}AANj}{{AHLl{CFlB@O`}}}}\",[]]Bo[\"{{{AHLl{CFlB@Od}}AANj}{{AHLl{CFlB@Od}}}}\",[]]Ci[\"{{{AHLl{D@h}}{KEh{AANj}}}{{KF`{{KEh{AANj}}AIHn}}}}\",[]]Bj[\"{{{AHLl{BBMn}}AIOdKAn}{{KFb{IBh}}}}\",[]]054Bm[\"{{{AHLl{NKn}}}{{KF`{{KEh{BFNd}}IDj}}}}\",[]]Bn[\"{{{AHLl{AINl}}}{{KF`{{KEh{BFNd}}IDj}}}}\",[]]Bn[\"{{{AHLl{NL`}}}{{KF`{{KEh{BFNd}}BBMl}}}}\",[]]Bo[\"{{{AHLl{AINn}}}{{KF`{{KEh{BFNd}}BBMl}}}}\",[]]Aj[\"{{{AHLl{AHBf}}}CMj}\",[]]Aj[\"{{{AHLl{AHBh}}}CMj}\",[]]Bl[\"{{{AHLl{AANn}}{KEh{{A@`{BEOn}}}}}IBh}\",[]]Cc[\"{{{AHLl{CFlAIIn}}{KEh{{AHLl{BF@`}}}}NIh}IBh}\",[]]Aj[\"{{{AHLl{AAO`}}}CMj}\",[]]Bf[\"{{{AHLl{AHA`}}{AHLl{AHA`}}}DBh}\",[]]Bn[\"{{{AHLl{CFlBANf}}AFl}{{AHLl{CFlBANf}}}}\",[]]Bn[\"{{{AHLl{CFlBANh}}AFl}{{AHLl{CFlBANh}}}}\",[]]Ak[\"{{{AHLl{AHDf}}}B@Lf}\",[]]0Ak[\"{{{AHLl{AIIl}}}BFMf}\",[]]Cb[\"{{{AHLl{AIIn}}KEl}{{KF`{{KEh{AIN`}}AIId}}}}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]Bn[\"{{{AHLl{CFlBCLf}}KGl}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}KGn}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{AGOjKEh}\",[]]Bo[\"{{{AHLl{AGOj}}{AHLl{CMd}}}{{KFb{BCLd}}}}\",[]]Em[\"{{{AHLl{AIIl}}{AHLl{CFl{JOf{Of}}}}{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}{{KF`{IBhBHIh}}}}\",[]]El[\"{{{AHLl{NKn}}{AHLl{CFl{JOf{Of}}}}{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}{{KF`{IBhBHIh}}}}\",[]]Em[\"{{{AHLl{AINl}}{AHLl{CFl{JOf{Of}}}}{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}{{KF`{IBhBHIh}}}}\",[]]El[\"{{{AHLl{NL`}}{AHLl{CFl{JOf{Of}}}}{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}{{KF`{IBhBHIj}}}}\",[]]Em[\"{{{AHLl{AINn}}{AHLl{CFl{JOf{Of}}}}{AHLl{{JOf{Of}}}}{AHLl{{JOf{Of}}}}}{{KF`{IBhBHIj}}}}\",[]]Am[\"{{{AHLl{CFlAGOn}}}IBh}\",[]]0Am[\"{{{AHLl{AIIn}}A@j}CMj}\",[]]Ak[\"{{{AHLl{AIIl}}}BGEh}\",[]]Bg[\"{{{AHLl{D@h}}}{{KF`{BEBjAIHn}}}}\",[]]>=E`[\"{{{AHLl{BHAl}}{AHLl{{JOf{Of}}}}{AHLl{BBHl}}{AHLl{BGLd}}}{{KF`{BHDbIDf}}}}\",[]]0Bg[\"{{{A@`{BAOl}}}{{KF`{B@HdBGFd}}}}\",[]]Bg[\"{{{A@`{BAOl}}}{{KF`{B@HhBGFd}}}}\",[]]Bn[\"{{{AHLl{CFlBFCn}}KGl}{{AHLl{CFlBFCn}}}}\",[]]Bn[\"{{{AHLl{CFlBFD`}}KGn}{{AHLl{CFlBFD`}}}}\",[]]o[\"{NHdNHd}\",[]]0000Aj[\"{{{AHLl{AIIn}}}DBh}\",[]]0Bd[\"{{{AHLl{CFlAIJ`}}CMjAANj}IBh}\",[]]Bc[\"{{{AHLl{CFlCNb}}CMjAANj}IBh}\",[]]Bd[\"{{{AHLl{CFlAIJb}}CMjAANj}IBh}\",[]]Bc[\"{{{AHLl{CFlCNd}}CMjAANj}IBh}\",[]]Ci[\"{{{AHLl{D@h}}{KEh{AANj}}}{{KF`{{KEh{AANj}}AIHn}}}}\",[]]Cd[\"{{{AHLl{CFlAAh}}{AHLl{AIGl}}}{{KF`{IBhIDl}}}}\",[]]Cd[\"{{{AHLl{CFlA@f}}{AHLl{AIGl}}}{{KF`{IBhIDl}}}}\",[]]Ce[\"{{{AHLl{CFlAAMl}}{AHLl{AIGl}}}{{KF`{IBhIDl}}}}\",[]]Ce[\"{{{AHLl{CFlAGOj}}{AHLl{AIGl}}}{{KF`{IBhIDl}}}}\",[]]Cd[\"{{{AHLl{CFlAAj}}{AHLl{AIGl}}}{{KF`{IBhIDl}}}}\",[]]Cd[\"{{{AHLl{CFlAAl}}{AHLl{AIGl}}}{{KF`{IBhIDl}}}}\",[]]Bo[\"{{{AHLl{CFlB@O`}}BGMb}{{AHLl{CFlB@O`}}}}\",[]]Bo[\"{{{AHLl{CFlB@Od}}BGMd}{{AHLl{CFlB@Od}}}}\",[]]Aj[\"{{{AHLl{BBJb}}}CMj}\",[]]Aj[\"{{{AHLl{BBJd}}}CMj}\",[]]Cd[\"{{{AHLl{CFlB@Hd}}{A@`{A`}}}{{AHLl{CFlB@Hd}}}}\",[]]Cd[\"{{{AHLl{CFlB@Hh}}{A@`{A`}}}{{AHLl{CFlB@Hh}}}}\",[]]Cl[\"{{BBJb{KEh{B@O`}}{A@b{BBHj}}{A@`{NKl}}}{{KFb{AANn}}}}\",[]]Aj[\"{{{AHLl{AAOf}}}DBh}\",[]]Aj[\"{{{AHLl{AAOh}}}DBh}\",[]]Ca[\"{{{AHLl{CFlAIIn}}NIhAIKh}{{KF`{IBhBHAb}}}}\",[]]Bd[\"{{{AHLl{BHAl}}}{{ABf{BCKl}}}}\",[]]Bj[\"{{{AHLl{AIIl}}AIN`}{{KF`{IBhIEf}}}}\",[]]Bm[\"{{{AHLl{CFlAIIn}}AIN`}{{KF`{IBhIEf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLf}}KAn}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}KAn}{{AHLl{CFlBCLh}}}}\",[]]Bm[\"{{}{{KF`{{KAl{BBHlBFFh}}{A@b{IDb}}}}}}\",[]]Ai[\"{AIIj{{KFd{IBh}}}}\",[]]A`[\"{AIJhCMj}\",[]]A`[\"{AIJjCMj}\",[]]An[\"{{{AHLl{AANn}}B@Hd}IBh}\",[]]Dc[\"{{{AHLl{CFlAIIn}}NIhKEl{KEh{AANj}}}{{KF`{{KEh{AANj}}AIId}}}}\",[]]o[\"{{}AHLl}\",[]]Aj[\"{{{AHLl{AIIl}}}KAn}\",[]]Bn[\"{{{AHLl{CFlB@O`}}CMj}{{AHLl{CFlB@O`}}}}\",[]]Bn[\"{{{AHLl{CFlB@Od}}CMj}{{AHLl{CFlB@Od}}}}\",[]]Bn[\"{{{AHLl{CFlBCLf}}KGl}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}KGn}{{AHLl{CFlBCLh}}}}\",[]]:Bc[\"{{{AHLl{CFlAIIn}}A@jKGn}IBh}\",[]]A`[\"{AHChCMj}\",[]]A`[\"{AHCjCMj}\",[]]Ak[\"{{}{{KF`{B@HdIE`}}}}\",[]]Ak[\"{{}{{KF`{B@HhIEb}}}}\",[]]Bn[\"{{{AHLl{CFlBCLf}}DBh}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}DBh}{{AHLl{CFlBCLh}}}}\",[]]87Cg[\"{{{AHLl{CFlAAh}}{AHLl{CFlAIGl}}}{{KF`{IBhIDl}}}}\",[]]Cg[\"{{{AHLl{CFlA@f}}{AHLl{CFlAIGl}}}{{KF`{IBhIDl}}}}\",[]]Ch[\"{{{AHLl{CFlAAMl}}{AHLl{CFlAIGl}}}{{KF`{IBhIDl}}}}\",[]]Ch[\"{{{AHLl{CFlAGOj}}{AHLl{CFlAIGl}}}{{KF`{IBhIDl}}}}\",[]]Cg[\"{{{AHLl{CFlAAj}}{AHLl{CFlAIGl}}}{{KF`{IBhIDl}}}}\",[]]Cg[\"{{{AHLl{CFlAAl}}{AHLl{CFlAIGl}}}{{KF`{IBhIDl}}}}\",[]]Bi[\"{{{AHLl{CFlAHA`}}{AHLl{AHA`}}}DBh}\",[]]n[\"{{}IBh}\",[]]Ce[\"{{{AHLl{CFlBCLf}}{KEh{KAn}}}{{AHLl{CFlBCLf}}}}\",[]]Ce[\"{{{AHLl{CFlBCLh}}{KEh{KAn}}}{{AHLl{CFlBCLh}}}}\",[]]Bn[\"{{{AHLl{AIIl}}}{{KF`{{ABf{AIN`}}IEf}}}}\",[]]Cd[\"{{{AHLl{CFlAIIn}}NIh}{{KF`{{ABf{AIN`}}IEf}}}}\",[]]Am[\"{{{AHLl{CFlAIJ`}}}IBh}\",[]]Al[\"{{{AHLl{CFlCNb}}}IBh}\",[]]Al[\"{{{AHLl{CFlICf}}}IBh}\",[]]Am[\"{{{AHLl{CFlAIJb}}}IBh}\",[]]Al[\"{{{AHLl{CFlCNd}}}IBh}\",[]]Al[\"{{{AHLl{CFlICh}}}IBh}\",[]]Ai[\"{AIIj{{KFd{IBh}}}}\",[]]Bj[\"{{{AHLl{AIIl}}AIN`}{{KF`{IBhIEf}}}}\",[]]Bm[\"{{{AHLl{CFlAIIn}}AIN`}{{KF`{IBhIEf}}}}\",[]]D`[\"{{{AHLl{CFlAIIn}}KEl{KEh{AANj}}}{{KF`{{KEh{AANj}}AIId}}}}\",[]]Cf[\"{{{AHLl{CFlBCLf}}{A@`{BENn}}}{{AHLl{CFlBCLf}}}}\",[]]Cf[\"{{{AHLl{CFlBCLh}}{A@`{BEO`}}}{{AHLl{CFlBCLh}}}}\",[]]Cf[\"{{{AHLl{CFlBCLf}}{KEh{AANj}}}{{AHLl{CFlBCLf}}}}\",[]]Cf[\"{{{AHLl{CFlBCLh}}{KEh{AANj}}}{{AHLl{CFlBCLh}}}}\",[]]Ce[\"{{{AHLl{CFlBCLf}}{KEh{CMj}}}{{AHLl{CFlBCLf}}}}\",[]]Ce[\"{{{AHLl{CFlBCLh}}{KEh{CMj}}}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{BALdKEh}\",[]]A`[\"{BALfKEh}\",[]];Bn[\"{{{AHLl{CFlBCLf}}DBh}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}DBh}{{AHLl{CFlBCLh}}}}\",[]]Am[\"{{{AHLl{AIIl}}KGl}IBh}\",[]]o[\"{NHdNHd}\",[]]Bn[\"{{{AHLl{CFlBCLf}}CMh}{{AHLl{CFlBCLf}}}}\",[]]Bn[\"{{{AHLl{CFlBCLh}}CMh}{{AHLl{CFlBCLh}}}}\",[]]310;:54Bn[\"{{{AHLl{AIIl}}}{{KF`{{ABf{AIN`}}IEf}}}}\",[]]Bn[\"{{{AHLl{AIIn}}}{{KF`{{ABf{AIN`}}IEf}}}}\",[]]10Bm[\"{{{AHLl{CFlBCLf}}Of}{{AHLl{CFlBCLf}}}}\",[]]Bm[\"{{{AHLl{CFlBCLh}}Of}{{AHLl{CFlBCLh}}}}\",[]]A`[\"{BFNdKEh}\",[]]") \ No newline at end of file diff --git a/pr/451/docs/search.index/generic_inverted_index/5e274bd1d1e0.js b/pr/451/docs/search.index/generic_inverted_index/5e274bd1d1e0.js new file mode 100644 index 000000000..735c3af32 --- /dev/null +++ b/pr/451/docs/search.index/generic_inverted_index/5e274bd1d1e0.js @@ -0,0 +1 @@ +rb_("QU5sADswAAABAADuAAIAbQPtAFYMAAA7MAAAAQAAawIqACQCGAA+AgwATAIAAE4CAABRAgEAVAICAFoCAABcAgcAaQIDAG4CCAB4AgAAewIGAIMCBACKAgEAjQIAAI8CAwCUAgMAmQIBAJwCDwC3AhYA0wIDANgCEQDrAgMA8AIFAPcCAwD8AgAA/gIEAAQDBQALAwYAEwMEABkDAAAbAwQAIQMCACUDAQAoAwcAMgMAADwDCwBJAw0AdQmMAAIL7QAVEAAAwhABADswAAABAADeAQ4AlwACAM0F7QACCjYAPAoSAFAKBQBXChwAdgoBAHoKFACRCgAAkwoeALMKBQC6CikA5goCAOsKFgA7MAAAAQAAIwELAG0CAADXAgAASAMAAIQHEwCyCgAAuQoAAOQKAADqCgAAnQ4EARwQAQBTEAEAOzAAAAEAAIUBBwBhBAAAQAkBAOUKAADpCgAAGA3tAAgOjADXEQUAOzAAAAEAAAYAAgDWBgMA2wYCADswAAABAAAsAAcAVwMVAJsFAADRBgQA3wYMADsJAQBnDAAA+BIAADswAAABAAAKAAIAfgQAAPALCQA7MAAAAQAADAABAG0EDAA7MAAAAQAABQACAGwMAwBYEQEAAsQRAADFEQAAAAAAAtEBAADaBgAAQ24AAAA7MAAAAQAA7QAMAAIKNgA8ChIAUAoFAFcKHAB2CgEAegoUAJEKAACTCh4AswoFALoKKQDmCgIA6woWAA==") \ No newline at end of file diff --git a/pr/451/docs/search.index/generic_inverted_index/e9848908a123.js b/pr/451/docs/search.index/generic_inverted_index/e9848908a123.js deleted file mode 100644 index 6e93adf41..000000000 --- a/pr/451/docs/search.index/generic_inverted_index/e9848908a123.js +++ /dev/null @@ -1 +0,0 @@ -rb_("QU5gADswAAABAADtAAIAZQPsAEQMAAA7MAAAAQAAaAIoACACFwA5Ag4ASwIAAE0CAABPAgMAVQIAAFcCAABZAgYAYgIAAGYCAQBpAgoAdwIGAH8CBACGAgEAiQIAAIsCAgCPAgUAlgIOAKoCAACxAhYAywIAAM4CAgDSAhIA5gIDAOsCBADxAgQA+AIFAP8CBAAFAwYADQMEABMDAAAVAwUAHAMEACIDBgA0AwwAQgMNAGcJiwDyCuwA/A8AAKgQAQA7MAAAAQAA3AEPAJYAAgDDBewA8wk1ACsKAAAtChIAQQoFAEgKHABnCgAAagoVAIMKHgCjCgQAqQoqANUKAADXCgEA2woWADswAAABAAAgAQsAaAIAANECAABBAwAAeAcTAKIKAACoCgAA1goAANkKAACIDgEBAhABADkQAQA7MAAAAQAAgwEHAFgEAAAzCQEA1AoAANoKAAAFDewA9A2LALwRBQA7MAAAAQAABgACAMsGAwDQBgIAOzAAAAEAACkABQBQAxQAkQUAAMYGBADUBgwALgkBADswAAABAAAKAAIAdQQAAN8LCQA7MAAAAQAADAABAGQEDAA7MAAAAQAABQACAFoMAwA9EQEAAqkRAACqEQAAAAAAAs0BAADPBgAARGIAAAA7MAAAAQAA7AANAPMJNQArCgAALQoSAEEKBQBIChwAZwoAAGoKFQCDCh4AowoEAKkKKgDVCgAA1woBANsKFgA=") \ No newline at end of file diff --git a/pr/451/docs/search.index/name/14e0528539da.js b/pr/451/docs/search.index/name/14e0528539da.js deleted file mode 100644 index 09541b27b..000000000 --- a/pr/451/docs/search.index/name/14e0528539da.js +++ /dev/null @@ -1 +0,0 @@ -rd_("b()b->bBi0bCe00bFnbIdbOkb[]beq00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bid000000000000000000brtbtybu8cAck0cAddcAnycArccBoxcBufcClicDir0cErrcMAX000cNotcOpt00cOrdcPincUni0cVeccadd00000ccmp0000000000000000000cdir000cecn000cf32cf64cfmt000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cget0ci32cimpcios0clen000clog0cmincmutcnew0000000000000000000000000000000000cnoqcnot0cnow000000crtt000crun0cstrcu16cu32cu64cudpdArgsdAsFddBbr300dDropdEct000dEct100dFromdHashdIntodKeys0dLong0dNonedPath00dPing0dPolldReaddSide0dZERO0dacks0daddr00dbbr3dbooldbulkdcodedcwnd0ddataddrop000000dfrom000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dhash00000000000000000000dinto00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dmain0000dnext0dopen00dpath0dperfdping000dpoll00000000000000dqlogdread00drecv00dseal0dsend00dsent0dside0dsign0dsize00dsmoldspindstop00dtake00000du128duniteAsRefeByteseChunk0eCloneeCloseeCubic00eDebugeDerefeError00000000000eEvent00eKnowneOther0eRangeeReset000eRetry0eShort0eSizedeStats0eSuiteeTokioeWriteealpns0earrayebenchebuild00000000ebytes000ecauseeclone0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eclose000ecubicederef0eerroreframe0eindex0eis_ce0elocal0eparse0epaths0eprint00ereset00eretry0esliceespawn00estats0000000000etitle0etokenetokioetupleeusizeewrite00fAcceptfAckEcn0fAes128fAes256fBackup0fBorrowfBufMutfChunksfClient00fClosed0fCrypto0fCursorfFuturefHasherfIpAddrfLaggedfOpenBifOpened0fOptionfPathId0fResult00000fServer00fStream000fStringfTypeIdfVarInt0faccept000fborrow00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fcipherfclient0000fclosedfcoding0fconfig0fcrypto0000000000fdecode000000000000fdst_ipfencode000000000000ffinish00fhandlefheader0fignore0finsert00000fkeylogfoffset0fon_ack0000000fpacket00frandomfreason00000000frebindfrefuse0fremote00frustls00000fserver0000fsharedfsource00000000fsrc_ip00fstatus00fstream0fstridefudp_rx000fudp_tx000fvarintfverify0fwindow0000000gBlocked00gCommandgContextgDefaultgDisplaygFromStrgGeneral0gHmacKey0gInitialgInstantgKeyPair0gLayeredgMaxData0gNewReno00gOpenUnigPadding0gPathAck0gPathBufgReadBufgRequestgRuntime0gSession0gStopped00000gStreamsgToOwnedgTooLonggTryFromgTryIntogUnknowngZeroRttgcid_len000gclientsgcommand00000gcompare0000000000000000000gconnect0gdecrypt00000gdefault000000000000000000000000000000000000000000000000000gdrainedgdst_cid0000000gencrypt000gis_0rtt0gmetrics0000000gmin_mtu0gnoq_udpgon_sent0gopen_bigpath_idgpointergruntimegsrc_cid0000gstopped0gstreams000gtag_len0gtype_id00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gupgrade0gversion00000hAcceptBihBytesMuthChacha20hCommandshDatagram00hDerefMuthDisabled0hDurationhEndpoint00hFinished0hIncoming0hIteratorhLayerExthLongTypehMAX_SIZE0hNO_ERROR0hNewToken0hOnClosedhOpenPathhOrderinghReadable0hRecvMetahResponsehStreamId0hTimedOut00hToStringhTokenLog0hTooLarge0hTransmit00hUdpStats0hUnixTimehWritable0hcong_alghcontentshdatagram0hdurationhendpoint0hfinalizehframe_rx000hframe_tx000hfrom_buf0hfrom_env0hfrom_keyhfrom_str0hfrom_u320hfrom_u640hgot_type000hgroup_id0000hhas_0rtthincominghinterval0hinto_any0000000his_alivehlifetime0hlocal_ip0000hmax_data0hmax_sizehnew_renohnoq_perfhon_byteshopen_unihpriority0hprotocol0hqlog_dirhresponsehrng_seed0hssthresh0hto_owned0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_from00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_into00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_poll000000000htry_sendhvalidate000iAbandoned000iAcceptUniiAddAssigniArbitraryiAsyncRead0iAvailable000iBorrowMutiCommonOptiConnectediCriterioniDatagramsiDecodableiDiscarded000iEncodableiFormatteriFourTupleiFrameType0iHandshakeiHeaderKey0iHistogramiMalformediMaxPathId0iMultipathiPacketKey0iPartialEqiPathError0iPathEvent000iPathStats0iReadError00iTryFutureiTryStreamiUdpSenderiUdpSocketiValueEnumiaccept_biiarbitrary0000000ias_rustlsiassemblericlone_box0000000idatagrams000ideref_mut0ifrom_bits00iinitiator0iinto_0rttiinto_iteriis_client0iis_closediis_server0imay_retry0imigration0inew_timer00inew_token0inoq_protoion_closediopen_path0ipath_acks0iping_pathipoll_next000ipoll_read00ipoll_recvipoll_sendireach_out0ireferenceireset_key0iretry_tag0000isize_hint000istable_idito_string000000000000000000000000000000000000000000000000000000000000000000itoken_key0itoken_positransport00iwait_idleiwrite_alljArgMatchesjAsyncTimerjAsyncWrite0jBATCH_SIZEjBbr3Config0jClosedPath00000jComparablejConnectingjConnection00jController0jEquivalent0jFrameStats0jIntoFuturejInvalidCid0jIoSliceMutjMaybeFrame0jNonZeroU32jOtherErrorjPartialOrdjPathAckEcn0jPathEventsjPathStatus0jQlogConfig0jRecvStream00jResetErrorjRetryError0jSendStream00jServerNamejSocketAddrjStreamInfojSubcommandjSystemTimejTimeSource0jTlsSession0jTokenStore0jUdpSockRefjWriteError0jaccept_unijadd_assign00000jallow_spin0jborrow_mut00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jbytes_read0jclone_into0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jclose_pathjcongestion000jconn_statsjconnection0jequivalent00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jerror_code000000jframe_type0jinto_inner0jis_drained0jlocal_addr0jlost_bytes000jnew_senderjpad_to_mtu0jpath_stats0000jpoll_closejpoll_flush0jpoll_write00jprint_jsonjread_chunk0jread_exactjset_statusjstart_time0jthroughputjtotal_sizekAcceptErrorkCLIENT_ADDRkCipherSuitekClientStatskCloseReasonkConfigError0kContentTypekCryptoError0kCubicConfig0kDataBlocked0kEstablished000kFinishErrorkIdleTimeout0kIntoRequestkOutOfBounds0kPathAbandon0kQlogFactory0kResetStream0kRuntimeTypekSERVER_ADDRkSERVER_NAMEkSmolRuntimekStopSending0kStreamEvent0kStreamStats0kaccept_withkadd_address0kbind_socketkcertificatekcurrent_mtu00kdescription0kdestination00kfrom_remotekinitial_mtu000kinitial_rtt00kinto_future0000000000000kmax_path_id0kmax_streamsknamed_layer0kon_end_acks000kon_intervalkpacing_rate0kpartial_cmp0000000000000000000kpath_eventskpath_statuskread_to_endkrecv_stream0ksample_size0ksend_bufferksend_stream0ksend_window00kserver_name0ktime_source0ktoken_store0kupload_sizekupper_bound0kweak_handle0kwith_crypto0kwith_prefix0kwrite_chunklAckFrequency0lAddressAddedlClientConfig00000lClosedStream000000lConnectError00lConnectionId00lDatagramInfolDecryptError0lEcnCodepoint00lEncryptError0lIllegalValuelImmediateAck0lIntoIteratorlLOCALHOST_V4lLastOpenPath0lNatTraversallNonZeroUsizelNoneTokenLog0lObservedAddr000lPathResponse0lPathsBlocked0lReadDatagramlRemoteStatus000lRttEstimator0lSendDatagramlServerConfig00000lSocketAddrV4lSocketAddrV6lStoppedErrorlTokioRuntimelUnstructuredlaugment_args0000lcid_lifetime000lclose_reasonlconnect_withlconservativeldata_blocked0ldrain_streamlearly_crypto000levent_streamlexpect_types000lgenerate_cid000lgro_segmentslhandle_event0linitial_keys00000000linto_request0llost_packets000lmax_incoming0lmay_fragment00lnetwork_pathlnew_receiverlnoprotection0lorig_dst_cid0lpath_abandon0lpoll_timeoutlqlog_factory0lreset_stream0lruntime_typelsegment_size00lsend_quantum0lsend_streamslset_lifetime0lset_priority0lstop_sending0lstream_statslupload_statslwith_initial000lwrite_chunksmAlertReceived0mBloomTokenLog0mCidsExhausted000mCloneToUninitmDatagramEventmEndpointEventmEndpointStatsmFinishedEarlymHandshakeData0mHandshakeDone0mHandshakeTypemINVALID_TOKEN0mIncomingAlpns0mInvalidHeadermLocallyClosed0mMaxStreamData0mMaxStreamsUni0mNewConnectionmNewRenoConfig0mParseIntErrormPartialDecodemPathChallenge0mPossibleValuemPrivateKeyDermReadableErrormRemoveAddress0mRootCertStoremStdSystemTime0mUnexpectedEndmaccepted_0rttmack_frequency00mbench_exports0mcid_generator00mcreate_sendermdata_transfermdownload_sizemduration_histmimmediate_ack0minto_incoming0mmax_ack_delay0mno_protectionmobserved_addr0mon_first_bytemon_mtu_update0000000mpath_response0mpaths_blocked0mpeer_identity0000mpoll_shutdownmpoll_transmitmqlog_from_env0mread_datagrammsend_datagrammsend_fairness0msignature_len0mstart_session000000000mtry_poll_next0nAddIpv4Address0nAddIpv6Address0nAddressRemovednAsyncUdpSocketnCertificateDernClosePathError0nCommandFactorynConnectionLost000000nEndpointConfig0nFromArgMatchesnINTERNAL_ERROR0nIncomingFuturenInvalidFrameIdnInvalidMessage00nMaxStreamsBidi0nNO_VIABLE_PATH0nNoneTokenStore0nPeerMisbehaved00nReachOutAtIpv40nReachOutAtIpv60nReadExactErrornReadToEndErrornShouldTransmitnTransferResultnTransportError00nUdpSocketStatenWeakPathHandlenconnect_clientncrypto_sessionndownload_statsnfor_connection000nhandle_timeoutnhandshake_data00000nhandshake_done0nhas_subcommandninitial_window0000000000000ninto_unorderednis_handshaking0000nis_valid_retry000nminimum_change0nnext_1rtt_keys000non_packet_lost000non_packet_sent000npath_challenge0nqlog_from_path0nread_handshake000nread_unorderednreceive_window00nreceived_reset00nremote_address000nremove_address0nsaturating_add000nsaturating_sub0nthroughput_bpsntime_threshold0ntotal_durationnvalue_variantsoConnectionClose000oConnectionError0oConnectionEvent0oConnectionStats0oGetRandomFailedoNewConnectionId0oOpenStreamStatsoPathCidsBlocked0oProtectedHeader0oQlogFileFactory0oRemoteAbandoned0oSignatureSchemeoSystemTimeErroroTokenReuseError0oTransportConfig00oTryFromIntErroroVersionMismatch0oZeroRttAcceptedoZeroRttRejected000obloom_token_logoclone_to_uninit0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000odefault_runtimeogrease_quic_bit0ointegrity_limit0ointerface_indexomax_stream_data0omax_streams_uni0oparse_byte_sizeorebind_abstractosend_unreliableoserver_endpointoset_path_statusoset_send_window0oshould_transmitostream_finishedothroughput_histowrap_udp_socket00owrite_handshake000A`AddressDiscoveryA`ApplicationClose000A`CertificateErrorA`ConnectionClosed0A`ConnectionHandleA`DatagramReceivedA`DecryptedInitial0A`EndpointStopping0A`FINAL_SIZE_ERROR0A`InconsistentKeys00A`KEY_UPDATE_ERROR0A`MaxPathIdReached0A`ObservedIpv4Addr0A`ObservedIpv6Addr0A`PathStatusBackup0A`PeerIncompatible00A`QuicClientConfig0A`QuicServerConfig0A`TokenMemoryCache0A`TooManyAddressesA`ValidationFailed00A`VersionNegotiate0A`check_and_insert00000A`configure_clientA`congestion_state0A`connection_close0A`force_key_update0A`from_arg_matches00000A`max_gso_segmentsA`max_idle_timeout0A`max_streams_bidi0A`n0_nat_traversal0A`open_connections0A`open_path_ensure0A`packet_threshold0A`read_many_chunksA`receive_datagramA`recv_buffer_size0A`send_buffer_size0A`transport_config0000A`validation_token0A`with_single_cert0A`write_all_chunksAaAPPLICATION_ERROR0AaApplicationClosed000AaControllerFactory0AaControllerMetrics0AaHandshakeTokenKey0AaInvalidServerName0AaNetworkChangeHint0AaPacketDecodeError0AaPathAbandonReason00AaSendDatagramError0AaStreamDataBlocked0AaStreamsBlockedUni0AaUnsupportedByPeer0Aacongestion_events0Aacongestion_window0Aamax_datagram_sizeAanew_connection_id0Aapath_cids_blocked0Aasend_buffer_spaceAaset_server_config0Aaset_up_connectionAato_possible_valueAawrite_many_chunksAbAEAD_LIMIT_REACHED0AbAckFrequencyConfig0AbBadMaxFragmentSize0AbCONNECTION_REFUSED0AbConnectionIdParserAbDatagramsUnblockedAbFLOW_CONTROL_ERROR0AbHandshakeConfirmedAbHandshakeDataReadyAbIllegalOrderedReadAbInvalidCertificate0AbMtuDiscoveryConfig0AbNotEnoughAddressesAbPERF_CIPHER_SUITESAbPROTOCOL_VIOLATION0AbPrivatePkcs8KeyDerAbRetireConnectionId0AbSTREAM_LIMIT_ERROR0AbSTREAM_STATE_ERROR0AbServerCertVerifiedAbServerCertVerifierAbSetPathStatusError0AbStreamsBlockedBidi0AbTokioCurrentThreadAbTransportErrorCode0AbUnsupportedVersion00000Abcommand_for_update0000Abcrypto_buffer_size0Abfrom_u64_unchecked0Abignored_handshakesAbis_same_connectionAbmin_reset_interval0Abnew_expected_items0Abpath_status_backup0Abrefused_handshakesAbremote_path_statusAbsend_datagram_waitAbset_alpn_protocols000Abset_receive_window0Absupported_versions0Abtoken_memory_cacheAbverify_server_certAbwith_start_instant0AcCongestionAlgorithmAcNatTraversalUpdatesAcPathNewConnectionId0AcPathStatusAvailable0AcRemoteCidsExhausted0AcTransportParametersAcUnorderedRecvStreamAcUnsupportedNameType0AcWrongConnectionSideAcaccepted_handshakesAcarbitrary_take_rest000Acaugment_subcommandsAcblack_hole_cooldown0Acearly_data_accepted000Achandshake_confirmedAcis_path_recoverable0Ackeep_alive_interval0Aclost_plpmtud_probes0Acon_congestion_event0000000Acoutgoing_handshakesAcremote_open_streamsAcsend_buffer_benches00Acsend_data_on_streamAcsent_plpmtud_probes0Acstream_data_blocked0Acstreams_blocked_uni0AdFRAME_ENCODING_ERROR0AdHandshakeNotComplete0AdInappropriateMessage000AdInvalidRemoteAddress000AdNoInitialCipherSuite0AdObservedExternalAddrAdServerSideNotAllowed0AdSupportedCipherSuiteAdVarIntBoundsExceeded0AdVerifierBuilderErrorAdWeakConnectionHandleAdack_frequency_config0Adblack_holes_detected0Adfrom_arg_matches_mut00000Adhandle_client_streamAdincoming_buffer_size0Adis_multipath_enabledAdmax_receive_segments0Admax_udp_payload_size00Admtu_discovery_config0Adpoll_endpoint_eventsAdpreferred_address_v40Adpreferred_address_v60Adread_certs_from_fileAdreordering_threshold0Adretire_connection_id0Adretry_token_lifetime0Adset_max_idle_timeoutAdset_recv_buffer_sizeAdset_send_buffer_sizeAdstreams_blocked_bidi0Adtransport_parameters00000AeConnectionIdGenerator0AeDigitallySignedStructAeNoApplicationProtocol0AeNoDefaultClientConfig0AePATH_UNSTABLE_OR_POOR0AeValidationTokenConfig0Aeconfidentiality_limit0Aehandle_network_change0Aeincoming_buffer_bytesAeis_probably_same_pathAeloss_reduction_factor0Aemax_transmit_segments0Aenat_traversal_updatesAepath_observed_addressAepath_status_available0Aestream_receive_window00AfCRYPTO_BUFFER_EXCEEDED0AfExtensionNotNegotiatedAfFailedToGetCurrentTime0AfFailedToGetRandomBytes0AfMultipathNotNegotiated000000AfNatTraversalRoundEndedAfPathRetireConnectionId0AfProtectedInitialHeaderAfSkipServerVerificationAfbuild_transport_configAfexport_keying_material0000Afget_into_many_segmentsAfget_loop_many_segmentsAfmax_concurrent_streamsAfobserved_external_addr0Afpath_new_connection_id0Afverify_tls12_signatureAfverify_tls13_signatureAfwith_root_certificates0AgCertRevocationListErrorAgHandshakeSignatureValidAgNoCertificatesPresented0AgPeerSentOversizedRecord0Agack_eliciting_threshold0Agaugment_args_for_update0000Aghas_pending_retransmitsAgis_multipath_negotiatedAgon_ack_frequency_update000Agset_keep_alive_intervalAgupdate_from_arg_matches00000Agvalidation_token_config0AhAPPLICATION_ABANDON_PATH0AhNoProtectionClientConfigAhNoProtectionServerConfigAhget_max_udp_payload_size0Ahinitial_dst_cid_provider0Ahnew_with_abstract_socketAhremote_address_validated0Ahset_max_concurrent_pathsAhsupported_verify_schemesAiCONNECTION_ID_LIMIT_ERROR0AiEncryptedClientHelloErrorAiExportKeyingMaterialError0AiInvalidCertRevocationList0AiNO_CID_AVAILABLE_FOR_PATH0AiTRANSPORT_PARAMETER_ERROR0AiUnsupportedOperationErrorAiadd_nat_traversal_address0Aidatagram_send_buffer_size0Aigenerate_self_signed_certAiopen_bidirectional_streamAipath_retire_connection_id0Aiset_default_client_configAiset_path_max_idle_timeoutAjDEFAULT_SUPPORTED_VERSIONSAjUnusableAfterNetworkChangeAjdatagram_send_buffer_spaceAjincoming_buffer_size_total0Ajmax_concurrent_uni_streams0Ajopen_unidirectional_streamAjset_max_concurrent_streamsAjspurious_congestion_events0Ajtry_with_platform_verifier0AkHashedConnectionIdGeneratorAkInvalidEncryptedClientHello0AkPATH_RESOURCE_LIMIT_REACHED0AkRandomConnectionIdGeneratorAkenable_segmentation_offload0Akmax_concurrent_bidi_streams0Akupdate_from_arg_matches_mut00000Akupdate_local_if_same_remoteAlconfigure_tracing_subscriberAldatagram_receive_buffer_size0Alinitiate_nat_traversal_round0Alon_spurious_congestion_event00000Alreceive_bidirectional_streamAlremove_nat_traversal_address0Alset_path_keep_alive_intervalAmFixedLengthConnectionIdParserAmInappropriateHandshakeMessage000Amcongestion_controller_factory0Amdefault_path_max_idle_timeout0Ammax_outgoing_bytes_per_second0Amnegotiated_key_exchange_group0Amreceive_unidirectional_streamAmsend_observed_address_reports0Amset_max_concurrent_bi_streamsAnaugment_subcommands_for_updateAnmax_concurrent_multipath_paths0Anset_max_concurrent_uni_streamsAopersistent_congestion_threshold0B`default_path_keep_alive_interval0B`receive_observed_address_reports0Baget_local_nat_traversal_addresses0Bbget_remote_nat_traversal_addresses0Bbmax_remote_nat_traversal_addresses00") \ No newline at end of file diff --git a/pr/451/docs/search.index/name/b64d8369dc91.js b/pr/451/docs/search.index/name/b64d8369dc91.js new file mode 100644 index 000000000..72651b222 --- /dev/null +++ b/pr/451/docs/search.index/name/b64d8369dc91.js @@ -0,0 +1 @@ +rd_("b()b->bBi0bCe00bFnbIdbOkb[]beq000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bid000000000000000000brtbtybu8cAck0cAddcAnycArccBoxcBufcClicDir0cErrcMAX000cNotcOpt00cOrdcPincUni0cVeccadd00000ccmp0000000000000000000cdir000cecn000cf32cf64cfmt00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cget0ci32cimpcios0clen000clog0cmincmutcnew00000000000000000000000000000000000cnoqcnot0cnow000000crtt000crun0cstrcu16cu32cu64cudpdArgsdAsFddBbr300dDropdEct000dEct100dFromdHashdIntodKeys0dLong0dNonedPath00dPing0dPolldReaddSide0dZERO0dacks0daddr00dbbr3dbooldbulkdcodedcwnd0ddataddrop000000dfrom000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dhash000000000000000000000dinto000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dmain0000dnext0dopen00dpath0dperfdping000dpoll00000000000000dqlogdread00drecv00dseal0dsend00dsent0dside0dsign0dsize00dsmoldspindstop00dtake00000du128duniteAsRefeByteseChunk0eCloneeCloseeCubic00eDebugeDerefeError00000000000eEvent00eKnowneOther0eRangeeReset000eRetry0eShort0eSizedeStats0eSuiteeTokioeWriteealpns0earrayebenchebuild00000000ebytes000ecauseeclone00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eclose000ecubicederef0eerroreframe0eindex0eis_ce0elocal0eparse0epaths0eprint00ereset00eretry0esliceespawn00estats0000000000etitle0etokenetokioetupleeusizeewrite00fAcceptfAckEcn0fAes128fAes256fBackup0fBorrowfBufMutfChunksfClient00fClosed0fCrypto0fCursorfFuturefHasherfIpAddrfLaggedfOpenBifOpened0fOptionfPathId0fResult00000fServer00fStream000fStringfTypeIdfVarInt0faccept000fborrow000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fcipherfclient0000fclosedfcoding0fconfig0fcrypto0000000000fdecode000000000000fdst_ipfencode000000000000ffinish00fhandlefheader0fignore0finsert00000fkeylogfoffset0fon_ack0000000fpacket00frandomfreason00000000frebindfrefuse0fremote000frustls00000fserver0000fsharedfsource00000000fsrc_ip00fstatus00fstream0fstridefudp_rx000fudp_tx000fvarintfverify0fwindow0000000gBlocked00gCommandgContextgDefaultgDisplaygFromStrgGeneral0gHmacKey0gInitialgInstantgKeyPair0gLayeredgMaxData0gNewReno00gOpenUnigPadding0gPathAck0gPathBufgReadBufgRequestgRuntime0gSession0gStopped00000gStreamsgToOwnedgTooLonggTryFromgTryIntogUnknowngZeroRttgcid_len000gclientsgcommand00000gcompare0000000000000000000gconnect0gdecrypt00000gdefault000000000000000000000000000000000000000000000000000gdrainedgdst_cid0000000gencrypt000gis_0rtt0gmetrics0000000gmin_mtu0gnoq_udpgon_sent0gopen_bigpath_idgpointergruntimegsrc_cid0000gstopped0gstreams000gtag_len0gtype_id000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gupgrade0gversion00000hAcceptBihBytesMuthChacha20hCommandshDatagram00hDerefMuthDisabled0hDurationhEndpoint00hFinished0hIncoming0hIteratorhLayerExthLongTypehMAX_SIZE0hNO_ERROR0hNewToken0hOnClosedhOpenPathhOrderinghReadable0hRecvMetahResponsehStreamId0hTimedOut00hToStringhTokenLog0hTooLarge0hTransmit00hUdpStats0hUnixTimehWritable0hcong_alghcontentshdatagram0hdurationhendpoint0hfinalizehframe_rx000hframe_tx000hfrom_buf0hfrom_env0hfrom_keyhfrom_str0hfrom_u320hfrom_u640hgot_type000hgroup_id0000hhas_0rtthincominghinterval0hinto_any0000000his_alivehlifetime0hlocal_ip00000hmax_data0hmax_sizehnew_renohnoq_perfhon_byteshopen_unihpriority0hprotocol0hqlog_dirhresponsehrng_seed0hssthresh0hto_owned00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_from000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_into000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000htry_poll000000000htry_sendhvalidate000iAbandoned000iAcceptUniiAddAssigniArbitraryiAsyncRead0iAvailable000iBorrowMutiCommonOptiConnectediCriterioniDatagramsiDecodableiDiscarded000iEncodableiFormatteriFourTuple0iFrameType0iHandshakeiHeaderKey0iHistogramiMalformediMaxPathId0iMultipathiPacketKey0iPartialEqiPathError0iPathEvent000iPathStats0iReadError00iTryFutureiTryStreamiUdpSenderiUdpSocketiValueEnumiaccept_biiarbitrary0000000ias_rustlsiassemblericlone_box0000000idatagrams000ideref_mut0ifrom_bits00iinitiator0iinto_0rttiinto_iteriis_client0iis_closediis_server0imay_retry0imigration0inew_timer00inew_token0inoq_protoion_closediopen_path0ipath_acks0iping_pathipoll_next000ipoll_read00ipoll_recvipoll_sendireach_out0ireferenceireset_key0iretry_tag0000isize_hint000istable_idito_string0000000000000000000000000000000000000000000000000000000000000000000itoken_key0itoken_positransport00iwait_idleiwrite_alljArgMatchesjAsyncTimerjAsyncWrite0jBATCH_SIZEjBbr3Config0jClosedPath00000jComparablejConnectingjConnection00jController0jEquivalent0jFrameStats0jIntoFuturejInvalidCid0jIoSliceMutjMaybeFrame0jNonZeroU32jOtherErrorjPartialOrdjPathAckEcn0jPathEventsjPathStatus0jQlogConfig0jRecvStream00jResetErrorjRetryError0jSendStream00jServerNamejSocketAddrjStreamInfojSubcommandjSystemTimejTimeSource0jTlsSession0jTokenStore0jUdpSockRefjWriteError0jaccept_unijadd_assign00000jallow_spin0jborrow_mut000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jbytes_read0jclone_into00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jclose_pathjcongestion000jconn_statsjconnection0jequivalent00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000jerror_code000000jframe_type0jinto_inner0jis_drained0jlocal_addr0jlost_bytes000jnew_senderjpad_to_mtu0jpath_stats0000jpoll_closejpoll_flush0jpoll_write00jprint_jsonjread_chunk0jread_exactjset_statusjstart_time0jthroughputjtotal_sizekAcceptErrorkCLIENT_ADDRkCipherSuitekClientStatskCloseReasonkConfigError0kContentTypekCryptoError0kCubicConfig0kDataBlocked0kEstablished000kFinishErrorkIdleTimeout0kIntoRequestkOutOfBounds0kPathAbandon0kQlogFactory0kResetStream0kRuntimeTypekSERVER_ADDRkSERVER_NAMEkSmolRuntimekStopSending0kStreamEvent0kStreamStats0kaccept_withkadd_address0kbind_socketkcertificatekcurrent_mtu00kdescription0kdestination00kfrom_remote0kinitial_mtu000kinitial_rtt00kinto_future0000000000000kis_drainingkmax_path_id0kmax_streamsknamed_layer0kon_end_acks000kon_intervalkpacing_rate0kpartial_cmp0000000000000000000kpath_eventskpath_statuskread_to_endkrecv_stream0ksample_size0ksend_bufferksend_stream0ksend_window00kserver_name0ktime_source0ktoken_store0kupload_sizekupper_bound0kweak_handle0kwith_crypto0kwith_prefix0kwrite_chunklAckFrequency0lAddressAddedlClientConfig00000lClosedStream000000lConnectError00lConnectionId00lDatagramInfolDecryptError0lEcnCodepoint00lEncryptError0lIllegalValuelImmediateAck0lIntoIteratorlLOCALHOST_V4lLastOpenPath0lNatTraversallNonZeroUsizelNoneTokenLog0lObservedAddr000lPathResponse0lPathsBlocked0lReadDatagramlRemoteStatus000lRttEstimator0lSendDatagramlServerConfig00000lSocketAddrV4lSocketAddrV6lStoppedErrorlTokioRuntimelUnstructuredlaugment_args0000lcid_lifetime000lclose_reasonlconnect_withlconservativeldata_blocked0ldrain_streamlearly_crypto000levent_streamlexpect_types000lgenerate_cid000lgro_segmentslhandle_event0linitial_keys00000000linto_request0llost_packets000lmax_incoming0lmay_fragment00lnetwork_path0lnew_receiverlnoprotection0lorig_dst_cid0lpath_abandon0lpoll_timeoutlqlog_factory0lreset_stream0lruntime_typelsegment_size00lsend_quantum0lsend_streamslset_lifetime0lset_priority0lstop_sending0lstream_statslupload_statslwith_initial000lwrite_chunksmAlertReceived0mBloomTokenLog0mCidsExhausted000mCloneToUninitmDatagramEventmEndpointEventmEndpointStatsmFinishedEarlymHandshakeData0mHandshakeDone0mHandshakeTypemINVALID_TOKEN0mIncomingAlpns0mInvalidHeadermLocallyClosed0mMaxStreamData0mMaxStreamsUni0mNewConnectionmNewRenoConfig0mParseIntErrormPartialDecodemPathChallenge0mPossibleValuemPrivateKeyDermReadableErrormRemoveAddress0mRootCertStoremStdSystemTime0mUnexpectedEndmaccepted_0rttmack_frequency00mbench_exports0mcid_generator00mcreate_sendermdata_transfermdownload_sizemduration_histmimmediate_ack0minto_incoming0mmax_ack_delay0mno_protectionmobserved_addr0mon_first_bytemon_mtu_update0000000mpath_response0mpaths_blocked0mpeer_identity0000mpoll_shutdownmpoll_transmitmqlog_from_env0mread_datagrammsend_datagrammsend_fairness0msignature_len0mstart_session000000000mtry_poll_next0nAddIpv4Address0nAddIpv6Address0nAddressRemovednAsyncUdpSocketnCertificateDernClosePathError0nCommandFactorynConnectionLost000000nEndpointConfig0nFromArgMatchesnINTERNAL_ERROR0nIncomingFuturenInvalidFrameIdnInvalidMessage00nMaxStreamsBidi0nNO_VIABLE_PATH0nNoneTokenStore0nPeerMisbehaved00nReachOutAtIpv40nReachOutAtIpv60nReadExactErrornReadToEndErrornShouldTransmitnTransferResultnTransportError00nUdpSocketStatenWeakPathHandlenconnect_clientncrypto_sessionndownload_statsnfor_connection000nhandle_timeoutnhandshake_data00000nhandshake_done0nhas_subcommandninitial_window0000000000000ninto_unorderednis_handshaking0000nis_valid_retry000nminimum_change0nnext_1rtt_keys000non_packet_lost000non_packet_sent000npath_challenge0nqlog_from_path0nread_handshake000nread_unorderednreceive_window00nreceived_reset00nremote_address000nremove_address0nsaturating_add000nsaturating_sub0nthroughput_bpsntime_threshold0ntotal_durationnvalue_variantsoConnectionClose000oConnectionError0oConnectionEvent0oConnectionStats0oGetRandomFailedoNewConnectionId0oOpenStreamStatsoPathCidsBlocked0oProtectedHeader0oQlogFileFactory0oRemoteAbandoned0oSignatureSchemeoSystemTimeErroroTokenReuseError0oTransportConfig00oTryFromIntErroroVersionMismatch0oZeroRttAcceptedoZeroRttRejected000obloom_token_logoclone_to_uninit00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000odefault_runtimeogrease_quic_bit0ointegrity_limit0ointerface_indexomax_stream_data0omax_streams_uni0oparse_byte_sizeorebind_abstractosend_unreliableoserver_endpointoset_path_statusoset_send_window0oshould_transmitostream_finishedothroughput_histowrap_udp_socket00owrite_handshake000A`AddressDiscoveryA`ApplicationClose000A`CertificateErrorA`ConnectionClosed0A`ConnectionHandleA`DatagramReceivedA`DecryptedInitial0A`EndpointStopping0A`FINAL_SIZE_ERROR0A`InconsistentKeys00A`KEY_UPDATE_ERROR0A`MaxPathIdReached0A`ObservedIpv4Addr0A`ObservedIpv6Addr0A`PathStatusBackup0A`PeerIncompatible00A`QuicClientConfig0A`QuicServerConfig0A`TokenMemoryCache0A`TooManyAddressesA`ValidationFailed00A`VersionNegotiate0A`check_and_insert00000A`configure_clientA`congestion_state0A`connection_close0A`force_key_update0A`from_arg_matches00000A`max_gso_segmentsA`max_idle_timeout0A`max_streams_bidi0A`n0_nat_traversal0A`open_connections0A`open_path_ensure0A`packet_threshold0A`read_many_chunksA`receive_datagramA`recv_buffer_size0A`send_buffer_size0A`transport_config0000A`validation_token0A`with_single_cert0A`write_all_chunksAaAPPLICATION_ERROR0AaApplicationClosed000AaControllerFactory0AaControllerMetrics0AaHandshakeTokenKey0AaInvalidServerName0AaNetworkChangeHint0AaPacketDecodeError0AaPathAbandonReason00AaSendDatagramError0AaStreamDataBlocked0AaStreamsBlockedUni0AaUnsupportedByPeer0Aacongestion_events0Aacongestion_window0Aamax_datagram_sizeAanew_connection_id0Aapath_cids_blocked0Aasend_buffer_spaceAaset_server_config0Aaset_up_connectionAato_possible_valueAawait_all_drainingAawrite_many_chunksAbAEAD_LIMIT_REACHED0AbAckFrequencyConfig0AbBadMaxFragmentSize0AbCONNECTION_REFUSED0AbConnectionIdParserAbDatagramsUnblockedAbFLOW_CONTROL_ERROR0AbHandshakeConfirmedAbHandshakeDataReadyAbIllegalOrderedReadAbInvalidCertificate0AbMtuDiscoveryConfig0AbNotEnoughAddressesAbPERF_CIPHER_SUITESAbPROTOCOL_VIOLATION0AbPrivatePkcs8KeyDerAbRetireConnectionId0AbSTREAM_LIMIT_ERROR0AbSTREAM_STATE_ERROR0AbServerCertVerifiedAbServerCertVerifierAbSetPathStatusError0AbStreamsBlockedBidi0AbTokioCurrentThreadAbTransportErrorCode0AbUnsupportedVersion00000Abcommand_for_update0000Abcrypto_buffer_size0Abfrom_u64_unchecked0Abignored_handshakesAbis_same_connectionAbmin_reset_interval0Abnew_expected_items0Abpath_status_backup0Abrefused_handshakesAbremote_path_statusAbsend_datagram_waitAbset_alpn_protocols000Abset_receive_window0Absupported_versions0Abtoken_memory_cacheAbverify_server_certAbwith_start_instant0AcCongestionAlgorithmAcNatTraversalUpdatesAcPathNewConnectionId0AcPathStatusAvailable0AcRemoteCidsExhausted0AcTransportParametersAcUnorderedRecvStreamAcUnsupportedNameType0AcWrongConnectionSideAcaccepted_handshakesAcarbitrary_take_rest000Acaugment_subcommandsAcblack_hole_cooldown0Acearly_data_accepted000Achandshake_confirmedAcis_path_recoverable0Ackeep_alive_interval0Aclost_plpmtud_probes0Acon_congestion_event0000000Acoutgoing_handshakesAcremote_open_streamsAcsend_buffer_benches00Acsend_data_on_streamAcsent_plpmtud_probes0Acstream_data_blocked0Acstreams_blocked_uni0AdFRAME_ENCODING_ERROR0AdHandshakeNotComplete0AdInappropriateMessage000AdInvalidRemoteAddress000AdNoInitialCipherSuite0AdObservedExternalAddrAdServerSideNotAllowed0AdSupportedCipherSuiteAdVarIntBoundsExceeded0AdVerifierBuilderErrorAdWeakConnectionHandleAdack_frequency_config0Adblack_holes_detected0Adfrom_arg_matches_mut00000Adhandle_client_streamAdincoming_buffer_size0Adis_multipath_enabledAdmax_receive_segments0Admax_udp_payload_size00Admtu_discovery_config0Adpoll_endpoint_eventsAdpreferred_address_v40Adpreferred_address_v60Adread_certs_from_fileAdreordering_threshold0Adretire_connection_id0Adretry_token_lifetime0Adset_max_idle_timeoutAdset_recv_buffer_sizeAdset_send_buffer_sizeAdstreams_blocked_bidi0Adtransport_parameters00000AeConnectionIdGenerator0AeDigitallySignedStructAeNoApplicationProtocol0AeNoDefaultClientConfig0AePATH_UNSTABLE_OR_POOR0AeValidationTokenConfig0Aeconfidentiality_limit0Aehandle_network_change0Aeincoming_buffer_bytesAeloss_reduction_factor0Aemax_transmit_segments0Aenat_traversal_updatesAepath_observed_addressAepath_status_available0Aestream_receive_window00AfCRYPTO_BUFFER_EXCEEDED0AfExtensionNotNegotiatedAfFailedToGetCurrentTime0AfFailedToGetRandomBytes0AfMultipathNotNegotiated000000AfPathRetireConnectionId0AfProtectedInitialHeaderAfSkipServerVerificationAfbuild_transport_configAfexport_keying_material0000Afget_into_many_segmentsAfget_loop_many_segmentsAfmax_concurrent_streamsAfobserved_external_addr0Afpath_new_connection_id0Afverify_tls12_signatureAfverify_tls13_signatureAfwith_root_certificates0AgCertRevocationListErrorAgHandshakeSignatureValidAgNoCertificatesPresented0AgPeerSentOversizedRecord0Agack_eliciting_threshold0Agaugment_args_for_update0000Aghas_pending_retransmitsAgis_multipath_negotiatedAgon_ack_frequency_update000Agset_keep_alive_intervalAgupdate_from_arg_matches00000Agvalidation_token_config0AhAPPLICATION_ABANDON_PATH0AhNoProtectionClientConfigAhNoProtectionServerConfigAhget_max_udp_payload_size0Ahinitial_dst_cid_provider0Ahnew_with_abstract_socketAhremote_address_validated0Ahset_max_concurrent_pathsAhsupported_verify_schemesAiCONNECTION_ID_LIMIT_ERROR0AiEncryptedClientHelloErrorAiExportKeyingMaterialError0AiInvalidCertRevocationList0AiNO_CID_AVAILABLE_FOR_PATH0AiTRANSPORT_PARAMETER_ERROR0AiUnsupportedOperationErrorAiadd_nat_traversal_address0Aidatagram_send_buffer_size0Aigenerate_self_signed_certAiopen_bidirectional_streamAipath_retire_connection_id0Aiset_default_client_configAiset_path_max_idle_timeoutAjDEFAULT_SUPPORTED_VERSIONSAjUnusableAfterNetworkChangeAjdatagram_send_buffer_spaceAjincoming_buffer_size_total0Ajmax_concurrent_uni_streams0Ajopen_unidirectional_streamAjserver_handshake_migration0Ajset_max_concurrent_streamsAjspurious_congestion_events0Ajtry_with_platform_verifier0AkHashedConnectionIdGeneratorAkInvalidEncryptedClientHello0AkPATH_RESOURCE_LIMIT_REACHED0AkRandomConnectionIdGeneratorAkenable_segmentation_offload0Akmax_concurrent_bidi_streams0Akupdate_from_arg_matches_mut00000Alconfigure_tracing_subscriberAldatagram_receive_buffer_size0Alinitiate_nat_traversal_round0Alon_spurious_congestion_event00000Alreceive_bidirectional_streamAlremove_nat_traversal_address0Alset_path_keep_alive_intervalAmFixedLengthConnectionIdParserAmInappropriateHandshakeMessage000Amcongestion_controller_factory0Amdefault_path_max_idle_timeout0Ammax_outgoing_bytes_per_second0Amnegotiated_key_exchange_group0Amreceive_unidirectional_streamAmsend_observed_address_reports0Amset_max_concurrent_bi_streamsAnaugment_subcommands_for_updateAnmax_concurrent_multipath_paths0Anset_max_concurrent_uni_streamsAopersistent_congestion_threshold0B`default_path_keep_alive_interval0B`receive_observed_address_reports0Baget_local_nat_traversal_addresses0Bbget_remote_nat_traversal_addresses0Bbmax_remote_nat_traversal_addresses00") \ No newline at end of file diff --git a/pr/451/docs/search.index/normalizedName/977a3ccfe9de.js b/pr/451/docs/search.index/normalizedName/977a3ccfe9de.js new file mode 100644 index 000000000..dc5d48aef --- /dev/null +++ b/pr/451/docs/search.index/normalizedName/977a3ccfe9de.js @@ -0,0 +1 @@ +rd_("b()b->bbi0bce00bfnbidbokb[]beq0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003333333333333333333brtbtybu8cack0caddcanycarccboxcbufcclicdir0cerrcmax000cnotcopt00cordcpincuni0cvec>>>>>>ccmp00000000000000000009999cecn000cf32cf64cfmt00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cget0ci32cimpcios0clen000clog0cmincmutcnew00000000000000000000000000000000000cnoqcnot0cnow000000crtt000crun0cstrcu16cu32cu64cudpdargsdasfddbbr300ddropdect000dect100dfromdhashdintodkeys0dlong0dnonedpath00dping0dpolldreaddside0dzero0dacks0daddr00dbbr3dbooldbulkdcodedcwnd0ddataddrop000000dfrom000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dhash000000000000000000000dinto000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dmain0000dnext0dopen00dpath0dperfdping000dpoll00000000000000dqlogdread00drecv00dseal0dsend00dsent0dside0dsign0dsize00dsmoldspindstop00dtake00000du128duniteasrefebytesechunk0ecloneecloseecubic00edebugederefeerror00000000000eevent00eknowneother0erangeereset000eretry0eshort0esizedestats0esuiteetokioewriteealpns0earrayebenchebuild00000000ebytes000ecauseeclone00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eclose000ecubicederef0eerroreframe0eindex0disce0elocal0eparse0epaths0eprint00ereset00eretry0esliceespawn00estats0000000000etitle0etokenetokioetupleeusizeewrite00facceptfackecn0faes128faes256fbackup0fborrowfbufmutfchunksfclient00fclosed0fcrypto0fcursorffuturefhasherfipaddrflaggedfopenbifopened0foptionfpathid0fresult00000fserver00fstream000fstringftypeidfvarint0faccept000fborrow000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fcipherfclient0000fclosedfcoding0fconfig0fcrypto0000000000fdecode000000000000edstipfencode000000000000ffinish00fhandlefheader0fignore0finsert00000fkeylogfoffset0eonack0000000fpacket00frandomfreason00000000frebindfrefuse0fremote000frustls00000fserver0000fsharedfsource00000000esrcip00fstatus00fstream0fstrideeudprx000eudptx000fvarintfverify0fwindow0000000gblocked00gcommandgcontextgdefaultgdisplaygfromstrggeneral0ghmackey0ginitialginstantgkeypair0glayeredgmaxdata0gnewreno00gopenunigpadding0gpathack0gpathbufgreadbufgrequestgruntime0gsession0gstopped00000gstreamsgtoownedgtoolonggtryfromgtryintogunknowngzerorttfcidlen000gclientsgcommand00000gcompare0000000000000000000gconnect0gdecrypt00000gdefault000000000000000000000000000000000000000000000000000gdrainedfdstcid0000000gencrypt000fis0rtt0gmetrics0000000fminmtu0fnoqudpfonsent0fopenbifpathidgpointergruntimefsrccid0000gstopped0gstreams000ftaglen0ftypeid000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gupgrade0gversion00000hacceptbihbytesmuthchacha20hcommandshdatagram00hderefmuthdisabled0hdurationhendpoint00hfinished0hincoming0hiteratorhlayerexthlongtypegmaxsize0gnoerror0hnewtoken0honclosedhopenpathhorderinghreadable0hrecvmetahresponsehstreamid0htimedout00htostringhtokenlog0htoolarge0htransmit00hudpstats0hunixtimehwritable0gcongalghcontentshdatagram0hdurationhendpoint0hfinalizegframerx000gframetx000gfrombuf0gfromenv0gfromkeygfromstr0gfromu320gfromu640ggottype000ggroupid0000ghas0rtthincominghinterval0gintoany0000000gisalivehlifetime0glocalip00000gmaxdata0gmaxsizegnewrenognoqperfgonbytesgopenunihpriority0hprotocol0gqlogdirhresponsegrngseed0hssthresh0gtoowned00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtryfrom000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtryinto000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtrypoll000000000gtrysendhvalidate000iabandoned000iacceptuniiaddassigniarbitraryiasyncread0iavailable000iborrowmuticommonopticonnectedicriterionidatagramsidecodableidiscarded000iencodableiformatterifourtuple0iframetype0ihandshakeiheaderkey0ihistogramimalformedimaxpathid0imultipathipacketkey0ipartialeqipatherror0ipathevent000ipathstats0ireaderror00itryfutureitrystreamiudpsenderiudpsocketivalueenumhacceptbiiarbitrary0000000hasrustlsiassemblerhclonebox0000000idatagrams000hderefmut0hfrombits00iinitiator0hinto0rtthintoiterhisclient0hisclosedhisserver0hmayretry0imigration0hnewtimer00hnewtoken0hnoqprotohonclosedhopenpath0hpathacks0hpingpathhpollnext000hpollread00hpollrecvhpollsendhreachout0ireferencehresetkey0hretrytag0000hsizehint000hstableidhtostring0000000000000000000000000000000000000000000000000000000000000000000htokenkey0htokenpositransport00hwaitidlehwritealljargmatchesjasynctimerjasyncwrite0ibatchsizejbbr3config0jclosedpath00000jcomparablejconnectingjconnection00jcontroller0jequivalent0jframestats0jintofuturejinvalidcid0jioslicemutjmaybeframe0jnonzerou32jothererrorjpartialordjpathackecn0jpatheventsjpathstatus0jqlogconfig0jrecvstream00jreseterrorjretryerror0jsendstream00jservernamejsocketaddrjstreaminfojsubcommandjsystemtimejtimesource0jtlssession0jtokenstore0judpsockrefjwriteerror0iacceptuniiaddassign00000iallowspin0iborrowmut000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ibytesread0icloneinto00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000iclosepathjcongestion000iconnstatsjconnection0jequivalent00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ierrorcode000000iframetype0iintoinner0iisdrained0ilocaladdr0ilostbytes000inewsenderhpadtomtu0ipathstats0000ipollcloseipollflush0ipollwrite00iprintjsonireadchunk0ireadexactisetstatusistarttime0jthroughputitotalsizekaccepterrorjclientaddrkciphersuitekclientstatskclosereasonkconfigerror0kcontenttypekcryptoerror0kcubicconfig0kdatablocked0kestablished000kfinisherrorkidletimeout0kintorequestkoutofbounds0kpathabandon0kqlogfactory0kresetstream0kruntimetypejserveraddrjservernameksmolruntimekstopsending0kstreamevent0kstreamstats0jacceptwithjaddaddress0jbindsocketkcertificatejcurrentmtu00kdescription0kdestination00jfromremote0jinitialmtu000jinitialrtt00jintofuture0000000000000jisdrainingimaxpathid0jmaxstreamsjnamedlayer0ionendacks000jonintervaljpacingrate0jpartialcmp0000000000000000000jpatheventsjpathstatusireadtoendjrecvstream0jsamplesize0jsendbufferjsendstream0jsendwindow00jservername0jtimesource0jtokenstore0juploadsizejupperbound0jweakhandle0jwithcrypto0jwithprefix0jwritechunklackfrequency0laddressaddedlclientconfig00000lclosedstream000000lconnecterror00lconnectionid00ldatagraminfoldecrypterror0lecncodepoint00lencrypterror0lillegalvaluelimmediateack0lintoiteratorklocalhostv4llastopenpath0lnattraversallnonzerousizelnonetokenlog0lobservedaddr000lpathresponse0lpathsblocked0lreaddatagramlremotestatus000lrttestimator0lsenddatagramlserverconfig00000lsocketaddrv4lsocketaddrv6lstoppederrorltokioruntimelunstructuredkaugmentargs0000kcidlifetime000kclosereasonkconnectwithlconservativekdatablocked0kdrainstreamkearlycrypto000keventstreamkexpecttypes000kgeneratecid000kgrosegmentskhandleevent0kinitialkeys00000000kintorequest0klostpackets000kmaxincoming0kmayfragment00knetworkpath0knewreceiverlnoprotection0jorigdstcid0kpathabandon0kpolltimeoutkqlogfactory0kresetstream0kruntimetypeksegmentsize00ksendquantum0ksendstreamsksetlifetime0ksetpriority0kstopsending0kstreamstatskuploadstatskwithinitial000kwritechunksmalertreceived0mbloomtokenlog0mcidsexhausted000mclonetouninitmdatagrameventmendpointeventmendpointstatsmfinishedearlymhandshakedata0mhandshakedone0mhandshaketypelinvalidtoken0mincomingalpns0minvalidheadermlocallyclosed0mmaxstreamdata0mmaxstreamsuni0mnewconnectionmnewrenoconfig0mparseinterrormpartialdecodempathchallenge0mpossiblevaluemprivatekeydermreadableerrormremoveaddress0mrootcertstoremstdsystemtime0munexpectedendlaccepted0rttlackfrequency00lbenchexports0lcidgenerator00lcreatesenderldatatransferldownloadsizeldurationhistlimmediateack0lintoincoming0kmaxackdelay0lnoprotectionlobservedaddr0konfirstbytekonmtuupdate0000000lpathresponse0lpathsblocked0lpeeridentity0000lpollshutdownlpolltransmitkqlogfromenv0lreaddatagramlsenddatagramlsendfairness0lsignaturelen0lstartsession000000000ktrypollnext0naddipv4address0naddipv6address0naddressremovednasyncudpsocketncertificatedernclosepatherror0ncommandfactorynconnectionlost000000nendpointconfig0nfromargmatchesminternalerror0nincomingfutureninvalidframeidninvalidmessage00nmaxstreamsbidi0lnoviablepath0nnonetokenstore0npeermisbehaved00nreachoutatipv40nreachoutatipv60nreadexacterrornreadtoenderrornshouldtransmitntransferresultntransporterror00nudpsocketstatenweakpathhandlemconnectclientmcryptosessionmdownloadstatsmforconnection000mhandletimeoutmhandshakedata00000mhandshakedone0mhassubcommandminitialwindow0000000000000mintounorderedmishandshaking0000lisvalidretry000mminimumchange0lnext1rttkeys000lonpacketlost000lonpacketsent000mpathchallenge0lqlogfrompath0mreadhandshake000mreadunorderedmreceivewindow00mreceivedreset00mremoteaddress000mremoveaddress0msaturatingadd000msaturatingsub0mthroughputbpsmtimethreshold0mtotaldurationmvaluevariantsoconnectionclose000oconnectionerror0oconnectionevent0oconnectionstats0ogetrandomfailedonewconnectionid0oopenstreamstatsopathcidsblocked0oprotectedheader0oqlogfilefactory0oremoteabandoned0osignatureschemeosystemtimeerrorotokenreuseerror0otransportconfig00otryfrominterroroversionmismatch0ozerorttacceptedozerorttrejected000mbloomtokenlogmclonetouninit00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ndefaultruntimemgreasequicbit0nintegritylimit0ninterfaceindexmmaxstreamdata0mmaxstreamsuni0mparsebytesizenrebindabstractnsendunreliablenserverendpointmsetpathstatusmsetsendwindow0nshouldtransmitnstreamfinishednthroughputhistmwrapudpsocket00nwritehandshake000A`addressdiscoveryA`applicationclose000A`certificateerrorA`connectionclosed0A`connectionhandleA`datagramreceivedA`decryptedinitial0A`endpointstopping0nfinalsizeerror0A`inconsistentkeys00nkeyupdateerror0A`maxpathidreached0A`observedipv4addr0A`observedipv6addr0A`pathstatusbackup0A`peerincompatible00A`quicclientconfig0A`quicserverconfig0A`tokenmemorycache0A`toomanyaddressesA`validationfailed00A`versionnegotiate0ncheckandinsert00000oconfigureclientocongestionstate0oconnectionclose0nforcekeyupdate0nfromargmatches00000nmaxgsosegmentsnmaxidletimeout0nmaxstreamsbidi0nn0nattraversal0oopenconnections0nopenpathensure0opacketthreshold0nreadmanychunksoreceivedatagramnrecvbuffersize0nsendbuffersize0otransportconfig0000ovalidationtoken0nwithsinglecert0nwriteallchunksA`applicationerror0Aaapplicationclosed000Aacontrollerfactory0Aacontrollermetrics0Aahandshaketokenkey0Aainvalidservername0Aanetworkchangehint0Aapacketdecodeerror0Aapathabandonreason00Aasenddatagramerror0Aastreamdatablocked0Aastreamsblockeduni0Aaunsupportedbypeer0A`congestionevents0A`congestionwindow0omaxdatagramsizeonewconnectionid0opathcidsblocked0osendbufferspaceosetserverconfig0osetupconnectionotopossiblevalueowaitalldrainingowritemanychunksA`aeadlimitreached0Abackfrequencyconfig0Abbadmaxfragmentsize0Aaconnectionrefused0AbconnectionidparserAbdatagramsunblockedA`flowcontrolerror0AbhandshakeconfirmedAbhandshakedatareadyAbillegalorderedreadAbinvalidcertificate0Abmtudiscoveryconfig0AbnotenoughaddressesA`perfciphersuitesAaprotocolviolation0Abprivatepkcs8keyderAbretireconnectionid0A`streamlimiterror0A`streamstateerror0AbservercertverifiedAbservercertverifierAbsetpathstatuserror0Abstreamsblockedbidi0AbtokiocurrentthreadAbtransporterrorcode0Abunsupportedversion00000A`commandforupdate0000A`cryptobuffersize0A`fromu64unchecked0AaignoredhandshakesA`issameconnectionA`minresetinterval0A`newexpecteditems0A`pathstatusbackup0AarefusedhandshakesA`remotepathstatusA`senddatagramwaitA`setalpnprotocols000A`setreceivewindow0Aasupportedversions0A`tokenmemorycacheA`verifyservercertA`withstartinstant0AccongestionalgorithmAcnattraversalupdatesAcpathnewconnectionid0Acpathstatusavailable0Acremotecidsexhausted0ActransportparametersAcunorderedrecvstreamAcunsupportednametype0AcwrongconnectionsideAbacceptedhandshakesAaarbitrarytakerest000AbaugmentsubcommandsAablackholecooldown0Aaearlydataaccepted000AbhandshakeconfirmedAaispathrecoverable0Aakeepaliveinterval0Aalostplpmtudprobes0Aaoncongestionevent0000000AboutgoinghandshakesAaremoteopenstreamsAasendbufferbenches00A`senddataonstreamAasentplpmtudprobes0Aastreamdatablocked0Aastreamsblockeduni0Abframeencodingerror0Adhandshakenotcomplete0Adinappropriatemessage000Adinvalidremoteaddress000Adnoinitialciphersuite0AdobservedexternaladdrAdserversidenotallowed0AdsupportedciphersuiteAdvarintboundsexceeded0AdverifierbuildererrorAdweakconnectionhandleAbackfrequencyconfig0Abblackholesdetected0Aafromargmatchesmut00000AbhandleclientstreamAbincomingbuffersize0AbismultipathenabledAbmaxreceivesegments0Aamaxudppayloadsize00Abmtudiscoveryconfig0AbpollendpointeventsAbpreferredaddressv40Abpreferredaddressv60AareadcertsfromfileAcreorderingthreshold0Abretireconnectionid0Abretrytokenlifetime0AasetmaxidletimeoutAasetrecvbuffersizeAasetsendbuffersizeAbstreamsblockedbidi0Actransportparameters00000Aeconnectionidgenerator0AedigitallysignedstructAenoapplicationprotocol0Aenodefaultclientconfig0Abpathunstableorpoor0Aevalidationtokenconfig0Adconfidentialitylimit0Achandlenetworkchange0AcincomingbufferbytesAclossreductionfactor0Acmaxtransmitsegments0AcnattraversalupdatesAcpathobservedaddressAcpathstatusavailable0Acstreamreceivewindow00Adcryptobufferexceeded0AfextensionnotnegotiatedAffailedtogetcurrenttime0Affailedtogetrandombytes0Afmultipathnotnegotiated000000Afpathretireconnectionid0AfprotectedinitialheaderAfskipserververificationAdbuildtransportconfigAdexportkeyingmaterial0000AcgetintomanysegmentsAcgetloopmanysegmentsAdmaxconcurrentstreamsAdobservedexternaladdr0Acpathnewconnectionid0Adverifytls12signatureAdverifytls13signatureAdwithrootcertificates0AgcertrevocationlisterrorAghandshakesignaturevalidAgnocertificatespresented0Agpeersentoversizedrecord0Aeackelicitingthreshold0Adaugmentargsforupdate0000AehaspendingretransmitsAeismultipathnegotiatedAdonackfrequencyupdate000AdsetkeepaliveintervalAdupdatefromargmatches00000Aevalidationtokenconfig0Afapplicationabandonpath0AhnoprotectionclientconfigAhnoprotectionserverconfigAdgetmaxudppayloadsize0Aeinitialdstcidprovider0AenewwithabstractsocketAfremoteaddressvalidated0AesetmaxconcurrentpathsAfsupportedverifyschemesAfconnectionidlimiterror0AiencryptedclienthelloerrorAiexportkeyingmaterialerror0Aiinvalidcertrevocationlist0Aenocidavailableforpath0Agtransportparametererror0AiunsupportedoperationerrorAfaddnattraversaladdress0Afdatagramsendbuffersize0AfgenerateselfsignedcertAgopenbidirectionalstreamAfpathretireconnectionid0AfsetdefaultclientconfigAesetpathmaxidletimeoutAhdefaultsupportedversionsAjunusableafternetworkchangeAgdatagramsendbufferspaceAgincomingbuffersizetotal0Agmaxconcurrentunistreams0AhopenunidirectionalstreamAhserverhandshakemigration0AgsetmaxconcurrentstreamsAhspuriouscongestionevents0Agtrywithplatformverifier0AkhashedconnectionidgeneratorAkinvalidencryptedclienthello0Ahpathresourcelimitreached0AkrandomconnectionidgeneratorAienablesegmentationoffload0Ahmaxconcurrentbidistreams0Agupdatefromargmatchesmut00000AjconfiguretracingsubscriberAidatagramreceivebuffersize0Aiinitiatenattraversalround0Aionspuriouscongestionevent00000AjreceivebidirectionalstreamAiremovenattraversaladdress0AhsetpathkeepaliveintervalAmfixedlengthconnectionidparserAminappropriatehandshakemessage000Akcongestioncontrollerfactory0Aidefaultpathmaxidletimeout0Aimaxoutgoingbytespersecond0Ajnegotiatedkeyexchangegroup0AkreceiveunidirectionalstreamAjsendobservedaddressreports0AisetmaxconcurrentbistreamsAkaugmentsubcommandsforupdateAkmaxconcurrentmultipathpaths0AjsetmaxconcurrentunistreamsAmpersistentcongestionthreshold0Aldefaultpathkeepaliveinterval0Amreceiveobservedaddressreports0Amgetlocalnattraversaladdresses0Angetremotenattraversaladdresses0Anmaxremotenattraversaladdresses00") \ No newline at end of file diff --git a/pr/451/docs/search.index/normalizedName/deb8ebf00d7a.js b/pr/451/docs/search.index/normalizedName/deb8ebf00d7a.js deleted file mode 100644 index 59974a316..000000000 --- a/pr/451/docs/search.index/normalizedName/deb8ebf00d7a.js +++ /dev/null @@ -1 +0,0 @@ -rd_("b()b->bbi0bce00bfnbidbokb[]beq000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003333333333333333333brtbtybu8cack0caddcanycarccboxcbufcclicdir0cerrcmax000cnotcopt00cordcpincuni0cvec>>>>>>ccmp00000000000000000009999cecn000cf32cf64cfmt000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cget0ci32cimpcios0clen000clog0cmincmutcnew0000000000000000000000000000000000cnoqcnot0cnow000000crtt000crun0cstrcu16cu32cu64cudpdargsdasfddbbr300ddropdect000dect100dfromdhashdintodkeys0dlong0dnonedpath00dping0dpolldreaddside0dzero0dacks0daddr00dbbr3dbooldbulkdcodedcwnd0ddataddrop000000dfrom000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dhash00000000000000000000dinto00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dmain0000dnext0dopen00dpath0dperfdping000dpoll00000000000000dqlogdread00drecv00dseal0dsend00dsent0dside0dsign0dsize00dsmoldspindstop00dtake00000du128duniteasrefebytesechunk0ecloneecloseecubic00edebugederefeerror00000000000eevent00eknowneother0erangeereset000eretry0eshort0esizedestats0esuiteetokioewriteealpns0earrayebenchebuild00000000ebytes000ecauseeclone0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eclose000ecubicederef0eerroreframe0eindex0disce0elocal0eparse0epaths0eprint00ereset00eretry0esliceespawn00estats0000000000etitle0etokenetokioetupleeusizeewrite00facceptfackecn0faes128faes256fbackup0fborrowfbufmutfchunksfclient00fclosed0fcrypto0fcursorffuturefhasherfipaddrflaggedfopenbifopened0foptionfpathid0fresult00000fserver00fstream000fstringftypeidfvarint0faccept000fborrow00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fcipherfclient0000fclosedfcoding0fconfig0fcrypto0000000000fdecode000000000000edstipfencode000000000000ffinish00fhandlefheader0fignore0finsert00000fkeylogfoffset0eonack0000000fpacket00frandomfreason00000000frebindfrefuse0fremote00frustls00000fserver0000fsharedfsource00000000esrcip00fstatus00fstream0fstrideeudprx000eudptx000fvarintfverify0fwindow0000000gblocked00gcommandgcontextgdefaultgdisplaygfromstrggeneral0ghmackey0ginitialginstantgkeypair0glayeredgmaxdata0gnewreno00gopenunigpadding0gpathack0gpathbufgreadbufgrequestgruntime0gsession0gstopped00000gstreamsgtoownedgtoolonggtryfromgtryintogunknowngzerorttfcidlen000gclientsgcommand00000gcompare0000000000000000000gconnect0gdecrypt00000gdefault000000000000000000000000000000000000000000000000000gdrainedfdstcid0000000gencrypt000fis0rtt0gmetrics0000000fminmtu0fnoqudpfonsent0fopenbifpathidgpointergruntimefsrccid0000gstopped0gstreams000ftaglen0ftypeid00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gupgrade0gversion00000hacceptbihbytesmuthchacha20hcommandshdatagram00hderefmuthdisabled0hdurationhendpoint00hfinished0hincoming0hiteratorhlayerexthlongtypegmaxsize0gnoerror0hnewtoken0honclosedhopenpathhorderinghreadable0hrecvmetahresponsehstreamid0htimedout00htostringhtokenlog0htoolarge0htransmit00hudpstats0hunixtimehwritable0gcongalghcontentshdatagram0hdurationhendpoint0hfinalizegframerx000gframetx000gfrombuf0gfromenv0gfromkeygfromstr0gfromu320gfromu640ggottype000ggroupid0000ghas0rtthincominghinterval0gintoany0000000gisalivehlifetime0glocalip0000gmaxdata0gmaxsizegnewrenognoqperfgonbytesgopenunihpriority0hprotocol0gqlogdirhresponsegrngseed0hssthresh0gtoowned0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtryfrom00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtryinto00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000gtrypoll000000000gtrysendhvalidate000iabandoned000iacceptuniiaddassigniarbitraryiasyncread0iavailable000iborrowmuticommonopticonnectedicriterionidatagramsidecodableidiscarded000iencodableiformatterifourtupleiframetype0ihandshakeiheaderkey0ihistogramimalformedimaxpathid0imultipathipacketkey0ipartialeqipatherror0ipathevent000ipathstats0ireaderror00itryfutureitrystreamiudpsenderiudpsocketivalueenumhacceptbiiarbitrary0000000hasrustlsiassemblerhclonebox0000000idatagrams000hderefmut0hfrombits00iinitiator0hinto0rtthintoiterhisclient0hisclosedhisserver0hmayretry0imigration0hnewtimer00hnewtoken0hnoqprotohonclosedhopenpath0hpathacks0hpingpathhpollnext000hpollread00hpollrecvhpollsendhreachout0ireferencehresetkey0hretrytag0000hsizehint000hstableidhtostring000000000000000000000000000000000000000000000000000000000000000000htokenkey0htokenpositransport00hwaitidlehwritealljargmatchesjasynctimerjasyncwrite0ibatchsizejbbr3config0jclosedpath00000jcomparablejconnectingjconnection00jcontroller0jequivalent0jframestats0jintofuturejinvalidcid0jioslicemutjmaybeframe0jnonzerou32jothererrorjpartialordjpathackecn0jpatheventsjpathstatus0jqlogconfig0jrecvstream00jreseterrorjretryerror0jsendstream00jservernamejsocketaddrjstreaminfojsubcommandjsystemtimejtimesource0jtlssession0jtokenstore0judpsockrefjwriteerror0iacceptuniiaddassign00000iallowspin0iborrowmut00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ibytesread0icloneinto0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000iclosepathjcongestion000iconnstatsjconnection0jequivalent00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ierrorcode000000iframetype0iintoinner0iisdrained0ilocaladdr0ilostbytes000inewsenderhpadtomtu0ipathstats0000ipollcloseipollflush0ipollwrite00iprintjsonireadchunk0ireadexactisetstatusistarttime0jthroughputitotalsizekaccepterrorjclientaddrkciphersuitekclientstatskclosereasonkconfigerror0kcontenttypekcryptoerror0kcubicconfig0kdatablocked0kestablished000kfinisherrorkidletimeout0kintorequestkoutofbounds0kpathabandon0kqlogfactory0kresetstream0kruntimetypejserveraddrjservernameksmolruntimekstopsending0kstreamevent0kstreamstats0jacceptwithjaddaddress0jbindsocketkcertificatejcurrentmtu00kdescription0kdestination00jfromremotejinitialmtu000jinitialrtt00jintofuture0000000000000imaxpathid0jmaxstreamsjnamedlayer0ionendacks000jonintervaljpacingrate0jpartialcmp0000000000000000000jpatheventsjpathstatusireadtoendjrecvstream0jsamplesize0jsendbufferjsendstream0jsendwindow00jservername0jtimesource0jtokenstore0juploadsizejupperbound0jweakhandle0jwithcrypto0jwithprefix0jwritechunklackfrequency0laddressaddedlclientconfig00000lclosedstream000000lconnecterror00lconnectionid00ldatagraminfoldecrypterror0lecncodepoint00lencrypterror0lillegalvaluelimmediateack0lintoiteratorklocalhostv4llastopenpath0lnattraversallnonzerousizelnonetokenlog0lobservedaddr000lpathresponse0lpathsblocked0lreaddatagramlremotestatus000lrttestimator0lsenddatagramlserverconfig00000lsocketaddrv4lsocketaddrv6lstoppederrorltokioruntimelunstructuredkaugmentargs0000kcidlifetime000kclosereasonkconnectwithlconservativekdatablocked0kdrainstreamkearlycrypto000keventstreamkexpecttypes000kgeneratecid000kgrosegmentskhandleevent0kinitialkeys00000000kintorequest0klostpackets000kmaxincoming0kmayfragment00knetworkpathknewreceiverlnoprotection0jorigdstcid0kpathabandon0kpolltimeoutkqlogfactory0kresetstream0kruntimetypeksegmentsize00ksendquantum0ksendstreamsksetlifetime0ksetpriority0kstopsending0kstreamstatskuploadstatskwithinitial000kwritechunksmalertreceived0mbloomtokenlog0mcidsexhausted000mclonetouninitmdatagrameventmendpointeventmendpointstatsmfinishedearlymhandshakedata0mhandshakedone0mhandshaketypelinvalidtoken0mincomingalpns0minvalidheadermlocallyclosed0mmaxstreamdata0mmaxstreamsuni0mnewconnectionmnewrenoconfig0mparseinterrormpartialdecodempathchallenge0mpossiblevaluemprivatekeydermreadableerrormremoveaddress0mrootcertstoremstdsystemtime0munexpectedendlaccepted0rttlackfrequency00lbenchexports0lcidgenerator00lcreatesenderldatatransferldownloadsizeldurationhistlimmediateack0lintoincoming0kmaxackdelay0lnoprotectionlobservedaddr0konfirstbytekonmtuupdate0000000lpathresponse0lpathsblocked0lpeeridentity0000lpollshutdownlpolltransmitkqlogfromenv0lreaddatagramlsenddatagramlsendfairness0lsignaturelen0lstartsession000000000ktrypollnext0naddipv4address0naddipv6address0naddressremovednasyncudpsocketncertificatedernclosepatherror0ncommandfactorynconnectionlost000000nendpointconfig0nfromargmatchesminternalerror0nincomingfutureninvalidframeidninvalidmessage00nmaxstreamsbidi0lnoviablepath0nnonetokenstore0npeermisbehaved00nreachoutatipv40nreachoutatipv60nreadexacterrornreadtoenderrornshouldtransmitntransferresultntransporterror00nudpsocketstatenweakpathhandlemconnectclientmcryptosessionmdownloadstatsmforconnection000mhandletimeoutmhandshakedata00000mhandshakedone0mhassubcommandminitialwindow0000000000000mintounorderedmishandshaking0000lisvalidretry000mminimumchange0lnext1rttkeys000lonpacketlost000lonpacketsent000mpathchallenge0lqlogfrompath0mreadhandshake000mreadunorderedmreceivewindow00mreceivedreset00mremoteaddress000mremoveaddress0msaturatingadd000msaturatingsub0mthroughputbpsmtimethreshold0mtotaldurationmvaluevariantsoconnectionclose000oconnectionerror0oconnectionevent0oconnectionstats0ogetrandomfailedonewconnectionid0oopenstreamstatsopathcidsblocked0oprotectedheader0oqlogfilefactory0oremoteabandoned0osignatureschemeosystemtimeerrorotokenreuseerror0otransportconfig00otryfrominterroroversionmismatch0ozerorttacceptedozerorttrejected000mbloomtokenlogmclonetouninit0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ndefaultruntimemgreasequicbit0nintegritylimit0ninterfaceindexmmaxstreamdata0mmaxstreamsuni0mparsebytesizenrebindabstractnsendunreliablenserverendpointmsetpathstatusmsetsendwindow0nshouldtransmitnstreamfinishednthroughputhistmwrapudpsocket00nwritehandshake000A`addressdiscoveryA`applicationclose000A`certificateerrorA`connectionclosed0A`connectionhandleA`datagramreceivedA`decryptedinitial0A`endpointstopping0nfinalsizeerror0A`inconsistentkeys00nkeyupdateerror0A`maxpathidreached0A`observedipv4addr0A`observedipv6addr0A`pathstatusbackup0A`peerincompatible00A`quicclientconfig0A`quicserverconfig0A`tokenmemorycache0A`toomanyaddressesA`validationfailed00A`versionnegotiate0ncheckandinsert00000oconfigureclientocongestionstate0oconnectionclose0nforcekeyupdate0nfromargmatches00000nmaxgsosegmentsnmaxidletimeout0nmaxstreamsbidi0nn0nattraversal0oopenconnections0nopenpathensure0opacketthreshold0nreadmanychunksoreceivedatagramnrecvbuffersize0nsendbuffersize0otransportconfig0000ovalidationtoken0nwithsinglecert0nwriteallchunksA`applicationerror0Aaapplicationclosed000Aacontrollerfactory0Aacontrollermetrics0Aahandshaketokenkey0Aainvalidservername0Aanetworkchangehint0Aapacketdecodeerror0Aapathabandonreason00Aasenddatagramerror0Aastreamdatablocked0Aastreamsblockeduni0Aaunsupportedbypeer0A`congestionevents0A`congestionwindow0omaxdatagramsizeonewconnectionid0opathcidsblocked0osendbufferspaceosetserverconfig0osetupconnectionotopossiblevalueowritemanychunksA`aeadlimitreached0Abackfrequencyconfig0Abbadmaxfragmentsize0Aaconnectionrefused0AbconnectionidparserAbdatagramsunblockedA`flowcontrolerror0AbhandshakeconfirmedAbhandshakedatareadyAbillegalorderedreadAbinvalidcertificate0Abmtudiscoveryconfig0AbnotenoughaddressesA`perfciphersuitesAaprotocolviolation0Abprivatepkcs8keyderAbretireconnectionid0A`streamlimiterror0A`streamstateerror0AbservercertverifiedAbservercertverifierAbsetpathstatuserror0Abstreamsblockedbidi0AbtokiocurrentthreadAbtransporterrorcode0Abunsupportedversion00000A`commandforupdate0000A`cryptobuffersize0A`fromu64unchecked0AaignoredhandshakesA`issameconnectionA`minresetinterval0A`newexpecteditems0A`pathstatusbackup0AarefusedhandshakesA`remotepathstatusA`senddatagramwaitA`setalpnprotocols000A`setreceivewindow0Aasupportedversions0A`tokenmemorycacheA`verifyservercertA`withstartinstant0AccongestionalgorithmAcnattraversalupdatesAcpathnewconnectionid0Acpathstatusavailable0Acremotecidsexhausted0ActransportparametersAcunorderedrecvstreamAcunsupportednametype0AcwrongconnectionsideAbacceptedhandshakesAaarbitrarytakerest000AbaugmentsubcommandsAablackholecooldown0Aaearlydataaccepted000AbhandshakeconfirmedAaispathrecoverable0Aakeepaliveinterval0Aalostplpmtudprobes0Aaoncongestionevent0000000AboutgoinghandshakesAaremoteopenstreamsAasendbufferbenches00A`senddataonstreamAasentplpmtudprobes0Aastreamdatablocked0Aastreamsblockeduni0Abframeencodingerror0Adhandshakenotcomplete0Adinappropriatemessage000Adinvalidremoteaddress000Adnoinitialciphersuite0AdobservedexternaladdrAdserversidenotallowed0AdsupportedciphersuiteAdvarintboundsexceeded0AdverifierbuildererrorAdweakconnectionhandleAbackfrequencyconfig0Abblackholesdetected0Aafromargmatchesmut00000AbhandleclientstreamAbincomingbuffersize0AbismultipathenabledAbmaxreceivesegments0Aamaxudppayloadsize00Abmtudiscoveryconfig0AbpollendpointeventsAbpreferredaddressv40Abpreferredaddressv60AareadcertsfromfileAcreorderingthreshold0Abretireconnectionid0Abretrytokenlifetime0AasetmaxidletimeoutAasetrecvbuffersizeAasetsendbuffersizeAbstreamsblockedbidi0Actransportparameters00000Aeconnectionidgenerator0AedigitallysignedstructAenoapplicationprotocol0Aenodefaultclientconfig0Abpathunstableorpoor0Aevalidationtokenconfig0Adconfidentialitylimit0Achandlenetworkchange0AcincomingbufferbytesAbisprobablysamepathAclossreductionfactor0Acmaxtransmitsegments0AcnattraversalupdatesAcpathobservedaddressAcpathstatusavailable0Acstreamreceivewindow00Adcryptobufferexceeded0AfextensionnotnegotiatedAffailedtogetcurrenttime0Affailedtogetrandombytes0Afmultipathnotnegotiated000000AfnattraversalroundendedAfpathretireconnectionid0AfprotectedinitialheaderAfskipserververificationAdbuildtransportconfigAdexportkeyingmaterial0000AcgetintomanysegmentsAcgetloopmanysegmentsAdmaxconcurrentstreamsAdobservedexternaladdr0Acpathnewconnectionid0Adverifytls12signatureAdverifytls13signatureAdwithrootcertificates0AgcertrevocationlisterrorAghandshakesignaturevalidAgnocertificatespresented0Agpeersentoversizedrecord0Aeackelicitingthreshold0Adaugmentargsforupdate0000AehaspendingretransmitsAeismultipathnegotiatedAdonackfrequencyupdate000AdsetkeepaliveintervalAdupdatefromargmatches00000Aevalidationtokenconfig0Afapplicationabandonpath0AhnoprotectionclientconfigAhnoprotectionserverconfigAdgetmaxudppayloadsize0Aeinitialdstcidprovider0AenewwithabstractsocketAfremoteaddressvalidated0AesetmaxconcurrentpathsAfsupportedverifyschemesAfconnectionidlimiterror0AiencryptedclienthelloerrorAiexportkeyingmaterialerror0Aiinvalidcertrevocationlist0Aenocidavailableforpath0Agtransportparametererror0AiunsupportedoperationerrorAfaddnattraversaladdress0Afdatagramsendbuffersize0AfgenerateselfsignedcertAgopenbidirectionalstreamAfpathretireconnectionid0AfsetdefaultclientconfigAesetpathmaxidletimeoutAhdefaultsupportedversionsAjunusableafternetworkchangeAgdatagramsendbufferspaceAgincomingbuffersizetotal0Agmaxconcurrentunistreams0AhopenunidirectionalstreamAgsetmaxconcurrentstreamsAhspuriouscongestionevents0Agtrywithplatformverifier0AkhashedconnectionidgeneratorAkinvalidencryptedclienthello0Ahpathresourcelimitreached0AkrandomconnectionidgeneratorAienablesegmentationoffload0Ahmaxconcurrentbidistreams0Agupdatefromargmatchesmut00000AgupdatelocalifsameremoteAjconfiguretracingsubscriberAidatagramreceivebuffersize0Aiinitiatenattraversalround0Aionspuriouscongestionevent00000AjreceivebidirectionalstreamAiremovenattraversaladdress0AhsetpathkeepaliveintervalAmfixedlengthconnectionidparserAminappropriatehandshakemessage000Akcongestioncontrollerfactory0Aidefaultpathmaxidletimeout0Aimaxoutgoingbytespersecond0Ajnegotiatedkeyexchangegroup0AkreceiveunidirectionalstreamAjsendobservedaddressreports0AisetmaxconcurrentbistreamsAkaugmentsubcommandsforupdateAkmaxconcurrentmultipathpaths0AjsetmaxconcurrentunistreamsAmpersistentcongestionthreshold0Aldefaultpathkeepaliveinterval0Amreceiveobservedaddressreports0Amgetlocalnattraversaladdresses0Angetremotenattraversaladdresses0Anmaxremotenattraversaladdresses00") \ No newline at end of file diff --git a/pr/451/docs/search.index/path/813227ed5be5.js b/pr/451/docs/search.index/path/813227ed5be5.js new file mode 100644 index 000000000..268bb3306 --- /dev/null +++ b/pr/451/docs/search.index/path/813227ed5be5.js @@ -0,0 +1 @@ +rd_("f[1,\"\"]0Al[10,\"core::ops\",\"core::ops\"]Ce[5,\"clap_builder::util::id\",\"clap_builder::util::id\"]22Ag[10,\"core::ops::arith\"]A`[10,\"core::any\"]Ao[5,\"alloc::sync\",\"alloc::sync\"]Ba[5,\"alloc::boxed\",\"alloc::boxed\"]Cb[10,\"bytes::buf::buf_impl\",\"bytes::buf::buf_impl\"]Ai[5,\"noq_perf\",\"noq_perf\"]i[6,\"noq\"]Ak[6,\"noq_proto\",\"noq_proto\"]Ae[10,\"core::ops::bit\"]Ac[5,\"bench\",\"bench\"]Ba[5,\"perf::client\",\"perf::client\"]Ba[5,\"perf::server\",\"perf::server\"]A`[10,\"core::cmp\"]Ak[5,\"core::pin\",\"core::pin\"]Am[5,\"alloc::vec\",\"alloc::vec\"]f[1,\"\"]00m[2,\"noq_udp\"]f[0,\"\"]f[3,\"\"]3333Ak[10,\"clap_builder::derive\"]Bn[10,\"std::os::fd::owned\",\"std::os::fd::owned\"]Ae[5,\"noq::congestion\"]Ci[5,\"noq_proto::congestion\",\"noq_proto::congestion::bbr3\"]Af[10,\"core::ops::drop\"]Ad[10,\"core::convert\"]Aa[10,\"core::hash\"]1Aa[5,\"noq::crypto\"]Bk[5,\"noq_proto::crypto\",\"noq_proto::crypto\"]Ba[15,\"noq_proto::ProtectedHeader\"]Ak[5,\"std::path\",\"std::path\"]Ae[5,\"noq\",\"noq::path\"]Bi[6,\"core::task::poll\",\"core::task::poll\"]i[6,\"noq\"]Ak[6,\"noq_proto\",\"noq_proto\"]Ak[2,\"noq_proto::congestion\"]f[1,\"\"]f[3,\"\"]i[2,\"noq\"]1Ag[2,\"noq_proto::config\"]Bd[2,\"noq_proto::connection::streams\"]0Ab[2,\"noq::runtime\"]55Bd[10,\"core::convert\",\"core::convert\"]Ba[5,\"bytes::bytes\",\"bytes::bytes\"]i[5,\"noq\"]Cb[5,\"noq_proto\",\"noq_proto::connection::assembler\"]Ab[10,\"core::clone\"]Bi[6,\"noq_proto::frame\",\"noq_proto::frame\"]Ae[5,\"noq::congestion\"]Cj[5,\"noq_proto::congestion\",\"noq_proto::congestion::cubic\"]A`[10,\"core::fmt\"]Ag[10,\"core::ops::deref\"]Ak[5,\"core::fmt\",\"core::fmt\"]Ab[10,\"core::error\"]Ai[2,\"noq::crypto::rustls\"]Ao[2,\"noq_proto::crypto::rustls\"]Cm[5,\"noq_proto::transport_error\",\"noq_proto::transport_error\"]Bc[6,\"rustls::error\",\"rustls::error\"]Ba[8,\"clap_builder\",\"clap_builder\"]Bi[6,\"arbitrary::error\",\"arbitrary::error\"]Ai[6,\"noq::crypto::rustls\"]Ao[6,\"noq_proto::crypto::rustls\"]Dg[6,\"noq_proto::transport_parameters\",\"noq_proto::transport_parameters\"]Co[6,\"noq_proto::n0_nat_traversal\",\"noq_proto::n0_nat_traversal\"]o[2,\"noq_proto\"]Bg[6,\"noq_proto\",\"noq_proto::connection\"]2Bi[5,\"core::ops::range\",\"core::ops::range\"]Ba[15,\"noq_proto::ProtectedHeader\"]0Bb[10,\"core::marker\",\"core::marker\"]Ba[5,\"bench::stats\",\"bench::stats\"]Ao[5,\"perf::stats\",\"perf::stats\"]Ba[5,\"rustls::quic\",\"rustls::quic\"]Ah[10,\"std::io\",\"std::io\"]f[1,\"\"]f[3,\"\"]Ak[2,\"noq_proto::congestion\"]l[2,\"rustls\"]Bi[5,\"bytes::bytes_mut\",\"bytes::bytes_mut\"]Ai[6,\"noq_perf\",\"noq_perf\"]Bb[5,\"noq_proto\",\"noq_proto::frame\"]Ag[10,\"core::ops::deref\"]Am[5,\"core::time\",\"core::time\"]Bc[5,\"noq::endpoint\",\"noq::endpoint\"]Ai[5,\"noq\",\"noq::endpoint\"]Be[5,\"noq_proto\",\"noq_proto::endpoint\"]Am[15,\"noq_proto::StreamEvent\"]Ai[5,\"noq\",\"noq::incoming\"]2Bc[10,\"core::iter::traits::iterator\"]An[10,\"tonic::service::layered\"]Bc[6,\"noq_proto\",\"noq_proto::packet\"]Ak[5,\"noq\",\"noq::connection\"]Ae[5,\"noq\",\"noq::path\"]Ak[6,\"core::cmp\",\"core::cmp\"]7Ag[5,\"noq_udp\",\"noq_udp\"]i[5,\"noq\"]Ak[5,\"noq_proto\",\"noq_proto\"]Ad[10,\"alloc::string\"]j[10,\"noq\"]Bc[10,\"noq_proto\",\"noq_proto::token\"]4354Bn[5,\"noq_proto\",\"noq_proto::connection::stats\"]Bi[5,\"rustls_pki_types\",\"rustls_pki_types\"]?i[2,\"noq\"]o[2,\"noq_proto\"]1Ak[2,\"noq_proto::congestion\"]f[3,\"\"]Ae[15,\"noq::PathEvent\"]Ak[15,\"noq_proto::PathEvent\"]Ak[5,\"noq\",\"noq::connection\"]Ag[10,\"core::ops::arith\"]A`[10,\"arbitrary\"]Al[10,\"tokio::io::async_read\"]Ai[10,\"futures_io::if_std\"]Am[15,\"noq_proto::StreamEvent\"]Ac[10,\"core::borrow\"]Aa[5,\"perf\",\"perf\"]Ak[5,\"criterion\",\"criterion\"]Cb[5,\"noq_proto\",\"noq_proto::connection::datagrams\"]Bl[10,\"noq_proto::coding\",\"noq_proto::coding\"]<;0Ak[5,\"core::fmt\",\"core::fmt\"]i[5,\"noq\"]Ak[5,\"noq_proto\",\"noq_proto\"]i[6,\"noq\"]Bb[6,\"noq_proto\",\"noq_proto::frame\"]Ab[10,\"noq::crypto\"]Bl[10,\"noq_proto::crypto\",\"noq_proto::crypto\"]Ba[5,\"hdrhistogram\",\"hdrhistogram\"]21A`[10,\"core::cmp\"]5Bg[6,\"noq_proto\",\"noq_proto::connection\"]i[2,\"noq\"]o[2,\"noq_proto\"]8Bn[6,\"noq_proto\",\"noq_proto::connection::paths\"];Bn[5,\"noq_proto\",\"noq_proto::connection::stats\"]Al[6,\"noq\",\"noq::recv_stream\"]Cf[6,\"noq_proto\",\"noq_proto::connection::streams::recv\"]Ak[10,\"futures_core::future\"]Ak[10,\"futures_core::stream\"]Ai[10,\"noq\",\"noq::runtime\"]Bc[5,\"std::net::udp\",\"std::net::udp\"]Ak[10,\"clap_builder::derive\"]Ak[2,\"noq_proto::connection\"]0f[3,\"\"]f[1,\"\"]Ag[2,\"noq_proto::config\"]Em[5,\"clap_builder::parser::matches::arg_matches\",\"clap_builder::parser::matches::arg_matches\"]7Am[10,\"tokio::io::async_write\"]Ai[10,\"futures_io::if_std\"]Ae[5,\"noq::congestion\"]Ci[5,\"noq_proto::congestion\",\"noq_proto::congestion::bbr3\"]i[5,\"noq\"]Bn[5,\"noq_proto\",\"noq_proto::connection::paths\"]Aa[10,\"equivalent\"]Ak[5,\"noq\",\"noq::connection\"]Bg[5,\"noq::connection\",\"noq::connection\"]1Bg[5,\"noq_proto\",\"noq_proto::connection\"]Af[10,\"noq::congestion\"]Cd[10,\"noq_proto::congestion\",\"noq_proto::congestion\"]A`[10,\"hashbrown\"]68Bn[5,\"noq_proto\",\"noq_proto::connection::stats\"]B`[10,\"core::future::into_future\"]:Bj[5,\"noq_proto\",\"noq_proto::cid_generator\"]Ag[5,\"std::io\",\"std::io\"]Bi[6,\"noq_proto::frame\",\"noq_proto::frame\"]Bb[6,\"noq_proto\",\"noq_proto::frame\"]Bm[8,\"core::num::nonzero\",\"core::num::nonzero\"]Cm[5,\"rustls::error::other_error\",\"rustls::error::other_error\"]A`[10,\"core::cmp\"]Am[5,\"noq\",\"noq::event_stream\"]i[6,\"noq\"]Bn[6,\"noq_proto\",\"noq_proto::connection::paths\"]i[5,\"noq\"]Bi[5,\"noq_proto\",\"noq_proto::config::qlog\"]Bi[5,\"noq::recv_stream\",\"noq::recv_stream\"]Al[5,\"noq\",\"noq::recv_stream\"]C`[5,\"noq_proto\",\"noq_proto::connection::streams\"]Al[6,\"noq\",\"noq::recv_stream\"]Ai[5,\"noq\",\"noq::incoming\"]Be[5,\"noq_proto\",\"noq_proto::endpoint\"]Bi[5,\"noq::send_stream\",\"noq::send_stream\"]Al[5,\"noq\",\"noq::send_stream\"]5Dc[6,\"rustls_pki_types::server_name\",\"rustls_pki_types::server_name\"]Ce[6,\"core::net::socket_addr\",\"core::net::socket_addr\"]Bb[5,\"noq_proto\",\"noq_proto::frame\"]Ak[10,\"clap_builder::derive\"]Ak[5,\"std::time\",\"std::time\"]j[10,\"noq\"]Bd[10,\"noq_proto\",\"noq_proto::config\"]Ai[5,\"noq::crypto::rustls\"]Ck[5,\"noq_proto::crypto::rustls\",\"noq_proto::crypto::rustls\"]3Bc[10,\"noq_proto\",\"noq_proto::token\"]Ag[5,\"noq_udp\",\"noq_udp\"]Al[6,\"noq\",\"noq::send_stream\"]Cf[6,\"noq_proto\",\"noq_proto::connection::streams::send\"]i[2,\"noq\"]o[2,\"noq_proto\"]10Be[5,\"noq_proto\",\"noq_proto::endpoint\"]Ac[6,\"bench\",\"bench\"]Aa[5,\"bulk\",\"bulk\"]Da[6,\"noq_proto::connection::state\",\"noq_proto::connection::state\"]i[6,\"noq\"]Bc[6,\"noq_proto\",\"noq_proto::config\"]Bc[6,\"rustls::enums\",\"rustls::enums\"]Aa[5,\"noq::crypto\"]Bk[5,\"noq_proto::crypto\",\"noq_proto::crypto\"]Ae[5,\"noq::congestion\"]Cj[5,\"noq_proto::congestion\",\"noq_proto::congestion::cubic\"]Ae[15,\"noq::PathEvent\"]Ak[15,\"noq_proto::PathEvent\"]?i[5,\"noq\"]Bn[5,\"noq_proto\",\"noq_proto::config::transport\"]Ae[10,\"tonic::request\"]j[10,\"noq\"]Bj[10,\"noq_proto\",\"noq_proto::config::qlog\"]Ac[6,\"bench\",\"bench\"]An[5,\"noq\",\"noq::runtime::smol\"]o[2,\"noq_proto\"]C`[6,\"noq_proto\",\"noq_proto::connection::streams\"]Ba[5,\"bench::stats\",\"bench::stats\"]Ao[5,\"perf::stats\",\"perf::stats\"]f[3,\"\"]i[2,\"noq\"]Ak[2,\"noq_proto::connection\"]1Bk[5,\"noq_proto::config\",\"noq_proto::config\"]Co[5,\"rustls::client::client_conn\",\"rustls::client::client_conn\"]?Ab[10,\"noq::crypto\"]Bc[5,\"noq_proto\",\"noq_proto::config\"]Bl[10,\"noq_proto::crypto\",\"noq_proto::crypto\"]i[5,\"noq\"]C`[5,\"noq_proto\",\"noq_proto::connection::streams\"]Bo[6,\"noq_proto::endpoint\",\"noq_proto::endpoint\"]i[6,\"noq\"]Be[6,\"noq_proto\",\"noq_proto::endpoint\"]Bk[5,\"noq_proto::shared\",\"noq_proto::shared\"]5Bc[5,\"noq_proto\",\"noq_proto::shared\"]Bb[5,\"noq_proto\",\"noq_proto::frame\"]4Bc[6,\"noq_proto\",\"noq_proto::shared\"]Ag[6,\"noq_udp\",\"noq_udp\"]Bb[10,\"core::iter::traits::collect\"]Bm[8,\"core::num::nonzero\",\"core::num::nonzero\"];Bb[5,\"noq_proto\",\"noq_proto::token\"]Ae[15,\"noq::PathEvent\"]Ak[15,\"noq_proto::PathEvent\"]Ak[5,\"noq\",\"noq::connection\"]21Da[5,\"noq_proto::connection::paths\",\"noq_proto::connection::paths\"]Bn[5,\"noq_proto\",\"noq_proto::connection::paths\"]2Bk[5,\"noq_proto::config\",\"noq_proto::config\"]Co[5,\"rustls::server::server_conn\",\"rustls::server::server_conn\"]i[5,\"noq\"]Ab[10,\"noq::crypto\"]Bc[5,\"noq_proto\",\"noq_proto::config\"]Bl[10,\"noq_proto::crypto\",\"noq_proto::crypto\"]Ce[5,\"core::net::socket_addr\",\"core::net::socket_addr\"]0Al[6,\"noq\",\"noq::send_stream\"]Ao[5,\"noq\",\"noq::runtime::tokio\"]Cg[5,\"arbitrary::unstructured\",\"arbitrary::unstructured\"]i[2,\"noq\"]j[2,\"perf\"]9Bl[5,\"noq_proto\",\"noq_proto::bloom_token_log\"]Ab[10,\"core::clone\"]Be[6,\"noq_proto\",\"noq_proto::endpoint\"]Bc[5,\"noq_proto\",\"noq_proto::shared\"]Ai[5,\"noq\",\"noq::endpoint\"]Ai[5,\"noq::crypto::rustls\"]Ck[5,\"noq_proto::crypto::rustls\",\"noq_proto::crypto::rustls\"]Bc[6,\"rustls::enums\",\"rustls::enums\"]Bo[5,\"noq_proto::endpoint\",\"noq_proto::endpoint\"]Be[5,\"noq_proto\",\"noq_proto::endpoint\"]Ae[5,\"noq::congestion\"]Cm[5,\"noq_proto::congestion\",\"noq_proto::congestion::new_reno\"]Bi[5,\"core::num::error\",\"core::num::error\"]Bc[5,\"noq_proto\",\"noq_proto::packet\"]Ec[5,\"clap_builder::builder::possible_value\",\"clap_builder::builder::possible_value\"]Bi[6,\"rustls_pki_types\",\"rustls_pki_types\"]Cf[6,\"noq_proto\",\"noq_proto::connection::streams::recv\"]Cg[5,\"rustls::webpki::anchors\",\"rustls::webpki::anchors\"]i[5,\"noq\"]Bc[5,\"noq_proto\",\"noq_proto::config\"]Bk[5,\"noq_proto::coding\",\"noq_proto::coding\"]o[2,\"noq_proto\"]0f[3,\"\"]Ai[10,\"noq\",\"noq::runtime\"]Bi[5,\"rustls_pki_types\",\"rustls_pki_types\"]i[6,\"noq\"]Bg[6,\"noq_proto\",\"noq_proto::connection\"]Ak[10,\"clap_builder::derive\"]Ag[15,\"noq_proto::Event\"]:91Ai[5,\"noq\",\"noq::incoming\"]Bb[5,\"noq_proto\",\"noq_proto::frame\"]Bc[6,\"rustls::error\",\"rustls::error\"]=Bb[5,\"noq_proto\",\"noq_proto::token\"]1Al[6,\"noq\",\"noq::recv_stream\"]0C`[5,\"noq_proto\",\"noq_proto::connection::streams\"]Ba[5,\"bench::stats\",\"bench::stats\"]o[5,\"noq_proto\"]Al[5,\"noq_udp\",\"noq_udp::imp\"]Ae[5,\"noq\",\"noq::path\"]i[5,\"noq\"]9>=Bc[5,\"noq_proto\",\"noq_proto::shared\"]1Bn[5,\"noq_proto\",\"noq_proto::connection::stats\"]Ba[5,\"rustls::rand\",\"rustls::rand\"]Ao[5,\"perf::stats\",\"perf::stats\"]o[2,\"noq_proto\"]Bc[6,\"noq_proto\",\"noq_proto::packet\"]6Bi[5,\"noq_proto\",\"noq_proto::config::qlog\"]Bc[15,\"noq_proto::PathAbandonReason\"]Bc[6,\"rustls::enums\",\"rustls::enums\"]Ak[5,\"std::time\",\"std::time\"]:Bb[5,\"noq_proto\",\"noq_proto::token\"]Da[5,\"noq_proto::config::transport\",\"noq_proto::config::transport\"]Bi[5,\"bytes::bytes_mut\",\"bytes::bytes_mut\"]Ai[6,\"noq_perf\",\"noq_perf\"]Bb[5,\"noq_proto\",\"noq_proto::frame\"]Ag[10,\"core::ops::deref\"]Am[5,\"core::time\",\"core::time\"]Bc[5,\"noq::endpoint\",\"noq::endpoint\"]Ai[5,\"noq\",\"noq::endpoint\"]Be[5,\"noq_proto\",\"noq_proto::endpoint\"]Am[15,\"noq_proto::StreamEvent\"]Ai[5,\"noq\",\"noq::incoming\"]2Bc[10,\"core::iter::traits::iterator\"]An[10,\"tonic::service::layered\"]Bc[6,\"noq_proto\",\"noq_proto::packet\"]Ak[5,\"noq\",\"noq::connection\"]Ae[5,\"noq\",\"noq::path\"]Ak[6,\"core::cmp\",\"core::cmp\"]7Ag[5,\"noq_udp\",\"noq_udp\"]i[5,\"noq\"]Ak[5,\"noq_proto\",\"noq_proto\"]Ad[10,\"alloc::string\"]j[10,\"noq\"]Bc[10,\"noq_proto\",\"noq_proto::token\"]4354Bn[5,\"noq_proto\",\"noq_proto::connection::stats\"]Bi[5,\"rustls_pki_types\",\"rustls_pki_types\"]?i[2,\"noq\"]o[2,\"noq_proto\"]1Ak[2,\"noq_proto::congestion\"]f[3,\"\"]Ae[15,\"noq::PathEvent\"]Ak[15,\"noq_proto::PathEvent\"]Ak[5,\"noq\",\"noq::connection\"]Ag[10,\"core::ops::arith\"]A`[10,\"arbitrary\"]Al[10,\"tokio::io::async_read\"]Ai[10,\"futures_io::if_std\"]Am[15,\"noq_proto::StreamEvent\"]Ac[10,\"core::borrow\"]Aa[5,\"perf\",\"perf\"]Ak[5,\"criterion\",\"criterion\"]Cb[5,\"noq_proto\",\"noq_proto::connection::datagrams\"]Bl[10,\"noq_proto::coding\",\"noq_proto::coding\"]<;0Ak[5,\"core::fmt\",\"core::fmt\"]Ak[5,\"noq_proto\",\"noq_proto\"]i[6,\"noq\"]Bb[6,\"noq_proto\",\"noq_proto::frame\"]Ab[10,\"noq::crypto\"]Bl[10,\"noq_proto::crypto\",\"noq_proto::crypto\"]Ba[5,\"hdrhistogram\",\"hdrhistogram\"]21A`[10,\"core::cmp\"]5Bg[6,\"noq_proto\",\"noq_proto::connection\"]i[2,\"noq\"]o[2,\"noq_proto\"]8Bn[6,\"noq_proto\",\"noq_proto::connection::paths\"]i[5,\"noq\"]Bn[5,\"noq_proto\",\"noq_proto::connection::stats\"]Al[6,\"noq\",\"noq::recv_stream\"]Cf[6,\"noq_proto\",\"noq_proto::connection::streams::recv\"]Ak[10,\"futures_core::future\"]Ak[10,\"futures_core::stream\"]Ai[10,\"noq\",\"noq::runtime\"]Bc[5,\"std::net::udp\",\"std::net::udp\"]Ak[10,\"clap_builder::derive\"]Ak[2,\"noq_proto::connection\"]0f[3,\"\"]f[1,\"\"]Ag[2,\"noq_proto::config\"]Em[5,\"clap_builder::parser::matches::arg_matches\",\"clap_builder::parser::matches::arg_matches\"]7Am[10,\"tokio::io::async_write\"]Ai[10,\"futures_io::if_std\"]Ae[5,\"noq::congestion\"]Ci[5,\"noq_proto::congestion\",\"noq_proto::congestion::bbr3\"]i[5,\"noq\"]Bn[5,\"noq_proto\",\"noq_proto::connection::paths\"]Aa[10,\"equivalent\"]Ak[5,\"noq\",\"noq::connection\"]Bg[5,\"noq::connection\",\"noq::connection\"]1Bg[5,\"noq_proto\",\"noq_proto::connection\"]Af[10,\"noq::congestion\"]Cd[10,\"noq_proto::congestion\",\"noq_proto::congestion\"]A`[10,\"hashbrown\"]68Bn[5,\"noq_proto\",\"noq_proto::connection::stats\"]B`[10,\"core::future::into_future\"]:Bj[5,\"noq_proto\",\"noq_proto::cid_generator\"]Ag[5,\"std::io\",\"std::io\"]Bi[6,\"noq_proto::frame\",\"noq_proto::frame\"]Bb[6,\"noq_proto\",\"noq_proto::frame\"]Bm[8,\"core::num::nonzero\",\"core::num::nonzero\"]Cm[5,\"rustls::error::other_error\",\"rustls::error::other_error\"]A`[10,\"core::cmp\"]Am[5,\"noq\",\"noq::event_stream\"]i[6,\"noq\"]Bn[6,\"noq_proto\",\"noq_proto::connection::paths\"]i[5,\"noq\"]Bi[5,\"noq_proto\",\"noq_proto::config::qlog\"]Bi[5,\"noq::recv_stream\",\"noq::recv_stream\"]Al[5,\"noq\",\"noq::recv_stream\"]C`[5,\"noq_proto\",\"noq_proto::connection::streams\"]Al[6,\"noq\",\"noq::recv_stream\"]Ai[5,\"noq\",\"noq::incoming\"]Be[5,\"noq_proto\",\"noq_proto::endpoint\"]Bi[5,\"noq::send_stream\",\"noq::send_stream\"]Al[5,\"noq\",\"noq::send_stream\"]5Dc[6,\"rustls_pki_types::server_name\",\"rustls_pki_types::server_name\"]Ce[6,\"core::net::socket_addr\",\"core::net::socket_addr\"]Bb[5,\"noq_proto\",\"noq_proto::frame\"]Ak[10,\"clap_builder::derive\"]Ak[5,\"std::time\",\"std::time\"]j[10,\"noq\"]Bd[10,\"noq_proto\",\"noq_proto::config\"]Ai[5,\"noq::crypto::rustls\"]Ck[5,\"noq_proto::crypto::rustls\",\"noq_proto::crypto::rustls\"]3Bc[10,\"noq_proto\",\"noq_proto::token\"]Ag[5,\"noq_udp\",\"noq_udp\"]Al[6,\"noq\",\"noq::send_stream\"]Cf[6,\"noq_proto\",\"noq_proto::connection::streams::send\"]i[2,\"noq\"]o[2,\"noq_proto\"]10Be[5,\"noq_proto\",\"noq_proto::endpoint\"]Ac[6,\"bench\",\"bench\"]Aa[5,\"bulk\",\"bulk\"]Da[6,\"noq_proto::connection::state\",\"noq_proto::connection::state\"]i[6,\"noq\"]Bc[6,\"noq_proto\",\"noq_proto::config\"]Bc[6,\"rustls::enums\",\"rustls::enums\"]Aa[5,\"noq::crypto\"]Bk[5,\"noq_proto::crypto\",\"noq_proto::crypto\"]Ae[5,\"noq::congestion\"]Cj[5,\"noq_proto::congestion\",\"noq_proto::congestion::cubic\"]Ae[15,\"noq::PathEvent\"]Ak[15,\"noq_proto::PathEvent\"]?i[5,\"noq\"]Bn[5,\"noq_proto\",\"noq_proto::config::transport\"]Ae[10,\"tonic::request\"]j[10,\"noq\"]Bj[10,\"noq_proto\",\"noq_proto::config::qlog\"]Ac[6,\"bench\",\"bench\"]An[5,\"noq\",\"noq::runtime::smol\"]o[2,\"noq_proto\"]C`[6,\"noq_proto\",\"noq_proto::connection::streams\"]Ba[5,\"bench::stats\",\"bench::stats\"]Ao[5,\"perf::stats\",\"perf::stats\"]f[3,\"\"]i[2,\"noq\"]Ak[2,\"noq_proto::connection\"]1Co[5,\"rustls::client::client_conn\",\"rustls::client::client_conn\"]Bk[5,\"noq_proto::config\",\"noq_proto::config\"]?Ab[10,\"noq::crypto\"]Bc[5,\"noq_proto\",\"noq_proto::config\"]Bl[10,\"noq_proto::crypto\",\"noq_proto::crypto\"]i[5,\"noq\"]C`[5,\"noq_proto\",\"noq_proto::connection::streams\"]Bo[6,\"noq_proto::endpoint\",\"noq_proto::endpoint\"]i[6,\"noq\"]Be[6,\"noq_proto\",\"noq_proto::endpoint\"]Bk[5,\"noq_proto::shared\",\"noq_proto::shared\"]5Bc[5,\"noq_proto\",\"noq_proto::shared\"]Bb[5,\"noq_proto\",\"noq_proto::frame\"]4Bc[6,\"noq_proto\",\"noq_proto::shared\"]Ag[6,\"noq_udp\",\"noq_udp\"]Bb[10,\"core::iter::traits::collect\"]Bm[8,\"core::num::nonzero\",\"core::num::nonzero\"];Bb[5,\"noq_proto\",\"noq_proto::token\"]Ae[15,\"noq::PathEvent\"]Ak[15,\"noq_proto::PathEvent\"]Ak[5,\"noq\",\"noq::connection\"]21Da[5,\"noq_proto::connection::paths\",\"noq_proto::connection::paths\"]Bn[5,\"noq_proto\",\"noq_proto::connection::paths\"]2Co[5,\"rustls::server::server_conn\",\"rustls::server::server_conn\"]Bk[5,\"noq_proto::config\",\"noq_proto::config\"]i[5,\"noq\"]Ab[10,\"noq::crypto\"]Bc[5,\"noq_proto\",\"noq_proto::config\"]Bl[10,\"noq_proto::crypto\",\"noq_proto::crypto\"]Ce[5,\"core::net::socket_addr\",\"core::net::socket_addr\"]0Al[6,\"noq\",\"noq::send_stream\"]Ao[5,\"noq\",\"noq::runtime::tokio\"]Cg[5,\"arbitrary::unstructured\",\"arbitrary::unstructured\"]i[2,\"noq\"]j[2,\"perf\"]9Bl[5,\"noq_proto\",\"noq_proto::bloom_token_log\"]Ab[10,\"core::clone\"]Be[6,\"noq_proto\",\"noq_proto::endpoint\"]Bc[5,\"noq_proto\",\"noq_proto::shared\"]Ai[5,\"noq\",\"noq::endpoint\"]Ai[5,\"noq::crypto::rustls\"]Ck[5,\"noq_proto::crypto::rustls\",\"noq_proto::crypto::rustls\"]Bc[6,\"rustls::enums\",\"rustls::enums\"]Bo[5,\"noq_proto::endpoint\",\"noq_proto::endpoint\"]Be[5,\"noq_proto\",\"noq_proto::endpoint\"]Ae[5,\"noq::congestion\"]Cm[5,\"noq_proto::congestion\",\"noq_proto::congestion::new_reno\"]Bi[5,\"core::num::error\",\"core::num::error\"]Bc[5,\"noq_proto\",\"noq_proto::packet\"]Ec[5,\"clap_builder::builder::possible_value\",\"clap_builder::builder::possible_value\"]Bi[6,\"rustls_pki_types\",\"rustls_pki_types\"]Cf[6,\"noq_proto\",\"noq_proto::connection::streams::recv\"]Cg[5,\"rustls::webpki::anchors\",\"rustls::webpki::anchors\"]i[5,\"noq\"]Bc[5,\"noq_proto\",\"noq_proto::config\"]Bk[5,\"noq_proto::coding\",\"noq_proto::coding\"]o[2,\"noq_proto\"]0f[3,\"\"]Ai[10,\"noq\",\"noq::runtime\"]Bi[5,\"rustls_pki_types\",\"rustls_pki_types\"]i[6,\"noq\"]Bg[6,\"noq_proto\",\"noq_proto::connection\"]Ak[10,\"clap_builder::derive\"]Ag[15,\"noq_proto::Event\"]:91Ai[5,\"noq\",\"noq::incoming\"]Bb[5,\"noq_proto\",\"noq_proto::frame\"]Bc[6,\"rustls::error\",\"rustls::error\"]=Bb[5,\"noq_proto\",\"noq_proto::token\"]1Al[6,\"noq\",\"noq::recv_stream\"]0C`[5,\"noq_proto\",\"noq_proto::connection::streams\"]Ba[5,\"bench::stats\",\"bench::stats\"]o[5,\"noq_proto\"]Al[5,\"noq_udp\",\"noq_udp::imp\"]Ae[5,\"noq\",\"noq::path\"]i[5,\"noq\"]9>=Bc[5,\"noq_proto\",\"noq_proto::shared\"]1Bn[5,\"noq_proto\",\"noq_proto::connection::stats\"]Ba[5,\"rustls::rand\",\"rustls::rand\"]Ao[5,\"perf::stats\",\"perf::stats\"]o[2,\"noq_proto\"]Bc[6,\"noq_proto\",\"noq_proto::packet\"]6Bi[5,\"noq_proto\",\"noq_proto::config::qlog\"]Bc[15,\"noq_proto::PathAbandonReason\"]Bc[6,\"rustls::enums\",\"rustls::enums\"]Ak[5,\"std::time\",\"std::time\"]:Bb[5,\"noq_proto\",\"noq_proto::token\"]Da[5,\"noq_proto::config::transport\",\"noq_proto::config::transport\"]Settings

All

Rustdoc settings

Back
\ No newline at end of file +Settings

All

Rustdoc settings

Back
\ No newline at end of file diff --git a/pr/451/docs/src/bulk/bulk.rs.html b/pr/451/docs/src/bulk/bulk.rs.html index 8a773e9b5..074ccbeae 100644 --- a/pr/451/docs/src/bulk/bulk.rs.html +++ b/pr/451/docs/src/bulk/bulk.rs.html @@ -162,7 +162,7 @@ 162 // to `Arc`ing them 163 connection.close(0u32.into(), b"Benchmark done"); 164 -165 endpoint.wait_idle().await; +165 endpoint.wait_all_draining().await; 166 167 if opt.stats { 168 println!("\nClient connection stats:\n{:#?}", connection.stats()); diff --git a/pr/451/docs/src/noq/connection.rs.html b/pr/451/docs/src/noq/connection.rs.html index a834619f0..ba06d5aa5 100644 --- a/pr/451/docs/src/noq/connection.rs.html +++ b/pr/451/docs/src/noq/connection.rs.html @@ -378,1513 +378,1534 @@ 378 } 379 } 380 -381 /// Opens a new path if no path exists yet for the remote address. +381 /// Opens a new path if no path exists yet for `network_path`. 382 /// -383 /// Otherwise behaves exactly as [`open_path`]. -384 /// -385 /// [`open_path`]: Self::open_path -386 pub fn open_path_ensure(&self, addr: SocketAddr, initial_status: PathStatus) -> OpenPath { -387 let mut state = self.0.lock_and_wake("open_path"); -388 -389 // If endpoint::State::ipv6 is true we want to keep all our IP addresses as IPv6. -390 // If not, we do not support IPv6. We can not access endpoint::State from here -391 // however, but either all our paths use an IPv6 address, or all our paths use an -392 // IPv4 address. So we can use that information. -393 let ipv6 = state -394 .inner -395 .paths() -396 .iter() -397 .filter_map(|id| { -398 state -399 .inner -400 .network_path(*id) -401 .map(|addrs| addrs.remote().is_ipv6()) -402 .ok() -403 }) -404 .next() -405 .unwrap_or_default(); -406 if addr.is_ipv6() && !ipv6 { -407 return OpenPath::rejected(PathError::InvalidRemoteAddress(addr)); -408 } -409 let addr = if ipv6 { -410 SocketAddr::V6(ensure_ipv6(addr)) -411 } else { -412 addr -413 }; -414 -415 let now = state.runtime.now(); -416 // TODO(matheus23): For now this means it's impossible to make use of short-circuiting path validation currently. -417 // However, changing that would mean changing the API. -418 let addrs = FourTuple::from_remote(addr); -419 let open_res = state.inner.open_path_ensure(addrs, initial_status, now); -420 match open_res { -421 Ok((path_id, existed)) if existed => { -422 let recv = state.open_path.get(&path_id).map(|tx| tx.subscribe()); -423 drop(state); -424 match recv { -425 Some(recv) => OpenPath::new(path_id, recv, self.0.clone()), -426 None => OpenPath::ready(path_id, self.0.clone()), -427 } -428 } -429 Ok((path_id, _)) => { -430 let (tx, rx) = watch::channel(Ok(())); -431 state.open_path.insert(path_id, tx); -432 drop(state); -433 OpenPath::new(path_id, rx, self.0.clone()) -434 } -435 Err(err) => OpenPath::rejected(err), -436 } -437 } -438 -439 /// Opens an additional path if the multipath extension is negotiated. -440 /// -441 /// The returned future completes once the path is either fully opened and ready to -442 /// carry application data, or if there was an error. -443 /// -444 /// Dropping the returned future does not cancel the opening of the path, the -445 /// [`PathEvent::Established`] event will still be emitted from [`Self::path_events`] if -446 /// the path opens. The [`PathId`] for the events can be extracted from -447 /// [`OpenPath::path_id`]. -448 /// -449 /// Failure to open a path can either occur immediately, before polling the returned -450 /// future, or at a later time. If the failure is immediate [`OpenPath::path_id`] will -451 /// return `None` and the future will be ready immediately. If the failure happens -452 /// later, a [`PathEvent`] will be emitted. -453 pub fn open_path(&self, addr: SocketAddr, initial_status: PathStatus) -> OpenPath { -454 let mut state = self.0.lock_and_wake("open_path"); -455 -456 // If endpoint::State::ipv6 is true we want to keep all our IP addresses as IPv6. -457 // If not, we do not support IPv6. We can not access endpoint::State from here -458 // however, but either all our paths use an IPv6 address, or all our paths use an -459 // IPv4 address. So we can use that information. -460 let ipv6 = state -461 .inner -462 .paths() -463 .iter() -464 .filter_map(|id| { -465 state -466 .inner -467 .network_path(*id) -468 .map(|addrs| addrs.remote().is_ipv6()) -469 .ok() -470 }) -471 .next() -472 .unwrap_or_default(); -473 if addr.is_ipv6() && !ipv6 { -474 return OpenPath::rejected(PathError::InvalidRemoteAddress(addr)); -475 } -476 let addr = if ipv6 { -477 SocketAddr::V6(ensure_ipv6(addr)) -478 } else { -479 addr -480 }; -481 -482 let (on_open_path_send, on_open_path_recv) = watch::channel(Ok(())); -483 let now = state.runtime.now(); -484 // TODO(matheus23): For now this means it's impossible to make use of short-circuiting path validation currently. -485 // However, changing that would mean changing the API. -486 let addrs = FourTuple::from_remote(addr); -487 let open_res = state.inner.open_path(addrs, initial_status, now); -488 match open_res { -489 Ok(path_id) => { -490 state.open_path.insert(path_id, on_open_path_send); -491 drop(state); -492 OpenPath::new(path_id, on_open_path_recv, self.0.clone()) -493 } -494 Err(err) => OpenPath::rejected(err), -495 } -496 } -497 -498 /// Returns the [`Path`] structure of an open path -499 pub fn path(&self, id: PathId) -> Option<Path> { -500 Path::new(&self.0, id) -501 } -502 -503 /// A stream of [`PathEvent`]s for all paths in this connection. -504 /// -505 /// The stream will yield a [`PathEvent`] whenever there is a change in the state of any path in this connection. -506 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. -507 /// -508 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating -509 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. -510 pub fn path_events(&self) -> crate::PathEvents { -511 crate::PathEvents::new( -512 self.0 -513 .lock_without_waking("path_events") -514 .path_events -515 .subscribe(), -516 ) -517 } -518 -519 /// A stream of NAT traversal updates for this connection. -520 /// -521 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. -522 /// -523 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating -524 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. -525 pub fn nat_traversal_updates(&self) -> crate::NatTraversalUpdates { -526 crate::NatTraversalUpdates::new( -527 self.0 -528 .lock_without_waking("nat_traversal_updates") -529 .nat_traversal_updates -530 .subscribe(), -531 ) -532 } -533 -534 /// Wait for the connection to be closed for any reason -535 /// -536 /// Despite the return type's name, closed connections are often not an error condition at the -537 /// application layer. Cases that might be routine include [`ConnectionError::LocallyClosed`] -538 /// and [`ConnectionError::ApplicationClosed`]. -539 pub async fn closed(&self) -> ConnectionError { -540 { -541 let conn = self.0.lock_without_waking("closed"); -542 if let Some(error) = conn.error.as_ref() { -543 return error.clone(); -544 } -545 // Construct the future while the lock is held to ensure we can't miss a wakeup if -546 // the `Notify` is signaled immediately after we release the lock. `await` it after -547 // the lock guard is out of scope. -548 self.0.shared.closed.notified() -549 } -550 .await; -551 self.0 -552 .lock_without_waking("closed") -553 .error -554 .as_ref() -555 .expect("closed without an error") -556 .clone() -557 } -558 -559 /// Wait for the connection to be closed without keeping a strong reference to the connection -560 /// -561 /// Returns a future that resolves, once the connection is closed, to a [`Closed`] struct -562 /// describing the close reason and final connection and per-path statistics. -563 /// -564 /// Calling [`Self::closed`] keeps the connection alive until it is either closed locally via [`Connection::close`] -565 /// or closed by the remote peer. This function instead does not keep the connection itself alive, -566 /// so if all *other* clones of the connection are dropped, the connection will be closed implicitly even -567 /// if there are futures returned from this function still being awaited. -568 pub fn on_closed(&self) -> OnClosed { -569 let (tx, rx) = oneshot::channel(); -570 let mut state = self.0.lock_without_waking("on_closed"); -571 if let Some(reason) = state.error.clone() { -572 // Connection already closed, send immediately -573 let _ = tx.send(Closed::new(&mut state, reason)); -574 } else { -575 state.on_closed.push(tx); -576 } -577 drop(state); -578 OnClosed { -579 conn: self.weak_handle(), -580 rx, -581 } -582 } -583 -584 /// Whether the connection is closed, and why. -585 /// -586 /// The close_reason is always set to `Some(ConnectionError)` when a socket is -587 /// closed; whether it was closed manually by calling [`Connection::close()`] or due to -588 /// an internal error (such as an idle timeout or the peer closing the -589 /// connection). +383 /// If `network_path` has no local IP set, then this will open a new path +384 /// if no path exists for this remote address, independent of the existing +385 /// path's local IP. If a local IP is set, it will match against the full +386 /// four-tuple of existing paths. +387 /// +388 /// Otherwise behaves exactly as [`open_path`]. +389 /// +390 /// [`open_path`]: Self::open_path +391 pub fn open_path_ensure( +392 &self, +393 network_path: impl Into<FourTuple>, +394 initial_status: PathStatus, +395 ) -> OpenPath { +396 let network_path = network_path.into(); +397 let mut state = self.0.lock_and_wake("open_path"); +398 +399 let network_path = match normalize_network_path(network_path, &state.inner) { +400 Ok(network_path) => network_path, +401 Err(err) => return OpenPath::rejected(err), +402 }; +403 +404 let now = state.runtime.now(); +405 let open_res = state +406 .inner +407 .open_path_ensure(network_path, initial_status, now); +408 match open_res { +409 Ok((path_id, existed)) if existed => { +410 let recv = state.open_path.get(&path_id).map(|tx| tx.subscribe()); +411 drop(state); +412 match recv { +413 Some(recv) => OpenPath::new(path_id, recv, self.0.clone()), +414 None => OpenPath::ready(path_id, self.0.clone()), +415 } +416 } +417 Ok((path_id, _)) => { +418 let (tx, rx) = watch::channel(Ok(())); +419 state.open_path.insert(path_id, tx); +420 drop(state); +421 OpenPath::new(path_id, rx, self.0.clone()) +422 } +423 Err(err) => OpenPath::rejected(err), +424 } +425 } +426 +427 /// Opens an additional path if the multipath extension is negotiated. +428 /// +429 /// This function takes a [`FourTuple`], which contains the remote address and an optional +430 /// local IP. If the local IP is set, the path will be opened with this source address, +431 /// and the endpoint must support sending from that IP address. You can also pass a +432 /// [`SocketAddr`] to only set the remote address and leave the local IP interface unspecified. +433 /// +434 /// The returned future completes once the path is either fully opened and ready to +435 /// carry application data, or if there was an error. +436 /// +437 /// Dropping the returned future does not cancel the opening of the path, the +438 /// [`PathEvent::Established`] event will still be emitted from [`Self::path_events`] if +439 /// the path opens. The [`PathId`] for the events can be extracted from +440 /// [`OpenPath::path_id`]. +441 /// +442 /// Failure to open a path can either occur immediately, before polling the returned +443 /// future, or at a later time. If the failure is immediate [`OpenPath::path_id`] will +444 /// return `None` and the future will be ready immediately. If the failure happens +445 /// later, a [`PathEvent`] will be emitted. +446 pub fn open_path( +447 &self, +448 network_path: impl Into<FourTuple>, +449 initial_status: PathStatus, +450 ) -> OpenPath { +451 let network_path = network_path.into(); +452 let mut state = self.0.lock_and_wake("open_path"); +453 +454 let network_path = match normalize_network_path(network_path, &state.inner) { +455 Ok(network_path) => network_path, +456 Err(err) => return OpenPath::rejected(err), +457 }; +458 +459 let (on_open_path_send, on_open_path_recv) = watch::channel(Ok(())); +460 let now = state.runtime.now(); +461 let open_res = state.inner.open_path(network_path, initial_status, now); +462 match open_res { +463 Ok(path_id) => { +464 state.open_path.insert(path_id, on_open_path_send); +465 drop(state); +466 OpenPath::new(path_id, on_open_path_recv, self.0.clone()) +467 } +468 Err(err) => OpenPath::rejected(err), +469 } +470 } +471 +472 /// Returns the [`Path`] structure of an open path +473 pub fn path(&self, id: PathId) -> Option<Path> { +474 Path::new(&self.0, id) +475 } +476 +477 /// A stream of [`PathEvent`]s for all paths in this connection. +478 /// +479 /// The stream will yield a [`PathEvent`] whenever there is a change in the state of any path in this connection. +480 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. +481 /// +482 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating +483 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. +484 pub fn path_events(&self) -> crate::PathEvents { +485 crate::PathEvents::new( +486 self.0 +487 .lock_without_waking("path_events") +488 .path_events +489 .subscribe(), +490 ) +491 } +492 +493 /// A stream of NAT traversal updates for this connection. +494 /// +495 /// The events need to be processed immediately, since there isn't an unbounded buffer for them. +496 /// +497 /// If processing of events lags behind too much, you will get an error of type [`crate::Lagged`] indicating +498 /// how many events were lost. The stream continues after a lag, delivering the oldest retained message next. +499 pub fn nat_traversal_updates(&self) -> crate::NatTraversalUpdates { +500 crate::NatTraversalUpdates::new( +501 self.0 +502 .lock_without_waking("nat_traversal_updates") +503 .nat_traversal_updates +504 .subscribe(), +505 ) +506 } +507 +508 /// Wait for the connection to be closed for any reason +509 /// +510 /// Despite the return type's name, closed connections are often not an error condition at the +511 /// application layer. Cases that might be routine include [`ConnectionError::LocallyClosed`] +512 /// and [`ConnectionError::ApplicationClosed`]. +513 pub async fn closed(&self) -> ConnectionError { +514 { +515 let conn = self.0.lock_without_waking("closed"); +516 if let Some(error) = conn.error.as_ref() { +517 return error.clone(); +518 } +519 // Construct the future while the lock is held to ensure we can't miss a wakeup if +520 // the `Notify` is signaled immediately after we release the lock. `await` it after +521 // the lock guard is out of scope. +522 self.0.shared.closed.notified() +523 } +524 .await; +525 self.0 +526 .lock_without_waking("closed") +527 .error +528 .as_ref() +529 .expect("closed without an error") +530 .clone() +531 } +532 +533 /// Wait for the connection to be closed without keeping a strong reference to the connection +534 /// +535 /// Returns a future that resolves, once the connection is closed, to a [`Closed`] struct +536 /// describing the close reason and final connection and per-path statistics. +537 /// +538 /// Calling [`Self::closed`] keeps the connection alive until it is either closed locally via [`Connection::close`] +539 /// or closed by the remote peer. This function instead does not keep the connection itself alive, +540 /// so if all *other* clones of the connection are dropped, the connection will be closed implicitly even +541 /// if there are futures returned from this function still being awaited. +542 pub fn on_closed(&self) -> OnClosed { +543 let (tx, rx) = oneshot::channel(); +544 let mut state = self.0.lock_without_waking("on_closed"); +545 if let Some(reason) = state.error.clone() { +546 // Connection already closed, send immediately +547 let _ = tx.send(Closed::new(&mut state, reason)); +548 } else { +549 state.on_closed.push(tx); +550 } +551 drop(state); +552 OnClosed { +553 conn: self.weak_handle(), +554 rx, +555 } +556 } +557 +558 /// Whether the connection is closed, and why. +559 /// +560 /// The close_reason is always set to `Some(ConnectionError)` when a socket is +561 /// closed; whether it was closed manually by calling [`Connection::close()`] or due to +562 /// an internal error (such as an idle timeout or the peer closing the +563 /// connection). +564 /// +565 /// Note: when the connection is closed, `connection.close_reason().is_some()` will always be true. +566 pub fn close_reason(&self) -> Option<ConnectionError> { +567 self.0.lock_without_waking("close_reason").error.clone() +568 } +569 +570 /// Close the connection immediately. +571 /// +572 /// Pending operations will fail immediately with [`ConnectionError::LocallyClosed`]. No +573 /// more data is sent to the peer and the peer may drop buffered data upon receiving +574 /// the CONNECTION_CLOSE frame. +575 /// +576 /// `error_code` and `reason` are not interpreted, and are provided directly to the peer. +577 /// +578 /// `reason` will be truncated to fit in a single packet with overhead; to improve odds that it +579 /// is preserved in full, it should be kept under 1KiB. +580 /// +581 /// # Gracefully closing a connection +582 /// +583 /// Only the peer last receiving application data can be certain that all data is +584 /// delivered. The only reliable action it can then take is to close the connection, +585 /// potentially with a custom error code. The delivery of the final CONNECTION_CLOSE +586 /// frame is very likely if both endpoints stay online long enough, and +587 /// [`Endpoint::wait_idle()`] can be used to provide sufficient time. Otherwise, the +588 /// remote peer will time out the connection, provided that the idle timeout is not +589 /// disabled. 590 /// -591 /// Note: when the connection is closed, `connection.close_reason().is_some()` will always be true. -592 pub fn close_reason(&self) -> Option<ConnectionError> { -593 self.0.lock_without_waking("close_reason").error.clone() -594 } -595 -596 /// Close the connection immediately. +591 /// The sending side can not guarantee all stream data is delivered to the remote +592 /// application. It only knows the data is delivered to the QUIC stack of the remote +593 /// endpoint. Once the local side sends a CONNECTION_CLOSE frame in response to calling +594 /// [`close()`] the remote endpoint may drop any data it received but is as yet +595 /// undelivered to the application, including data that was acknowledged as received to +596 /// the local endpoint. 597 /// -598 /// Pending operations will fail immediately with [`ConnectionError::LocallyClosed`]. No -599 /// more data is sent to the peer and the peer may drop buffered data upon receiving -600 /// the CONNECTION_CLOSE frame. -601 /// -602 /// `error_code` and `reason` are not interpreted, and are provided directly to the peer. -603 /// -604 /// `reason` will be truncated to fit in a single packet with overhead; to improve odds that it -605 /// is preserved in full, it should be kept under 1KiB. -606 /// -607 /// # Gracefully closing a connection -608 /// -609 /// Only the peer last receiving application data can be certain that all data is -610 /// delivered. The only reliable action it can then take is to close the connection, -611 /// potentially with a custom error code. The delivery of the final CONNECTION_CLOSE -612 /// frame is very likely if both endpoints stay online long enough, and -613 /// [`Endpoint::wait_idle()`] can be used to provide sufficient time. Otherwise, the -614 /// remote peer will time out the connection, provided that the idle timeout is not -615 /// disabled. -616 /// -617 /// The sending side can not guarantee all stream data is delivered to the remote -618 /// application. It only knows the data is delivered to the QUIC stack of the remote -619 /// endpoint. Once the local side sends a CONNECTION_CLOSE frame in response to calling -620 /// [`close()`] the remote endpoint may drop any data it received but is as yet -621 /// undelivered to the application, including data that was acknowledged as received to -622 /// the local endpoint. -623 /// -624 /// [`ConnectionError::LocallyClosed`]: crate::ConnectionError::LocallyClosed -625 /// [`Endpoint::wait_idle()`]: crate::Endpoint::wait_idle -626 /// [`close()`]: Connection::close -627 pub fn close(&self, error_code: VarInt, reason: &[u8]) { -628 let conn = &mut *self.0.lock_without_waking("close"); // conn.close self-wakes -629 conn.close(error_code, Bytes::copy_from_slice(reason), &self.0.shared); -630 } -631 -632 /// Wait for the handshake to be confirmed. -633 /// -634 /// As a server, who must be authenticated by clients, -635 /// this happens when the handshake completes -636 /// upon receiving a TLS Finished message from the client. -637 /// In return, the server send a HANDSHAKE_DONE frame. -638 /// -639 /// As a client, this happens when receiving a HANDSHAKE_DONE frame. -640 /// At this point, the server has either accepted our authentication, -641 /// or, if client authentication is not required, accepted our lack of authentication. -642 pub async fn handshake_confirmed(&self) -> Result<(), ConnectionError> { -643 { -644 let conn = self.0.lock_without_waking("handshake_confirmed"); -645 if let Some(error) = conn.error.as_ref() { -646 return Err(error.clone()); -647 } -648 if conn.handshake_confirmed { -649 return Ok(()); -650 } -651 // Construct the future while the lock is held to ensure we can't miss a wakeup if -652 // the `Notify` is signaled immediately after we release the lock. `await` it after -653 // the lock guard is out of scope. -654 self.0.shared.handshake_confirmed.notified() +598 /// [`ConnectionError::LocallyClosed`]: crate::ConnectionError::LocallyClosed +599 /// [`Endpoint::wait_idle()`]: crate::Endpoint::wait_idle +600 /// [`close()`]: Connection::close +601 pub fn close(&self, error_code: VarInt, reason: &[u8]) { +602 let conn = &mut *self.0.lock_without_waking("close"); // conn.close self-wakes +603 conn.close(error_code, Bytes::copy_from_slice(reason), &self.0.shared); +604 } +605 +606 /// Wait for the handshake to be confirmed. +607 /// +608 /// As a server, who must be authenticated by clients, +609 /// this happens when the handshake completes +610 /// upon receiving a TLS Finished message from the client. +611 /// In return, the server send a HANDSHAKE_DONE frame. +612 /// +613 /// As a client, this happens when receiving a HANDSHAKE_DONE frame. +614 /// At this point, the server has either accepted our authentication, +615 /// or, if client authentication is not required, accepted our lack of authentication. +616 pub async fn handshake_confirmed(&self) -> Result<(), ConnectionError> { +617 { +618 let conn = self.0.lock_without_waking("handshake_confirmed"); +619 if let Some(error) = conn.error.as_ref() { +620 return Err(error.clone()); +621 } +622 if conn.handshake_confirmed { +623 return Ok(()); +624 } +625 // Construct the future while the lock is held to ensure we can't miss a wakeup if +626 // the `Notify` is signaled immediately after we release the lock. `await` it after +627 // the lock guard is out of scope. +628 self.0.shared.handshake_confirmed.notified() +629 } +630 .await; +631 if let Some(error) = self +632 .0 +633 .lock_without_waking("handshake_confirmed") +634 .error +635 .as_ref() +636 { +637 Err(error.clone()) +638 } else { +639 Ok(()) +640 } +641 } +642 +643 /// Transmit `data` as an unreliable, unordered application datagram +644 /// +645 /// Application datagrams are a low-level primitive. They may be lost or delivered out of order, +646 /// and `data` must both fit inside a single QUIC packet and be smaller than the maximum +647 /// dictated by the peer. +648 /// +649 /// Previously queued datagrams which are still unsent may be discarded to make space for this +650 /// datagram, in order of oldest to newest. +651 pub fn send_datagram(&self, data: Bytes) -> Result<(), SendDatagramError> { +652 let conn = &mut *self.0.lock_and_wake("send_datagram"); +653 if let Some(ref x) = conn.error { +654 return Err(SendDatagramError::ConnectionLost(x.clone())); 655 } -656 .await; -657 if let Some(error) = self -658 .0 -659 .lock_without_waking("handshake_confirmed") -660 .error -661 .as_ref() -662 { -663 Err(error.clone()) -664 } else { -665 Ok(()) -666 } -667 } -668 -669 /// Transmit `data` as an unreliable, unordered application datagram -670 /// -671 /// Application datagrams are a low-level primitive. They may be lost or delivered out of order, -672 /// and `data` must both fit inside a single QUIC packet and be smaller than the maximum -673 /// dictated by the peer. +656 use proto::SendDatagramError::*; +657 match conn.inner.datagrams().send(data, true) { +658 Ok(()) => Ok(()), +659 Err(e) => Err(match e { +660 Blocked(..) => unreachable!(), +661 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, +662 Disabled => SendDatagramError::Disabled, +663 TooLarge => SendDatagramError::TooLarge, +664 }), +665 } +666 } +667 +668 /// Transmit `data` as an unreliable, unordered application datagram +669 /// +670 /// Unlike [`send_datagram()`], this method will wait for buffer space during congestion +671 /// conditions, which effectively prioritizes old datagrams over new datagrams. +672 /// +673 /// See [`send_datagram()`] for details. 674 /// -675 /// Previously queued datagrams which are still unsent may be discarded to make space for this -676 /// datagram, in order of oldest to newest. -677 pub fn send_datagram(&self, data: Bytes) -> Result<(), SendDatagramError> { -678 let conn = &mut *self.0.lock_and_wake("send_datagram"); -679 if let Some(ref x) = conn.error { -680 return Err(SendDatagramError::ConnectionLost(x.clone())); +675 /// [`send_datagram()`]: Connection::send_datagram +676 pub fn send_datagram_wait(&self, data: Bytes) -> SendDatagram<'_> { +677 SendDatagram { +678 conn: &self.0, +679 data: Some(data), +680 notify: self.0.shared.datagrams_unblocked.notified(), 681 } -682 use proto::SendDatagramError::*; -683 match conn.inner.datagrams().send(data, true) { -684 Ok(()) => Ok(()), -685 Err(e) => Err(match e { -686 Blocked(..) => unreachable!(), -687 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, -688 Disabled => SendDatagramError::Disabled, -689 TooLarge => SendDatagramError::TooLarge, -690 }), -691 } -692 } -693 -694 /// Transmit `data` as an unreliable, unordered application datagram -695 /// -696 /// Unlike [`send_datagram()`], this method will wait for buffer space during congestion -697 /// conditions, which effectively prioritizes old datagrams over new datagrams. -698 /// -699 /// See [`send_datagram()`] for details. -700 /// -701 /// [`send_datagram()`]: Connection::send_datagram -702 pub fn send_datagram_wait(&self, data: Bytes) -> SendDatagram<'_> { -703 SendDatagram { -704 conn: &self.0, -705 data: Some(data), -706 notify: self.0.shared.datagrams_unblocked.notified(), -707 } -708 } -709 -710 /// Compute the maximum size of datagrams that may be passed to [`send_datagram()`]. -711 /// -712 /// Returns `None` if datagrams are unsupported by the peer or disabled locally. -713 /// -714 /// This may change over the lifetime of a connection according to variation in the path MTU -715 /// estimate. The peer can also enforce an arbitrarily small fixed limit, but if the peer's -716 /// limit is large this is guaranteed to be a little over a kilobyte at minimum. -717 /// -718 /// Not necessarily the maximum size of received datagrams. -719 /// -720 /// [`send_datagram()`]: Connection::send_datagram -721 pub fn max_datagram_size(&self) -> Option<usize> { -722 self.0 -723 .lock_without_waking("max_datagram_size") -724 .inner -725 .datagrams() -726 .max_size() -727 } -728 -729 /// Bytes available in the outgoing datagram buffer -730 /// -731 /// When greater than zero, calling [`send_datagram()`](Self::send_datagram) with a datagram of -732 /// at most this size is guaranteed not to cause older datagrams to be dropped. -733 pub fn datagram_send_buffer_space(&self) -> usize { -734 self.0 -735 .lock_without_waking("datagram_send_buffer_space") -736 .inner -737 .datagrams() -738 .send_buffer_space() -739 } -740 -741 /// The side of the connection (client or server) -742 pub fn side(&self) -> Side { -743 self.0.lock_without_waking("side").inner.side() -744 } -745 -746 /// Current best estimate of this connection's latency (round-trip-time) -747 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { -748 self.0.lock_without_waking("rtt").inner.rtt(path_id) -749 } -750 -751 /// Returns connection statistics -752 pub fn stats(&self) -> ConnectionStats { -753 self.0.lock_without_waking("stats").inner.stats() -754 } -755 -756 /// Returns path statistics -757 pub fn path_stats(&self, path_id: PathId) -> Option<PathStats> { -758 self.0.lock_without_waking("path_stats").path_stats(path_id) -759 } -760 -761 /// Current state of the congestion control algorithm, for debugging purposes -762 pub fn congestion_state(&self, path_id: PathId) -> Option<Box<dyn Controller>> { -763 self.0 -764 .lock_without_waking("congestion_state") -765 .inner -766 .congestion_state(path_id) -767 .map(|c| c.clone_box()) -768 } -769 -770 /// Parameters negotiated during the handshake -771 /// -772 /// Guaranteed to return `Some` on fully established connections or after -773 /// [`Connecting::handshake_data()`] succeeds. See that method's documentations for details on -774 /// the returned value. -775 /// -776 /// [`Connection::handshake_data()`]: crate::Connecting::handshake_data -777 pub fn handshake_data(&self) -> Option<Box<dyn Any>> { -778 self.0 -779 .lock_without_waking("handshake_data") -780 .inner -781 .crypto_session() -782 .handshake_data() -783 } -784 -785 /// Cryptographic identity of the peer -786 /// -787 /// The dynamic type returned is determined by the configured -788 /// [`Session`](proto::crypto::Session). For the default `rustls` session, the return value can -789 /// be [`downcast`](Box::downcast) to a <code>Vec<[rustls::pki_types::CertificateDer]></code> -790 pub fn peer_identity(&self) -> Option<Box<dyn Any>> { -791 self.0 -792 .lock_without_waking("peer_identity") -793 .inner -794 .crypto_session() -795 .peer_identity() -796 } -797 -798 /// A stable identifier for this connection -799 /// -800 /// Peer addresses and connection IDs can change, but this value will remain -801 /// fixed for the lifetime of the connection. -802 pub fn stable_id(&self) -> usize { -803 self.0.stable_id() -804 } -805 -806 /// Update traffic keys spontaneously -807 /// -808 /// This primarily exists for testing purposes. -809 pub fn force_key_update(&self) { -810 self.0 -811 .lock_and_wake("force_key_update") -812 .inner -813 .force_key_update() -814 } -815 -816 /// Derive keying material from this connection's TLS session secrets. -817 /// -818 /// When both peers call this method with the same `label` and `context` -819 /// arguments and `output` buffers of equal length, they will get the -820 /// same sequence of bytes in `output`. These bytes are cryptographically -821 /// strong and pseudorandom, and are suitable for use as keying material. -822 /// -823 /// See [RFC5705](https://tools.ietf.org/html/rfc5705) for more information. -824 pub fn export_keying_material( -825 &self, -826 output: &mut [u8], -827 label: &[u8], -828 context: &[u8], -829 ) -> Result<(), proto::crypto::ExportKeyingMaterialError> { -830 self.0 -831 .lock_without_waking("export_keying_material") -832 .inner -833 .crypto_session() -834 .export_keying_material(output, label, context) -835 } -836 -837 /// Modify the number of remotely initiated unidirectional streams that may be concurrently open -838 /// -839 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -840 /// `count`s increase both minimum and worst-case memory consumption. -841 pub fn set_max_concurrent_uni_streams(&self, count: VarInt) { -842 let mut conn = self.0.lock_and_wake("set_max_concurrent_uni_streams"); -843 conn.inner.set_max_concurrent_streams(Dir::Uni, count); -844 } -845 -846 /// See [`proto::TransportConfig::send_window()`] -847 pub fn set_send_window(&self, send_window: u64) { -848 let mut conn = self.0.lock_and_wake("set_send_window"); -849 conn.inner.set_send_window(send_window); -850 } -851 -852 /// See [`proto::TransportConfig::receive_window()`] -853 pub fn set_receive_window(&self, receive_window: VarInt) { -854 let mut conn = self.0.lock_and_wake("set_receive_window"); -855 conn.inner.set_receive_window(receive_window); -856 } -857 -858 /// Modify the number of remotely initiated bidirectional streams that may be concurrently open -859 /// -860 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -861 /// `count`s increase both minimum and worst-case memory consumption. -862 pub fn set_max_concurrent_bi_streams(&self, count: VarInt) { -863 let mut conn = self.0.lock_and_wake("set_max_concurrent_bi_streams"); -864 conn.inner.set_max_concurrent_streams(Dir::Bi, count); -865 } -866 -867 /// Track changes on our external address as reported by the peer. -868 pub fn observed_external_addr(&self) -> crate::ObservedExternalAddr { -869 let conn = self.0.lock_without_waking("external_addr"); -870 crate::ObservedExternalAddr::new(conn.observed_external_addr.subscribe()) -871 } -872 -873 /// Is multipath enabled? -874 // TODO(flub): not a useful API, once we do real things with multipath we can remove -875 // this again. -876 pub fn is_multipath_enabled(&self) -> bool { -877 let conn = self.0.lock_without_waking("is_multipath_enabled"); -878 conn.inner.is_multipath_negotiated() -879 } -880 -881 /// Registers one address at which this endpoint might be reachable -882 /// -883 /// When the NAT traversal extension is negotiated, servers send these addresses to clients in -884 /// `ADD_ADDRESS` frames. This allows clients to obtain server address candidates to initiate -885 /// NAT traversal attempts. Clients provide their own reachable addresses in `REACH_OUT` frames -886 /// when [`Self::initiate_nat_traversal_round`] is called. -887 pub fn add_nat_traversal_address( +682 } +683 +684 /// Compute the maximum size of datagrams that may be passed to [`send_datagram()`]. +685 /// +686 /// Returns `None` if datagrams are unsupported by the peer or disabled locally. +687 /// +688 /// This may change over the lifetime of a connection according to variation in the path MTU +689 /// estimate. The peer can also enforce an arbitrarily small fixed limit, but if the peer's +690 /// limit is large this is guaranteed to be a little over a kilobyte at minimum. +691 /// +692 /// Not necessarily the maximum size of received datagrams. +693 /// +694 /// [`send_datagram()`]: Connection::send_datagram +695 pub fn max_datagram_size(&self) -> Option<usize> { +696 self.0 +697 .lock_without_waking("max_datagram_size") +698 .inner +699 .datagrams() +700 .max_size() +701 } +702 +703 /// Bytes available in the outgoing datagram buffer +704 /// +705 /// When greater than zero, calling [`send_datagram()`](Self::send_datagram) with a datagram of +706 /// at most this size is guaranteed not to cause older datagrams to be dropped. +707 pub fn datagram_send_buffer_space(&self) -> usize { +708 self.0 +709 .lock_without_waking("datagram_send_buffer_space") +710 .inner +711 .datagrams() +712 .send_buffer_space() +713 } +714 +715 /// The side of the connection (client or server) +716 pub fn side(&self) -> Side { +717 self.0.lock_without_waking("side").inner.side() +718 } +719 +720 /// Current best estimate of this connection's latency (round-trip-time) +721 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { +722 self.0.lock_without_waking("rtt").inner.rtt(path_id) +723 } +724 +725 /// Returns connection statistics +726 pub fn stats(&self) -> ConnectionStats { +727 self.0.lock_without_waking("stats").inner.stats() +728 } +729 +730 /// Returns path statistics +731 pub fn path_stats(&self, path_id: PathId) -> Option<PathStats> { +732 self.0.lock_without_waking("path_stats").path_stats(path_id) +733 } +734 +735 /// Current state of the congestion control algorithm, for debugging purposes +736 pub fn congestion_state(&self, path_id: PathId) -> Option<Box<dyn Controller>> { +737 self.0 +738 .lock_without_waking("congestion_state") +739 .inner +740 .congestion_state(path_id) +741 .map(|c| c.clone_box()) +742 } +743 +744 /// Parameters negotiated during the handshake +745 /// +746 /// Guaranteed to return `Some` on fully established connections or after +747 /// [`Connecting::handshake_data()`] succeeds. See that method's documentations for details on +748 /// the returned value. +749 /// +750 /// [`Connection::handshake_data()`]: crate::Connecting::handshake_data +751 pub fn handshake_data(&self) -> Option<Box<dyn Any>> { +752 self.0 +753 .lock_without_waking("handshake_data") +754 .inner +755 .crypto_session() +756 .handshake_data() +757 } +758 +759 /// Cryptographic identity of the peer +760 /// +761 /// The dynamic type returned is determined by the configured +762 /// [`Session`](proto::crypto::Session). For the default `rustls` session, the return value can +763 /// be [`downcast`](Box::downcast) to a <code>Vec<[rustls::pki_types::CertificateDer]></code> +764 pub fn peer_identity(&self) -> Option<Box<dyn Any>> { +765 self.0 +766 .lock_without_waking("peer_identity") +767 .inner +768 .crypto_session() +769 .peer_identity() +770 } +771 +772 /// A stable identifier for this connection +773 /// +774 /// Peer addresses and connection IDs can change, but this value will remain +775 /// fixed for the lifetime of the connection. +776 pub fn stable_id(&self) -> usize { +777 self.0.stable_id() +778 } +779 +780 /// Update traffic keys spontaneously +781 /// +782 /// This primarily exists for testing purposes. +783 pub fn force_key_update(&self) { +784 self.0 +785 .lock_and_wake("force_key_update") +786 .inner +787 .force_key_update() +788 } +789 +790 /// Derive keying material from this connection's TLS session secrets. +791 /// +792 /// When both peers call this method with the same `label` and `context` +793 /// arguments and `output` buffers of equal length, they will get the +794 /// same sequence of bytes in `output`. These bytes are cryptographically +795 /// strong and pseudorandom, and are suitable for use as keying material. +796 /// +797 /// See [RFC5705](https://tools.ietf.org/html/rfc5705) for more information. +798 pub fn export_keying_material( +799 &self, +800 output: &mut [u8], +801 label: &[u8], +802 context: &[u8], +803 ) -> Result<(), proto::crypto::ExportKeyingMaterialError> { +804 self.0 +805 .lock_without_waking("export_keying_material") +806 .inner +807 .crypto_session() +808 .export_keying_material(output, label, context) +809 } +810 +811 /// Modify the number of remotely initiated unidirectional streams that may be concurrently open +812 /// +813 /// No streams may be opened by the peer unless fewer than `count` are already open. Large +814 /// `count`s increase both minimum and worst-case memory consumption. +815 pub fn set_max_concurrent_uni_streams(&self, count: VarInt) { +816 let mut conn = self.0.lock_and_wake("set_max_concurrent_uni_streams"); +817 conn.inner.set_max_concurrent_streams(Dir::Uni, count); +818 } +819 +820 /// See [`proto::TransportConfig::send_window()`] +821 pub fn set_send_window(&self, send_window: u64) { +822 let mut conn = self.0.lock_and_wake("set_send_window"); +823 conn.inner.set_send_window(send_window); +824 } +825 +826 /// See [`proto::TransportConfig::receive_window()`] +827 pub fn set_receive_window(&self, receive_window: VarInt) { +828 let mut conn = self.0.lock_and_wake("set_receive_window"); +829 conn.inner.set_receive_window(receive_window); +830 } +831 +832 /// Modify the number of remotely initiated bidirectional streams that may be concurrently open +833 /// +834 /// No streams may be opened by the peer unless fewer than `count` are already open. Large +835 /// `count`s increase both minimum and worst-case memory consumption. +836 pub fn set_max_concurrent_bi_streams(&self, count: VarInt) { +837 let mut conn = self.0.lock_and_wake("set_max_concurrent_bi_streams"); +838 conn.inner.set_max_concurrent_streams(Dir::Bi, count); +839 } +840 +841 /// Track changes on our external address as reported by the peer. +842 pub fn observed_external_addr(&self) -> crate::ObservedExternalAddr { +843 let conn = self.0.lock_without_waking("external_addr"); +844 crate::ObservedExternalAddr::new(conn.observed_external_addr.subscribe()) +845 } +846 +847 /// Is multipath enabled? +848 // TODO(flub): not a useful API, once we do real things with multipath we can remove +849 // this again. +850 pub fn is_multipath_enabled(&self) -> bool { +851 let conn = self.0.lock_without_waking("is_multipath_enabled"); +852 conn.inner.is_multipath_negotiated() +853 } +854 +855 /// Registers one address at which this endpoint might be reachable +856 /// +857 /// When the NAT traversal extension is negotiated, servers send these addresses to clients in +858 /// `ADD_ADDRESS` frames. This allows clients to obtain server address candidates to initiate +859 /// NAT traversal attempts. Clients provide their own reachable addresses in `REACH_OUT` frames +860 /// when [`Self::initiate_nat_traversal_round`] is called. +861 pub fn add_nat_traversal_address( +862 &self, +863 address: SocketAddr, +864 ) -> Result<(), n0_nat_traversal::Error> { +865 let mut conn = self.0.lock_and_wake("add_nat_traversal_addresses"); +866 conn.inner.add_nat_traversal_address(address) +867 } +868 +869 /// Removes one or more addresses from the set of addresses at which this endpoint is reachable +870 /// +871 /// When the NAT traversal extension is negotiated, servers send address removals to +872 /// clients in `REMOVE_ADDRESS` frames. This allows clients to stop using outdated +873 /// server address candidates that are no longer valid for NAT traversal. +874 /// +875 /// For clients, removed addresses will no longer be advertised in `REACH_OUT` frames. +876 /// +877 /// Addresses not present in the set will be silently ignored. +878 pub fn remove_nat_traversal_address( +879 &self, +880 address: SocketAddr, +881 ) -> Result<(), n0_nat_traversal::Error> { +882 let mut conn = self.0.lock_and_wake("remove_nat_traversal_addresses"); +883 conn.inner.remove_nat_traversal_address(address) +884 } +885 +886 /// Get the current local nat traversal addresses +887 pub fn get_local_nat_traversal_addresses( 888 &self, -889 address: SocketAddr, -890 ) -> Result<(), n0_nat_traversal::Error> { -891 let mut conn = self.0.lock_and_wake("add_nat_traversal_addresses"); -892 conn.inner.add_nat_traversal_address(address) -893 } -894 -895 /// Removes one or more addresses from the set of addresses at which this endpoint is reachable -896 /// -897 /// When the NAT traversal extension is negotiated, servers send address removals to -898 /// clients in `REMOVE_ADDRESS` frames. This allows clients to stop using outdated -899 /// server address candidates that are no longer valid for NAT traversal. -900 /// -901 /// For clients, removed addresses will no longer be advertised in `REACH_OUT` frames. -902 /// -903 /// Addresses not present in the set will be silently ignored. -904 pub fn remove_nat_traversal_address( -905 &self, -906 address: SocketAddr, -907 ) -> Result<(), n0_nat_traversal::Error> { -908 let mut conn = self.0.lock_and_wake("remove_nat_traversal_addresses"); -909 conn.inner.remove_nat_traversal_address(address) -910 } -911 -912 /// Get the current local nat traversal addresses -913 pub fn get_local_nat_traversal_addresses( -914 &self, -915 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -916 let conn = self -917 .0 -918 .lock_without_waking("get_local_nat_traversal_addresses"); -919 conn.inner.get_local_nat_traversal_addresses() -920 } -921 -922 /// Get the currently advertised nat traversal addresses by the server -923 pub fn get_remote_nat_traversal_addresses( -924 &self, -925 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -926 let conn = self -927 .0 -928 .lock_without_waking("get_remote_nat_traversal_addresses"); -929 conn.inner.get_remote_nat_traversal_addresses() -930 } -931 -932 /// Initiates a new nat traversal round -933 /// -934 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` -935 /// frames, and initiating probing of the known remote addresses. When a new round is -936 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. -937 /// -938 /// Returns the server addresses that are now being probed. -939 pub fn initiate_nat_traversal_round(&self) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -940 let mut conn = self.0.lock_and_wake("initiate_nat_traversal_round"); -941 let now = conn.runtime.now(); -942 conn.inner.initiate_nat_traversal_round(now) -943 } -944} -945 -946pin_project! { -947 /// Future produced by [`Connection::open_uni`] -948 pub struct OpenUni<'a> { -949 conn: &'a ConnectionRef, -950 #[pin] -951 notify: Notified<'a>, -952 } -953} -954 -955impl Future for OpenUni<'_> { -956 type Output = Result<SendStream, ConnectionError>; -957 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -958 let this = self.project(); -959 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Uni))?; -960 Poll::Ready(Ok(SendStream::new(conn, id, is_0rtt))) -961 } -962} -963 -964pin_project! { -965 /// Future produced by [`Connection::open_bi`] -966 pub struct OpenBi<'a> { -967 conn: &'a ConnectionRef, -968 #[pin] -969 notify: Notified<'a>, -970 } -971} -972 -973impl Future for OpenBi<'_> { -974 type Output = Result<(SendStream, RecvStream), ConnectionError>; -975 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -976 let this = self.project(); -977 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Bi))?; -978 -979 Poll::Ready(Ok(( -980 SendStream::new(conn.clone(), id, is_0rtt), -981 RecvStream::new(conn, id, is_0rtt), -982 ))) -983 } -984} -985 -986fn poll_open<'a>( -987 ctx: &mut Context<'_>, -988 conn: &'a ConnectionRef, -989 mut notify: Pin<&mut Notified<'a>>, -990 dir: Dir, -991) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { -992 let mut state = conn.lock_without_waking("poll_open"); -993 if let Some(ref e) = state.error { -994 return Poll::Ready(Err(e.clone())); -995 } else if let Some(id) = state.inner.streams().open(dir) { -996 let is_0rtt = state.inner.side().is_client() && state.inner.is_handshaking(); -997 drop(state); // Release the lock so clone can take it -998 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); -999 } -1000 loop { -1001 match notify.as_mut().poll(ctx) { -1002 // `state` lock ensures we didn't race with readiness -1003 Poll::Pending => return Poll::Pending, -1004 // Spurious wakeup, get a new future -1005 Poll::Ready(()) => { -1006 notify.set(conn.shared.stream_budget_available[dir as usize].notified()) -1007 } -1008 } -1009 } -1010} -1011 -1012pin_project! { -1013 /// Future produced by [`Connection::accept_uni`] -1014 pub struct AcceptUni<'a> { -1015 conn: &'a ConnectionRef, -1016 #[pin] -1017 notify: Notified<'a>, -1018 } -1019} -1020 -1021impl Future for AcceptUni<'_> { -1022 type Output = Result<RecvStream, ConnectionError>; -1023 -1024 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1025 let this = self.project(); -1026 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Uni))?; -1027 Poll::Ready(Ok(RecvStream::new(conn, id, is_0rtt))) -1028 } -1029} -1030 -1031pin_project! { -1032 /// Future produced by [`Connection::accept_bi`] -1033 pub struct AcceptBi<'a> { -1034 conn: &'a ConnectionRef, -1035 #[pin] -1036 notify: Notified<'a>, -1037 } -1038} -1039 -1040impl Future for AcceptBi<'_> { -1041 type Output = Result<(SendStream, RecvStream), ConnectionError>; -1042 -1043 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1044 let this = self.project(); -1045 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Bi))?; -1046 Poll::Ready(Ok(( -1047 SendStream::new(conn.clone(), id, is_0rtt), -1048 RecvStream::new(conn, id, is_0rtt), -1049 ))) -1050 } -1051} -1052 -1053fn poll_accept<'a>( -1054 ctx: &mut Context<'_>, -1055 conn: &'a ConnectionRef, -1056 mut notify: Pin<&mut Notified<'a>>, -1057 dir: Dir, -1058) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { -1059 let mut state = conn.lock_and_wake("poll_accept"); -1060 // Check for incoming streams before checking `state.error` so that already-received streams, -1061 // which are necessarily finite, can be drained from a closed connection. -1062 if let Some(id) = state.inner.streams().accept(dir) { -1063 let is_0rtt = state.inner.is_handshaking(); -1064 drop(state); // Release the lock (wake on drop) so clone can take it -1065 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); -1066 } else if let Some(ref e) = state.error { -1067 return Poll::Ready(Err(e.clone())); -1068 } -1069 loop { -1070 match notify.as_mut().poll(ctx) { -1071 // `state` lock ensures we didn't race with readiness -1072 Poll::Pending => return Poll::Pending, -1073 // Spurious wakeup, get a new future -1074 Poll::Ready(()) => notify.set(conn.shared.stream_incoming[dir as usize].notified()), -1075 } +889 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +890 let conn = self +891 .0 +892 .lock_without_waking("get_local_nat_traversal_addresses"); +893 conn.inner.get_local_nat_traversal_addresses() +894 } +895 +896 /// Get the currently advertised nat traversal addresses by the server +897 pub fn get_remote_nat_traversal_addresses( +898 &self, +899 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +900 let conn = self +901 .0 +902 .lock_without_waking("get_remote_nat_traversal_addresses"); +903 conn.inner.get_remote_nat_traversal_addresses() +904 } +905 +906 /// Initiates a new nat traversal round +907 /// +908 /// A nat traversal round involves advertising the client's local addresses in `REACH_OUT` +909 /// frames, and initiating probing of the known remote addresses. When a new round is +910 /// initiated, the previous one is cancelled, and paths that have not been opened are closed. +911 /// +912 /// Returns the server addresses that are now being probed. +913 pub fn initiate_nat_traversal_round(&self) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +914 let mut conn = self.0.lock_and_wake("initiate_nat_traversal_round"); +915 let now = conn.runtime.now(); +916 conn.inner.initiate_nat_traversal_round(now) +917 } +918} +919 +920/// Normalizes a [`FourTuple`] against the connection's address family. +921/// +922/// If the connection already uses IPv6 paths, the remote is canonicalised via +923/// [`ensure_ipv6`]. If it uses IPv4 and the requested remote is IPv6, this returns +924/// [`PathError::InvalidRemoteAddress`]. +925fn normalize_network_path( +926 network_path: FourTuple, +927 conn: &proto::Connection, +928) -> Result<FourTuple, PathError> { +929 // If endpoint::State::ipv6 is true we want to keep all our IP addresses as IPv6. +930 // If not, we do not support IPv6. We can not access endpoint::State from here +931 // however, but either all our paths use an IPv6 address, or all our paths use an +932 // IPv4 address. So we can use that information. +933 let ipv6 = conn +934 .paths() +935 .iter() +936 .filter_map(|id| { +937 conn.network_path(*id) +938 .map(|addrs| addrs.remote().is_ipv6()) +939 .ok() +940 }) +941 .next() +942 .unwrap_or_default(); +943 let remote = network_path.remote(); +944 if remote.is_ipv6() && !ipv6 { +945 Err(PathError::InvalidRemoteAddress(remote)) +946 } else if ipv6 { +947 let remote = SocketAddr::V6(ensure_ipv6(remote)); +948 Ok(FourTuple::new(remote, network_path.local_ip())) +949 } else { +950 Ok(network_path) +951 } +952} +953 +954pin_project! { +955 /// Future produced by [`Connection::open_uni`] +956 pub struct OpenUni<'a> { +957 conn: &'a ConnectionRef, +958 #[pin] +959 notify: Notified<'a>, +960 } +961} +962 +963impl Future for OpenUni<'_> { +964 type Output = Result<SendStream, ConnectionError>; +965 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +966 let this = self.project(); +967 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Uni))?; +968 Poll::Ready(Ok(SendStream::new(conn, id, is_0rtt))) +969 } +970} +971 +972pin_project! { +973 /// Future produced by [`Connection::open_bi`] +974 pub struct OpenBi<'a> { +975 conn: &'a ConnectionRef, +976 #[pin] +977 notify: Notified<'a>, +978 } +979} +980 +981impl Future for OpenBi<'_> { +982 type Output = Result<(SendStream, RecvStream), ConnectionError>; +983 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +984 let this = self.project(); +985 let (conn, id, is_0rtt) = ready!(poll_open(ctx, this.conn, this.notify, Dir::Bi))?; +986 +987 Poll::Ready(Ok(( +988 SendStream::new(conn.clone(), id, is_0rtt), +989 RecvStream::new(conn, id, is_0rtt), +990 ))) +991 } +992} +993 +994fn poll_open<'a>( +995 ctx: &mut Context<'_>, +996 conn: &'a ConnectionRef, +997 mut notify: Pin<&mut Notified<'a>>, +998 dir: Dir, +999) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { +1000 let mut state = conn.lock_without_waking("poll_open"); +1001 if let Some(ref e) = state.error { +1002 return Poll::Ready(Err(e.clone())); +1003 } else if let Some(id) = state.inner.streams().open(dir) { +1004 let is_0rtt = state.inner.side().is_client() && state.inner.is_handshaking(); +1005 drop(state); // Release the lock so clone can take it +1006 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); +1007 } +1008 loop { +1009 match notify.as_mut().poll(ctx) { +1010 // `state` lock ensures we didn't race with readiness +1011 Poll::Pending => return Poll::Pending, +1012 // Spurious wakeup, get a new future +1013 Poll::Ready(()) => { +1014 notify.set(conn.shared.stream_budget_available[dir as usize].notified()) +1015 } +1016 } +1017 } +1018} +1019 +1020pin_project! { +1021 /// Future produced by [`Connection::accept_uni`] +1022 pub struct AcceptUni<'a> { +1023 conn: &'a ConnectionRef, +1024 #[pin] +1025 notify: Notified<'a>, +1026 } +1027} +1028 +1029impl Future for AcceptUni<'_> { +1030 type Output = Result<RecvStream, ConnectionError>; +1031 +1032 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +1033 let this = self.project(); +1034 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Uni))?; +1035 Poll::Ready(Ok(RecvStream::new(conn, id, is_0rtt))) +1036 } +1037} +1038 +1039pin_project! { +1040 /// Future produced by [`Connection::accept_bi`] +1041 pub struct AcceptBi<'a> { +1042 conn: &'a ConnectionRef, +1043 #[pin] +1044 notify: Notified<'a>, +1045 } +1046} +1047 +1048impl Future for AcceptBi<'_> { +1049 type Output = Result<(SendStream, RecvStream), ConnectionError>; +1050 +1051 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +1052 let this = self.project(); +1053 let (conn, id, is_0rtt) = ready!(poll_accept(ctx, this.conn, this.notify, Dir::Bi))?; +1054 Poll::Ready(Ok(( +1055 SendStream::new(conn.clone(), id, is_0rtt), +1056 RecvStream::new(conn, id, is_0rtt), +1057 ))) +1058 } +1059} +1060 +1061fn poll_accept<'a>( +1062 ctx: &mut Context<'_>, +1063 conn: &'a ConnectionRef, +1064 mut notify: Pin<&mut Notified<'a>>, +1065 dir: Dir, +1066) -> Poll<Result<(ConnectionRef, StreamId, bool), ConnectionError>> { +1067 let mut state = conn.lock_and_wake("poll_accept"); +1068 // Check for incoming streams before checking `state.error` so that already-received streams, +1069 // which are necessarily finite, can be drained from a closed connection. +1070 if let Some(id) = state.inner.streams().accept(dir) { +1071 let is_0rtt = state.inner.is_handshaking(); +1072 drop(state); // Release the lock (wake on drop) so clone can take it +1073 return Poll::Ready(Ok((conn.clone(), id, is_0rtt))); +1074 } else if let Some(ref e) = state.error { +1075 return Poll::Ready(Err(e.clone())); 1076 } -1077} -1078 -1079pin_project! { -1080 /// Future produced by [`Connection::read_datagram`] -1081 pub struct ReadDatagram<'a> { -1082 conn: &'a ConnectionRef, -1083 #[pin] -1084 notify: Notified<'a>, -1085 } -1086} -1087 -1088impl Future for ReadDatagram<'_> { -1089 type Output = Result<Bytes, ConnectionError>; -1090 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1091 let mut this = self.project(); -1092 let mut state = this.conn.lock_without_waking("ReadDatagram::poll"); -1093 // Check for buffered datagrams before checking `state.error` so that already-received -1094 // datagrams, which are necessarily finite, can be drained from a closed connection. -1095 if let Some(x) = state.inner.datagrams().recv() { -1096 return Poll::Ready(Ok(x)); -1097 } else if let Some(ref e) = state.error { -1098 return Poll::Ready(Err(e.clone())); -1099 } -1100 loop { -1101 match this.notify.as_mut().poll(ctx) { -1102 // `state` lock ensures we didn't race with readiness -1103 Poll::Pending => return Poll::Pending, -1104 // Spurious wakeup, get a new future -1105 Poll::Ready(()) => this -1106 .notify -1107 .set(this.conn.shared.datagram_received.notified()), -1108 } -1109 } -1110 } -1111} -1112 -1113pin_project! { -1114 /// Future produced by [`Connection::send_datagram_wait`] -1115 pub struct SendDatagram<'a> { -1116 conn: &'a ConnectionRef, -1117 data: Option<Bytes>, -1118 #[pin] -1119 notify: Notified<'a>, -1120 } -1121} -1122 -1123impl Future for SendDatagram<'_> { -1124 type Output = Result<(), SendDatagramError>; -1125 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { -1126 let mut this = self.project(); -1127 let mut state = this.conn.lock_and_wake("SendDatagram::poll"); -1128 if let Some(ref e) = state.error { -1129 return Poll::Ready(Err(SendDatagramError::ConnectionLost(e.clone()))); -1130 } -1131 use proto::SendDatagramError::*; -1132 match state -1133 .inner -1134 .datagrams() -1135 .send(this.data.take().unwrap(), false) -1136 { -1137 Ok(()) => Poll::Ready(Ok(())), -1138 Err(e) => Poll::Ready(Err(match e { -1139 Blocked(data) => { -1140 this.data.replace(data); -1141 loop { -1142 match this.notify.as_mut().poll(ctx) { -1143 Poll::Pending => return Poll::Pending, -1144 // Spurious wakeup, get a new future -1145 Poll::Ready(()) => this -1146 .notify -1147 .set(this.conn.shared.datagrams_unblocked.notified()), -1148 } -1149 } -1150 } -1151 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, -1152 Disabled => SendDatagramError::Disabled, -1153 TooLarge => SendDatagramError::TooLarge, -1154 })), -1155 } -1156 } -1157} -1158 -1159/// State of a [`Connection`] at the moment it was closed. -1160/// -1161/// Returned by the [`OnClosed`] future from [`Connection::on_closed`]. -1162#[derive(Debug, Clone)] -1163#[non_exhaustive] -1164pub struct Closed { -1165 /// The reason the connection was closed. -1166 pub reason: ConnectionError, -1167 /// Aggregate connection statistics at the moment of close. -1168 pub stats: ConnectionStats, -1169 /// Per-path statistics for every path the connection knew about at close time. -1170 /// -1171 /// This includes paths that haven't been discarded at close time, plus any -1172 /// already-discarded paths whose final stats had been retained because a [`Path`] -1173 /// or [`WeakPathHandle`] handle was kept alive. -1174 /// -1175 /// [`WeakPathHandle`]: crate::WeakPathHandle -1176 pub path_stats: Vec<(PathId, PathStats)>, -1177} -1178 -1179impl Closed { -1180 /// Snapshot the current connection state into a [`Closed`] value. -1181 /// -1182 /// Must only be called once `state.error` has been set. -1183 pub(crate) fn new(state: &mut State, reason: ConnectionError) -> Self { -1184 let stats = state.inner.stats(); -1185 -1186 let non_discarded_paths = state.inner.paths(); -1187 let mut path_stats = -1188 Vec::with_capacity(non_discarded_paths.len() + state.final_path_stats.len()); -1189 -1190 // Non-discarded paths are tracked by proto::Connection. -1191 path_stats.extend( -1192 non_discarded_paths -1193 .into_iter() -1194 .filter_map(|id| state.inner.path_stats(id).map(|stats| (id, stats))), -1195 ); -1196 // Already-discarded paths whose final stats we kept around. -1197 path_stats.extend( -1198 state -1199 .final_path_stats -1200 .iter() -1201 .map(|(id, stats)| (*id, *stats)), -1202 ); -1203 Self { -1204 reason, -1205 stats, -1206 path_stats, -1207 } -1208 } -1209} -1210 -1211/// Future returned by [`Connection::on_closed`] -1212/// -1213/// Resolves to [`Closed`]. -1214pub struct OnClosed { -1215 rx: oneshot::Receiver<Closed>, -1216 conn: WeakConnectionHandle, +1077 loop { +1078 match notify.as_mut().poll(ctx) { +1079 // `state` lock ensures we didn't race with readiness +1080 Poll::Pending => return Poll::Pending, +1081 // Spurious wakeup, get a new future +1082 Poll::Ready(()) => notify.set(conn.shared.stream_incoming[dir as usize].notified()), +1083 } +1084 } +1085} +1086 +1087pin_project! { +1088 /// Future produced by [`Connection::read_datagram`] +1089 pub struct ReadDatagram<'a> { +1090 conn: &'a ConnectionRef, +1091 #[pin] +1092 notify: Notified<'a>, +1093 } +1094} +1095 +1096impl Future for ReadDatagram<'_> { +1097 type Output = Result<Bytes, ConnectionError>; +1098 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +1099 let mut this = self.project(); +1100 let mut state = this.conn.lock_without_waking("ReadDatagram::poll"); +1101 // Check for buffered datagrams before checking `state.error` so that already-received +1102 // datagrams, which are necessarily finite, can be drained from a closed connection. +1103 if let Some(x) = state.inner.datagrams().recv() { +1104 return Poll::Ready(Ok(x)); +1105 } else if let Some(ref e) = state.error { +1106 return Poll::Ready(Err(e.clone())); +1107 } +1108 loop { +1109 match this.notify.as_mut().poll(ctx) { +1110 // `state` lock ensures we didn't race with readiness +1111 Poll::Pending => return Poll::Pending, +1112 // Spurious wakeup, get a new future +1113 Poll::Ready(()) => this +1114 .notify +1115 .set(this.conn.shared.datagram_received.notified()), +1116 } +1117 } +1118 } +1119} +1120 +1121pin_project! { +1122 /// Future produced by [`Connection::send_datagram_wait`] +1123 pub struct SendDatagram<'a> { +1124 conn: &'a ConnectionRef, +1125 data: Option<Bytes>, +1126 #[pin] +1127 notify: Notified<'a>, +1128 } +1129} +1130 +1131impl Future for SendDatagram<'_> { +1132 type Output = Result<(), SendDatagramError>; +1133 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +1134 let mut this = self.project(); +1135 let mut state = this.conn.lock_and_wake("SendDatagram::poll"); +1136 if let Some(ref e) = state.error { +1137 return Poll::Ready(Err(SendDatagramError::ConnectionLost(e.clone()))); +1138 } +1139 use proto::SendDatagramError::*; +1140 match state +1141 .inner +1142 .datagrams() +1143 .send(this.data.take().unwrap(), false) +1144 { +1145 Ok(()) => Poll::Ready(Ok(())), +1146 Err(e) => Poll::Ready(Err(match e { +1147 Blocked(data) => { +1148 this.data.replace(data); +1149 loop { +1150 match this.notify.as_mut().poll(ctx) { +1151 Poll::Pending => return Poll::Pending, +1152 // Spurious wakeup, get a new future +1153 Poll::Ready(()) => this +1154 .notify +1155 .set(this.conn.shared.datagrams_unblocked.notified()), +1156 } +1157 } +1158 } +1159 UnsupportedByPeer => SendDatagramError::UnsupportedByPeer, +1160 Disabled => SendDatagramError::Disabled, +1161 TooLarge => SendDatagramError::TooLarge, +1162 })), +1163 } +1164 } +1165} +1166 +1167/// State of a [`Connection`] at the moment it was closed. +1168/// +1169/// Returned by the [`OnClosed`] future from [`Connection::on_closed`]. +1170#[derive(Debug, Clone)] +1171#[non_exhaustive] +1172pub struct Closed { +1173 /// The reason the connection was closed. +1174 pub reason: ConnectionError, +1175 /// Aggregate connection statistics at the moment of close. +1176 pub stats: ConnectionStats, +1177 /// Per-path statistics for every path the connection knew about at close time. +1178 /// +1179 /// This includes paths that haven't been discarded at close time, plus any +1180 /// already-discarded paths whose final stats had been retained because a [`Path`] +1181 /// or [`WeakPathHandle`] handle was kept alive. +1182 /// +1183 /// [`WeakPathHandle`]: crate::WeakPathHandle +1184 pub path_stats: Vec<(PathId, PathStats)>, +1185} +1186 +1187impl Closed { +1188 /// Snapshot the current connection state into a [`Closed`] value. +1189 /// +1190 /// Must only be called once `state.error` has been set. +1191 pub(crate) fn new(state: &mut State, reason: ConnectionError) -> Self { +1192 let stats = state.inner.stats(); +1193 +1194 let non_discarded_paths = state.inner.paths(); +1195 let mut path_stats = +1196 Vec::with_capacity(non_discarded_paths.len() + state.final_path_stats.len()); +1197 +1198 // Non-discarded paths are tracked by proto::Connection. +1199 path_stats.extend( +1200 non_discarded_paths +1201 .into_iter() +1202 .filter_map(|id| state.inner.path_stats(id).map(|stats| (id, stats))), +1203 ); +1204 // Already-discarded paths whose final stats we kept around. +1205 path_stats.extend( +1206 state +1207 .final_path_stats +1208 .iter() +1209 .map(|(id, stats)| (*id, *stats)), +1210 ); +1211 Self { +1212 reason, +1213 stats, +1214 path_stats, +1215 } +1216 } 1217} 1218 -1219impl Drop for OnClosed { -1220 fn drop(&mut self) { -1221 if self.rx.is_terminated() { -1222 return; -1223 }; -1224 if let Some(conn) = self.conn.upgrade() { -1225 self.rx.close(); -1226 conn.0 -1227 .lock_without_waking("OnClosed::drop") -1228 .on_closed -1229 .retain(|tx| !tx.is_closed()); -1230 } -1231 } -1232} -1233 -1234impl Future for OnClosed { -1235 type Output = Closed; -1236 -1237 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { -1238 let this = self.get_mut(); -1239 // The `expect` is safe because `State::drop` ensures that all senders are triggered -1240 // before being dropped. -1241 Pin::new(&mut this.rx) -1242 .poll(cx) -1243 .map(|x| x.expect("on_close sender is never dropped before sending")) -1244 } -1245} -1246 -1247#[derive(Debug)] -1248#[allow(clippy::redundant_allocation)] -1249pub(crate) struct ConnectionRef(Arc<Arc<ConnectionInner>>); -1250 -1251impl ConnectionRef { -1252 #[allow(clippy::redundant_allocation)] -1253 fn from_arc(inner: Arc<Arc<ConnectionInner>>) -> Self { -1254 inner.shared.ref_count.fetch_add(1, Ordering::Relaxed); -1255 Self(inner) -1256 } -1257 -1258 pub(crate) fn stable_id(&self) -> usize { -1259 &*self.0 as *const _ as usize -1260 } -1261 -1262 pub(crate) fn weak_handle(&self) -> WeakConnectionHandle { -1263 WeakConnectionHandle(Arc::downgrade(&self.0)) +1219/// Future returned by [`Connection::on_closed`] +1220/// +1221/// Resolves to [`Closed`]. +1222pub struct OnClosed { +1223 rx: oneshot::Receiver<Closed>, +1224 conn: WeakConnectionHandle, +1225} +1226 +1227impl Drop for OnClosed { +1228 fn drop(&mut self) { +1229 if self.rx.is_terminated() { +1230 return; +1231 }; +1232 if let Some(conn) = self.conn.upgrade() { +1233 self.rx.close(); +1234 conn.0 +1235 .lock_without_waking("OnClosed::drop") +1236 .on_closed +1237 .retain(|tx| !tx.is_closed()); +1238 } +1239 } +1240} +1241 +1242impl Future for OnClosed { +1243 type Output = Closed; +1244 +1245 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { +1246 let this = self.get_mut(); +1247 // The `expect` is safe because `State::drop` ensures that all senders are triggered +1248 // before being dropped. +1249 Pin::new(&mut this.rx) +1250 .poll(cx) +1251 .map(|x| x.expect("on_close sender is never dropped before sending")) +1252 } +1253} +1254 +1255#[derive(Debug)] +1256#[allow(clippy::redundant_allocation)] +1257pub(crate) struct ConnectionRef(Arc<Arc<ConnectionInner>>); +1258 +1259impl ConnectionRef { +1260 #[allow(clippy::redundant_allocation)] +1261 fn from_arc(inner: Arc<Arc<ConnectionInner>>) -> Self { +1262 inner.shared.ref_count.fetch_add(1, Ordering::Relaxed); +1263 Self(inner) 1264 } -1265} -1266 -1267impl Clone for ConnectionRef { -1268 fn clone(&self) -> Self { -1269 Self::from_arc(Arc::clone(&self.0)) -1270 } -1271} -1272 -1273impl Drop for ConnectionRef { -1274 fn drop(&mut self) { -1275 if self.shared.ref_count.fetch_sub(1, Ordering::Relaxed) > 1 { -1276 return; -1277 } -1278 -1279 let conn = &mut *self.lock_without_waking("drop"); +1265 +1266 pub(crate) fn stable_id(&self) -> usize { +1267 &*self.0 as *const _ as usize +1268 } +1269 +1270 pub(crate) fn weak_handle(&self) -> WeakConnectionHandle { +1271 WeakConnectionHandle(Arc::downgrade(&self.0)) +1272 } +1273} +1274 +1275impl Clone for ConnectionRef { +1276 fn clone(&self) -> Self { +1277 Self::from_arc(Arc::clone(&self.0)) +1278 } +1279} 1280 -1281 if !conn.inner.is_closed() { -1282 // If the driver is alive, it's just it and us, so we'd better shut it down. If it's -1283 // not, we can't do any harm. If there were any streams being opened, then either -1284 // the connection will be closed for an unrelated reason or a fresh reference will -1285 // be constructed for the newly opened stream. -1286 conn.implicit_close(&self.shared); -1287 } -1288 } -1289} -1290 -1291impl std::ops::Deref for ConnectionRef { -1292 type Target = ConnectionInner; -1293 fn deref(&self) -> &Self::Target { -1294 &self.0 -1295 } -1296} -1297 -1298#[derive(Debug)] -1299pub(crate) struct ConnectionInner { -1300 /// Kept private intentionally, use [`Self::lock_and_wake`]. -1301 state: Mutex<State>, -1302 pub(crate) shared: Shared, -1303} -1304 -1305impl ConnectionInner { -1306 /// Lock the state and return a guard that wakes the connection driver on drop. -1307 /// -1308 /// Use this for operations that may queue frames. The wake ensures the driver sends queued frames. -1309 /// If that's not needed, use [`Self::lock_without_waking`]. -1310 pub(crate) fn lock_and_wake(&self, purpose: &'static str) -> WakeGuard<'_> { -1311 WakeGuard { -1312 guard: self.state.lock(purpose), -1313 wake: true, -1314 } -1315 } -1316 -1317 /// Lock the state and return a guard that unlocks once dropped. -1318 /// -1319 /// Use this for operations that don't require any action from the connection driver. -1320 /// Otherwise, use [`Self::lock_and_wake`] instead. -1321 pub(crate) fn lock_without_waking(&self, purpose: &'static str) -> WakeGuard<'_> { -1322 WakeGuard { -1323 guard: self.state.lock(purpose), -1324 wake: false, -1325 } -1326 } -1327} -1328 -1329/// [`MutexGuard`] wrapper that calls [`State::wake`] on drop. -1330#[derive(derive_more::Deref, derive_more::DerefMut)] -1331pub(crate) struct WakeGuard<'a> { -1332 #[deref] -1333 #[deref_mut] -1334 guard: MutexGuard<'a, State>, -1335 wake: bool, -1336} -1337 -1338impl WakeGuard<'_> { -1339 pub(crate) fn skip_waking(&mut self) { -1340 self.wake = false; -1341 } -1342} -1343 -1344impl Drop for WakeGuard<'_> { -1345 fn drop(&mut self) { -1346 if self.wake { -1347 self.guard.wake(); -1348 } +1281impl Drop for ConnectionRef { +1282 fn drop(&mut self) { +1283 if self.shared.ref_count.fetch_sub(1, Ordering::Relaxed) > 1 { +1284 return; +1285 } +1286 +1287 let conn = &mut *self.lock_without_waking("drop"); +1288 +1289 if !conn.inner.is_closed() { +1290 // If the driver is alive, it's just it and us, so we'd better shut it down. If it's +1291 // not, we can't do any harm. If there were any streams being opened, then either +1292 // the connection will be closed for an unrelated reason or a fresh reference will +1293 // be constructed for the newly opened stream. +1294 conn.implicit_close(&self.shared); +1295 } +1296 } +1297} +1298 +1299impl std::ops::Deref for ConnectionRef { +1300 type Target = ConnectionInner; +1301 fn deref(&self) -> &Self::Target { +1302 &self.0 +1303 } +1304} +1305 +1306#[derive(Debug)] +1307pub(crate) struct ConnectionInner { +1308 /// Kept private intentionally, use [`Self::lock_and_wake`]. +1309 state: Mutex<State>, +1310 pub(crate) shared: Shared, +1311} +1312 +1313impl ConnectionInner { +1314 /// Lock the state and return a guard that wakes the connection driver on drop. +1315 /// +1316 /// Use this for operations that may queue frames. The wake ensures the driver sends queued frames. +1317 /// If that's not needed, use [`Self::lock_without_waking`]. +1318 pub(crate) fn lock_and_wake(&self, purpose: &'static str) -> WakeGuard<'_> { +1319 WakeGuard { +1320 guard: self.state.lock(purpose), +1321 wake: true, +1322 } +1323 } +1324 +1325 /// Lock the state and return a guard that unlocks once dropped. +1326 /// +1327 /// Use this for operations that don't require any action from the connection driver. +1328 /// Otherwise, use [`Self::lock_and_wake`] instead. +1329 pub(crate) fn lock_without_waking(&self, purpose: &'static str) -> WakeGuard<'_> { +1330 WakeGuard { +1331 guard: self.state.lock(purpose), +1332 wake: false, +1333 } +1334 } +1335} +1336 +1337/// [`MutexGuard`] wrapper that calls [`State::wake`] on drop. +1338#[derive(derive_more::Deref, derive_more::DerefMut)] +1339pub(crate) struct WakeGuard<'a> { +1340 #[deref] +1341 #[deref_mut] +1342 guard: MutexGuard<'a, State>, +1343 wake: bool, +1344} +1345 +1346impl WakeGuard<'_> { +1347 pub(crate) fn skip_waking(&mut self) { +1348 self.wake = false; 1349 } 1350} 1351 -1352/// A handle to some connection internals, use with care. -1353/// -1354/// This contains a weak reference to the connection so will not itself keep the connection -1355/// alive. -1356#[derive(Debug, Clone)] -1357pub struct WeakConnectionHandle(Weak<Arc<ConnectionInner>>); -1358 -1359impl WeakConnectionHandle { -1360 /// Returns `true` if the [`Connection`] associated with this handle is still alive. -1361 pub fn is_alive(&self) -> bool { -1362 self.0.upgrade().is_some() -1363 } -1364 -1365 /// Upgrade the handle to a full `Connection` -1366 pub fn upgrade(&self) -> Option<Connection> { -1367 self.upgrade_to_ref().map(Connection) -1368 } -1369 -1370 pub(crate) fn upgrade_to_ref(&self) -> Option<ConnectionRef> { -1371 self.0.upgrade().map(ConnectionRef::from_arc) -1372 } -1373 -1374 /// Returns `true` if the two [`WeakConnectionHandle`] point at the same connection. -1375 pub fn is_same_connection(&self, other: &Self) -> bool { -1376 self.0.ptr_eq(&other.0) -1377 } -1378} -1379 -1380#[derive(Debug, Default)] -1381pub(crate) struct Shared { -1382 handshake_confirmed: Notify, -1383 /// Notified when new streams may be locally initiated due to an increase in stream ID flow -1384 /// control budget -1385 stream_budget_available: [Notify; 2], -1386 /// Notified when the peer has initiated a new stream -1387 stream_incoming: [Notify; 2], -1388 datagram_received: Notify, -1389 datagrams_unblocked: Notify, -1390 closed: Notify, -1391 /// Number of live handles that can be used to initiate or handle I/O; excludes the driver -1392 ref_count: AtomicUsize, -1393} -1394 -1395pub(crate) struct State { -1396 pub(crate) inner: proto::Connection, -1397 driver: Option<Waker>, -1398 handle: ConnectionHandle, -1399 on_handshake_data: Option<oneshot::Sender<()>>, -1400 on_connected: Option<oneshot::Sender<bool>>, -1401 connected: bool, -1402 handshake_confirmed: bool, -1403 timer: Option<Pin<Box<dyn AsyncTimer>>>, -1404 timer_deadline: Option<Instant>, -1405 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, -1406 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, -1407 pub(crate) blocked_writers: FxHashMap<StreamId, Waker>, -1408 pub(crate) blocked_readers: FxHashMap<StreamId, Waker>, -1409 pub(crate) stopped: FxHashMap<StreamId, Arc<Notify>>, -1410 /// Always set to Some before the connection becomes drained -1411 pub(crate) error: Option<ConnectionError>, -1412 /// Tracks paths being opened -1413 open_path: FxHashMap<PathId, watch::Sender<Result<(), PathError>>>, -1414 /// Tracks reference counts for paths. -1415 /// -1416 /// I.e. how many [`Path`] and [`WeakPathHandle`] structs are alive for a path. -1417 /// Each entry's [`PathRefOwner`] holds an [`AtomicUsize`] so that cloning or -1418 /// dropping a [`PathRef`] (held by [`Path`] or [`WeakPathHandle`]) does not need -1419 /// to lock this state. -1420 /// -1421 /// [`WeakPathHandle`]: crate::path::WeakPathHandle -1422 pub(crate) path_refs: FxHashMap<PathId, PathRefOwner>, -1423 /// Final path stats for discarded paths. -1424 /// -1425 /// We only insert entries if the discarded path has a non-zero reference count in [`Self::path_refs`]. -1426 /// When the last reference to a path is dropped its entry is removed from both maps. -1427 pub(crate) final_path_stats: FxHashMap<PathId, PathStats>, -1428 pub(crate) path_events: tokio::sync::broadcast::Sender<PathEvent>, -1429 sender: Pin<Box<dyn UdpSender>>, -1430 pub(crate) runtime: Arc<dyn Runtime>, -1431 send_buffer: Vec<u8>, -1432 /// We buffer a transmit when the underlying I/O would block -1433 buffered_transmit: Option<proto::Transmit>, -1434 /// Our last external address reported by the peer. When multipath is enabled, this will be the -1435 /// last report across all paths. -1436 pub(crate) observed_external_addr: watch::Sender<Option<SocketAddr>>, -1437 pub(crate) nat_traversal_updates: tokio::sync::broadcast::Sender<n0_nat_traversal::Event>, -1438 on_closed: Vec<oneshot::Sender<Closed>>, -1439} -1440 -1441impl State { -1442 #[allow(clippy::too_many_arguments)] -1443 fn new( -1444 inner: proto::Connection, -1445 handle: ConnectionHandle, -1446 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, -1447 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, -1448 on_handshake_data: oneshot::Sender<()>, -1449 on_connected: oneshot::Sender<bool>, -1450 sender: Pin<Box<dyn UdpSender>>, -1451 runtime: Arc<dyn Runtime>, -1452 ) -> Self { -1453 Self { -1454 inner, -1455 driver: None, -1456 handle, -1457 on_handshake_data: Some(on_handshake_data), -1458 on_connected: Some(on_connected), -1459 connected: false, -1460 handshake_confirmed: false, -1461 timer: None, -1462 timer_deadline: None, -1463 conn_events, -1464 endpoint_events, -1465 blocked_writers: FxHashMap::default(), -1466 blocked_readers: FxHashMap::default(), -1467 stopped: FxHashMap::default(), -1468 open_path: FxHashMap::default(), -1469 error: None, -1470 sender, -1471 runtime, -1472 send_buffer: Vec::new(), -1473 buffered_transmit: None, -1474 path_events: tokio::sync::broadcast::channel(32).0, -1475 observed_external_addr: watch::Sender::new(None), -1476 nat_traversal_updates: tokio::sync::broadcast::channel(32).0, -1477 on_closed: Vec::new(), -1478 final_path_stats: Default::default(), -1479 path_refs: Default::default(), -1480 } -1481 } -1482 -1483 fn drive_transmit(&mut self, cx: &mut Context<'_>) -> io::Result<bool> { -1484 let now = self.runtime.now(); -1485 let mut transmits = 0; -1486 -1487 let max_datagrams = self -1488 .sender -1489 .max_transmit_segments() -1490 .min(MAX_TRANSMIT_SEGMENTS); -1491 -1492 loop { -1493 // Retry the last transmit, or get a new one. -1494 let t = match self.buffered_transmit.take() { -1495 Some(t) => t, -1496 None => { -1497 self.send_buffer.clear(); -1498 match self -1499 .inner -1500 .poll_transmit(now, max_datagrams, &mut self.send_buffer) -1501 { -1502 Some(t) => { -1503 transmits += match t.segment_size { -1504 None => 1, -1505 Some(s) => t.size.div_ceil(s), // round up -1506 }; -1507 t -1508 } -1509 None => break, -1510 } -1511 } -1512 }; -1513 -1514 let len = t.size; -1515 match self -1516 .sender -1517 .as_mut() -1518 .poll_send(&udp_transmit(&t, &self.send_buffer[..len]), cx) -1519 { -1520 Poll::Pending => { -1521 self.buffered_transmit = Some(t); -1522 return Ok(false); -1523 } -1524 Poll::Ready(Err(e)) => return Err(e), -1525 Poll::Ready(Ok(())) => {} -1526 } -1527 -1528 if transmits >= MAX_TRANSMIT_DATAGRAMS { -1529 // TODO: What isn't ideal here yet is that if we don't poll all -1530 // datagrams that could be sent we don't go into the `app_limited` -1531 // state and CWND continues to grow until we get here the next time. -1532 // See https://github.com/quinn-rs/quinn/issues/1126 -1533 return Ok(true); +1352impl Drop for WakeGuard<'_> { +1353 fn drop(&mut self) { +1354 if self.wake { +1355 self.guard.wake(); +1356 } +1357 } +1358} +1359 +1360/// A handle to some connection internals, use with care. +1361/// +1362/// This contains a weak reference to the connection so will not itself keep the connection +1363/// alive. +1364#[derive(Debug, Clone)] +1365pub struct WeakConnectionHandle(Weak<Arc<ConnectionInner>>); +1366 +1367impl WeakConnectionHandle { +1368 /// Returns `true` if the [`Connection`] associated with this handle is still alive. +1369 pub fn is_alive(&self) -> bool { +1370 self.0.upgrade().is_some() +1371 } +1372 +1373 /// Upgrade the handle to a full `Connection` +1374 pub fn upgrade(&self) -> Option<Connection> { +1375 self.upgrade_to_ref().map(Connection) +1376 } +1377 +1378 pub(crate) fn upgrade_to_ref(&self) -> Option<ConnectionRef> { +1379 self.0.upgrade().map(ConnectionRef::from_arc) +1380 } +1381 +1382 /// Returns `true` if the two [`WeakConnectionHandle`] point at the same connection. +1383 pub fn is_same_connection(&self, other: &Self) -> bool { +1384 self.0.ptr_eq(&other.0) +1385 } +1386} +1387 +1388#[derive(Debug, Default)] +1389pub(crate) struct Shared { +1390 handshake_confirmed: Notify, +1391 /// Notified when new streams may be locally initiated due to an increase in stream ID flow +1392 /// control budget +1393 stream_budget_available: [Notify; 2], +1394 /// Notified when the peer has initiated a new stream +1395 stream_incoming: [Notify; 2], +1396 datagram_received: Notify, +1397 datagrams_unblocked: Notify, +1398 closed: Notify, +1399 /// Number of live handles that can be used to initiate or handle I/O; excludes the driver +1400 ref_count: AtomicUsize, +1401} +1402 +1403pub(crate) struct State { +1404 pub(crate) inner: proto::Connection, +1405 driver: Option<Waker>, +1406 handle: ConnectionHandle, +1407 on_handshake_data: Option<oneshot::Sender<()>>, +1408 on_connected: Option<oneshot::Sender<bool>>, +1409 connected: bool, +1410 handshake_confirmed: bool, +1411 timer: Option<Pin<Box<dyn AsyncTimer>>>, +1412 timer_deadline: Option<Instant>, +1413 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, +1414 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, +1415 pub(crate) blocked_writers: FxHashMap<StreamId, Waker>, +1416 pub(crate) blocked_readers: FxHashMap<StreamId, Waker>, +1417 pub(crate) stopped: FxHashMap<StreamId, Arc<Notify>>, +1418 /// Always set to Some before the connection becomes drained +1419 pub(crate) error: Option<ConnectionError>, +1420 /// Tracks paths being opened +1421 open_path: FxHashMap<PathId, watch::Sender<Result<(), PathError>>>, +1422 /// Tracks reference counts for paths. +1423 /// +1424 /// I.e. how many [`Path`] and [`WeakPathHandle`] structs are alive for a path. +1425 /// Each entry's [`PathRefOwner`] holds an [`AtomicUsize`] so that cloning or +1426 /// dropping a [`PathRef`] (held by [`Path`] or [`WeakPathHandle`]) does not need +1427 /// to lock this state. +1428 /// +1429 /// [`WeakPathHandle`]: crate::path::WeakPathHandle +1430 pub(crate) path_refs: FxHashMap<PathId, PathRefOwner>, +1431 /// Final path stats for discarded paths. +1432 /// +1433 /// We only insert entries if the discarded path has a non-zero reference count in [`Self::path_refs`]. +1434 /// When the last reference to a path is dropped its entry is removed from both maps. +1435 pub(crate) final_path_stats: FxHashMap<PathId, PathStats>, +1436 pub(crate) path_events: tokio::sync::broadcast::Sender<PathEvent>, +1437 sender: Pin<Box<dyn UdpSender>>, +1438 pub(crate) runtime: Arc<dyn Runtime>, +1439 send_buffer: Vec<u8>, +1440 /// We buffer a transmit when the underlying I/O would block +1441 buffered_transmit: Option<proto::Transmit>, +1442 /// Our last external address reported by the peer. When multipath is enabled, this will be the +1443 /// last report across all paths. +1444 pub(crate) observed_external_addr: watch::Sender<Option<SocketAddr>>, +1445 pub(crate) nat_traversal_updates: tokio::sync::broadcast::Sender<n0_nat_traversal::Event>, +1446 on_closed: Vec<oneshot::Sender<Closed>>, +1447} +1448 +1449impl State { +1450 #[allow(clippy::too_many_arguments)] +1451 fn new( +1452 inner: proto::Connection, +1453 handle: ConnectionHandle, +1454 endpoint_events: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, +1455 conn_events: mpsc::UnboundedReceiver<ConnectionEvent>, +1456 on_handshake_data: oneshot::Sender<()>, +1457 on_connected: oneshot::Sender<bool>, +1458 sender: Pin<Box<dyn UdpSender>>, +1459 runtime: Arc<dyn Runtime>, +1460 ) -> Self { +1461 Self { +1462 inner, +1463 driver: None, +1464 handle, +1465 on_handshake_data: Some(on_handshake_data), +1466 on_connected: Some(on_connected), +1467 connected: false, +1468 handshake_confirmed: false, +1469 timer: None, +1470 timer_deadline: None, +1471 conn_events, +1472 endpoint_events, +1473 blocked_writers: FxHashMap::default(), +1474 blocked_readers: FxHashMap::default(), +1475 stopped: FxHashMap::default(), +1476 open_path: FxHashMap::default(), +1477 error: None, +1478 sender, +1479 runtime, +1480 send_buffer: Vec::new(), +1481 buffered_transmit: None, +1482 path_events: tokio::sync::broadcast::channel(32).0, +1483 observed_external_addr: watch::Sender::new(None), +1484 nat_traversal_updates: tokio::sync::broadcast::channel(32).0, +1485 on_closed: Vec::new(), +1486 final_path_stats: Default::default(), +1487 path_refs: Default::default(), +1488 } +1489 } +1490 +1491 fn drive_transmit(&mut self, cx: &mut Context<'_>) -> io::Result<bool> { +1492 let now = self.runtime.now(); +1493 let mut transmits = 0; +1494 +1495 let max_datagrams = self +1496 .sender +1497 .max_transmit_segments() +1498 .min(MAX_TRANSMIT_SEGMENTS); +1499 +1500 loop { +1501 // Retry the last transmit, or get a new one. +1502 let t = match self.buffered_transmit.take() { +1503 Some(t) => t, +1504 None => { +1505 self.send_buffer.clear(); +1506 match self +1507 .inner +1508 .poll_transmit(now, max_datagrams, &mut self.send_buffer) +1509 { +1510 Some(t) => { +1511 transmits += match t.segment_size { +1512 None => 1, +1513 Some(s) => t.size.div_ceil(s), // round up +1514 }; +1515 t +1516 } +1517 None => break, +1518 } +1519 } +1520 }; +1521 +1522 let len = t.size; +1523 match self +1524 .sender +1525 .as_mut() +1526 .poll_send(&udp_transmit(&t, &self.send_buffer[..len]), cx) +1527 { +1528 Poll::Pending => { +1529 self.buffered_transmit = Some(t); +1530 return Ok(false); +1531 } +1532 Poll::Ready(Err(e)) => return Err(e), +1533 Poll::Ready(Ok(())) => {} 1534 } -1535 } -1536 -1537 Ok(false) -1538 } -1539 -1540 fn forward_endpoint_events(&mut self) { -1541 while let Some(event) = self.inner.poll_endpoint_events() { -1542 // If the endpoint driver is gone, noop. -1543 let _ = self.endpoint_events.send((self.handle, event)); -1544 } -1545 } -1546 -1547 /// If this returns `Err`, the endpoint is dead, so the driver should exit immediately. -1548 fn process_conn_events( -1549 &mut self, -1550 shared: &Shared, -1551 cx: &mut Context<'_>, -1552 ) -> Result<(), ConnectionError> { -1553 loop { -1554 match self.conn_events.poll_recv(cx) { -1555 Poll::Ready(Some(ConnectionEvent::Rebind(sender))) => { -1556 self.sender = sender; -1557 self.inner.handle_network_change(None, self.runtime.now()); -1558 } -1559 Poll::Ready(Some(ConnectionEvent::LocalAddressChanged(hint))) => { -1560 self.inner -1561 .handle_network_change(hint.as_deref().map(|x| x as _), self.runtime.now()); -1562 } -1563 Poll::Ready(Some(ConnectionEvent::Proto(event))) => { -1564 self.inner.handle_event(event); -1565 } -1566 Poll::Ready(Some(ConnectionEvent::Close { reason, error_code })) => { -1567 self.close(error_code, reason, shared); -1568 } -1569 Poll::Ready(None) => { -1570 return Err(ConnectionError::TransportError(TransportError::new( -1571 TransportErrorCode::INTERNAL_ERROR, -1572 "endpoint driver future was dropped".to_string(), -1573 ))); -1574 } -1575 Poll::Pending => { -1576 return Ok(()); -1577 } -1578 } -1579 } -1580 } -1581 -1582 fn forward_app_events(&mut self, shared: &Shared) { -1583 while let Some(event) = self.inner.poll() { -1584 use proto::Event::*; -1585 match event { -1586 HandshakeDataReady => { -1587 if let Some(x) = self.on_handshake_data.take() { -1588 let _ = x.send(()); -1589 } -1590 } -1591 Connected => { -1592 self.connected = true; -1593 if let Some(x) = self.on_connected.take() { -1594 // We don't care if the on-connected future was dropped -1595 let _ = x.send(self.inner.accepted_0rtt()); -1596 } -1597 if self.inner.side().is_client() && !self.inner.accepted_0rtt() { -1598 // Wake up rejected 0-RTT streams so they can fail immediately with -1599 // `ZeroRttRejected` errors. -1600 wake_all(&mut self.blocked_writers); -1601 wake_all(&mut self.blocked_readers); -1602 wake_all_notify(&mut self.stopped); -1603 } -1604 } -1605 HandshakeConfirmed => { -1606 self.handshake_confirmed = true; -1607 shared.handshake_confirmed.notify_waiters(); -1608 } -1609 ConnectionLost { reason } => { -1610 self.terminate(reason, shared); -1611 } -1612 Stream(StreamEvent::Writable { id }) => wake_stream(id, &mut self.blocked_writers), -1613 Stream(StreamEvent::Opened { dir: Dir::Uni }) => { -1614 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); -1615 } -1616 Stream(StreamEvent::Opened { dir: Dir::Bi }) => { -1617 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); -1618 } -1619 DatagramReceived => { -1620 shared.datagram_received.notify_waiters(); -1621 } -1622 DatagramsUnblocked => { -1623 shared.datagrams_unblocked.notify_waiters(); -1624 } -1625 Stream(StreamEvent::Readable { id }) => wake_stream(id, &mut self.blocked_readers), -1626 Stream(StreamEvent::Available { dir }) => { -1627 // Might mean any number of streams are ready, so we wake up everyone -1628 shared.stream_budget_available[dir as usize].notify_waiters(); +1535 +1536 if transmits >= MAX_TRANSMIT_DATAGRAMS { +1537 // TODO: What isn't ideal here yet is that if we don't poll all +1538 // datagrams that could be sent we don't go into the `app_limited` +1539 // state and CWND continues to grow until we get here the next time. +1540 // See https://github.com/quinn-rs/quinn/issues/1126 +1541 return Ok(true); +1542 } +1543 } +1544 +1545 Ok(false) +1546 } +1547 +1548 fn forward_endpoint_events(&mut self) { +1549 while let Some(event) = self.inner.poll_endpoint_events() { +1550 // If the endpoint driver is gone, noop. +1551 let _ = self.endpoint_events.send((self.handle, event)); +1552 } +1553 } +1554 +1555 /// If this returns `Err`, the endpoint is dead, so the driver should exit immediately. +1556 fn process_conn_events( +1557 &mut self, +1558 shared: &Shared, +1559 cx: &mut Context<'_>, +1560 ) -> Result<(), ConnectionError> { +1561 loop { +1562 match self.conn_events.poll_recv(cx) { +1563 Poll::Ready(Some(ConnectionEvent::Rebind(sender))) => { +1564 self.sender = sender; +1565 self.inner.handle_network_change(None, self.runtime.now()); +1566 } +1567 Poll::Ready(Some(ConnectionEvent::LocalAddressChanged(hint))) => { +1568 self.inner +1569 .handle_network_change(hint.as_deref().map(|x| x as _), self.runtime.now()); +1570 } +1571 Poll::Ready(Some(ConnectionEvent::Proto(event))) => { +1572 self.inner.handle_event(event); +1573 } +1574 Poll::Ready(Some(ConnectionEvent::Close { reason, error_code })) => { +1575 self.close(error_code, reason, shared); +1576 } +1577 Poll::Ready(None) => { +1578 return Err(ConnectionError::TransportError(TransportError::new( +1579 TransportErrorCode::INTERNAL_ERROR, +1580 "endpoint driver future was dropped".to_string(), +1581 ))); +1582 } +1583 Poll::Pending => { +1584 return Ok(()); +1585 } +1586 } +1587 } +1588 } +1589 +1590 fn forward_app_events(&mut self, shared: &Shared) { +1591 while let Some(event) = self.inner.poll() { +1592 use proto::Event::*; +1593 match event { +1594 HandshakeDataReady => { +1595 if let Some(x) = self.on_handshake_data.take() { +1596 let _ = x.send(()); +1597 } +1598 } +1599 Connected => { +1600 self.connected = true; +1601 if let Some(x) = self.on_connected.take() { +1602 // We don't care if the on-connected future was dropped +1603 let _ = x.send(self.inner.accepted_0rtt()); +1604 } +1605 if self.inner.side().is_client() && !self.inner.accepted_0rtt() { +1606 // Wake up rejected 0-RTT streams so they can fail immediately with +1607 // `ZeroRttRejected` errors. +1608 wake_all(&mut self.blocked_writers); +1609 wake_all(&mut self.blocked_readers); +1610 wake_all_notify(&mut self.stopped); +1611 } +1612 } +1613 HandshakeConfirmed => { +1614 self.handshake_confirmed = true; +1615 shared.handshake_confirmed.notify_waiters(); +1616 } +1617 ConnectionLost { reason } => { +1618 self.terminate(reason, shared); +1619 } +1620 Stream(StreamEvent::Writable { id }) => wake_stream(id, &mut self.blocked_writers), +1621 Stream(StreamEvent::Opened { dir: Dir::Uni }) => { +1622 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); +1623 } +1624 Stream(StreamEvent::Opened { dir: Dir::Bi }) => { +1625 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); +1626 } +1627 DatagramReceived => { +1628 shared.datagram_received.notify_waiters(); 1629 } -1630 Stream(StreamEvent::Finished { id }) => wake_stream_notify(id, &mut self.stopped), -1631 Stream(StreamEvent::Stopped { id, .. }) => { -1632 wake_stream_notify(id, &mut self.stopped); -1633 wake_stream(id, &mut self.blocked_writers); -1634 } -1635 Path(ref evt @ PathEvent::ObservedAddr { addr: observed, .. }) => { -1636 self.path_events.send(evt.clone()).ok(); -1637 self.observed_external_addr.send_if_modified(|addr| { -1638 let old = addr.replace(observed); -1639 old != *addr -1640 }); -1641 } -1642 Path(ref evt @ PathEvent::Established { id }) => { -1643 self.path_events.send(evt.clone()).ok(); -1644 if let Some(sender) = self.open_path.remove(&id) { -1645 sender.send_modify(|value| *value = Ok(())); -1646 } -1647 } -1648 Path(ref evt @ PathEvent::Discarded { id, ref path_stats }) => { -1649 if self.path_refs.contains_key(&id) { -1650 self.final_path_stats.insert(id, *path_stats.clone()); -1651 } -1652 self.path_events.send(evt.clone()).ok(); -1653 } -1654 Path(ref evt @ PathEvent::Abandoned { id, .. }) => { -1655 if let Some(sender) = self.open_path.remove(&id) { -1656 // We don't care for the reason why this path was closed here, because semantically -1657 // all close reasons for a path that has not yet been opened equals to `ValidationFailed`. -1658 // With the noq API, there is no way to application-close a not-yet-opened path, so -1659 // `ApplicationClosed` cannot occur. And all other variants will only occur for paths -1660 // that have already been opened. -1661 // The previous iteration of this code had another event `PathEvent::LocallyClosed` which -1662 // contained a `PathError`, but that was only ever set to `ValidationFailed`. -1663 let error = PathError::ValidationFailed; -1664 sender.send_modify(|value| *value = Err(error)); -1665 } -1666 // this will happen also for already opened paths -1667 self.path_events.send(evt.clone()).ok(); -1668 } -1669 Path(evt @ PathEvent::RemoteStatus { .. }) => { -1670 self.path_events.send(evt).ok(); -1671 } -1672 NatTraversal(update) => { -1673 self.nat_traversal_updates.send(update).ok(); -1674 } -1675 } -1676 } -1677 } -1678 -1679 fn drive_timer(&mut self, cx: &mut Context<'_>) -> bool { -1680 // Check whether we need to (re)set the timer. If so, we must poll again to ensure the -1681 // timer is registered with the runtime (and check whether it's already -1682 // expired). -1683 match self.inner.poll_timeout() { -1684 Some(deadline) => { -1685 if let Some(delay) = &mut self.timer { -1686 // There is no need to reset the tokio timer if the deadline -1687 // did not change -1688 if self -1689 .timer_deadline -1690 .map(|current_deadline| current_deadline != deadline) -1691 .unwrap_or(true) -1692 { -1693 delay.as_mut().reset(deadline); -1694 } -1695 } else { -1696 self.timer = Some(self.runtime.new_timer(deadline)); -1697 } -1698 // Store the actual expiration time of the timer -1699 self.timer_deadline = Some(deadline); -1700 } -1701 None => { -1702 self.timer_deadline = None; -1703 return false; -1704 } -1705 } -1706 -1707 if self.timer_deadline.is_none() { -1708 return false; -1709 } -1710 -1711 let delay = self -1712 .timer -1713 .as_mut() -1714 .expect("timer must exist in this state") -1715 .as_mut(); -1716 if delay.poll(cx).is_pending() { -1717 // Since there wasn't a timeout event, there is nothing new -1718 // for the connection to do -1719 return false; -1720 } -1721 -1722 // A timer expired, so the caller needs to check for -1723 // new transmits, which might cause new timers to be set. -1724 self.inner.handle_timeout(self.runtime.now()); -1725 self.timer_deadline = None; -1726 true -1727 } -1728 -1729 /// Wake up a blocked `Driver` task to process I/O -1730 pub(crate) fn wake(&mut self) { -1731 if let Some(x) = self.driver.take() { -1732 x.wake(); -1733 } -1734 } -1735 -1736 /// Used to wake up all blocked futures when the connection becomes closed for any reason -1737 fn terminate(&mut self, reason: ConnectionError, shared: &Shared) { -1738 self.error = Some(reason.clone()); -1739 if let Some(x) = self.on_handshake_data.take() { -1740 let _ = x.send(()); +1630 DatagramsUnblocked => { +1631 shared.datagrams_unblocked.notify_waiters(); +1632 } +1633 Stream(StreamEvent::Readable { id }) => wake_stream(id, &mut self.blocked_readers), +1634 Stream(StreamEvent::Available { dir }) => { +1635 // Might mean any number of streams are ready, so we wake up everyone +1636 shared.stream_budget_available[dir as usize].notify_waiters(); +1637 } +1638 Stream(StreamEvent::Finished { id }) => wake_stream_notify(id, &mut self.stopped), +1639 Stream(StreamEvent::Stopped { id, .. }) => { +1640 wake_stream_notify(id, &mut self.stopped); +1641 wake_stream(id, &mut self.blocked_writers); +1642 } +1643 Path(ref evt @ PathEvent::ObservedAddr { addr: observed, .. }) => { +1644 self.path_events.send(evt.clone()).ok(); +1645 self.observed_external_addr.send_if_modified(|addr| { +1646 let old = addr.replace(observed); +1647 old != *addr +1648 }); +1649 } +1650 Path(ref evt @ PathEvent::Established { id, .. }) => { +1651 self.path_events.send(evt.clone()).ok(); +1652 if let Some(sender) = self.open_path.remove(&id) { +1653 sender.send_modify(|value| *value = Ok(())); +1654 } +1655 } +1656 Path( +1657 ref evt @ PathEvent::Discarded { +1658 id, ref path_stats, .. +1659 }, +1660 ) => { +1661 if self.path_refs.contains_key(&id) { +1662 self.final_path_stats.insert(id, *path_stats.clone()); +1663 } +1664 self.path_events.send(evt.clone()).ok(); +1665 } +1666 Path(ref evt @ PathEvent::Abandoned { id, .. }) => { +1667 if let Some(sender) = self.open_path.remove(&id) { +1668 // We don't care for the reason why this path was closed here, because semantically +1669 // all close reasons for a path that has not yet been opened equals to `ValidationFailed`. +1670 // With the noq API, there is no way to application-close a not-yet-opened path, so +1671 // `ApplicationClosed` cannot occur. And all other variants will only occur for paths +1672 // that have already been opened. +1673 // The previous iteration of this code had another event `PathEvent::LocallyClosed` which +1674 // contained a `PathError`, but that was only ever set to `ValidationFailed`. +1675 let error = PathError::ValidationFailed; +1676 sender.send_modify(|value| *value = Err(error)); +1677 } +1678 // this will happen also for already opened paths +1679 self.path_events.send(evt.clone()).ok(); +1680 } +1681 Path(evt @ PathEvent::RemoteStatus { .. }) => { +1682 self.path_events.send(evt).ok(); +1683 } +1684 NatTraversal(update) => { +1685 self.nat_traversal_updates.send(update).ok(); +1686 } +1687 _ => { +1688 // PathEvent is #[non_exhaustive]. +1689 // It's possible that noq is built against a newer noq-proto version. +1690 // In that case, we need to ignore path events we can't handle yet. +1691 // But for tests, we expect noq and noq-proto to be in sync, so we +1692 // should panic in case we don't actually handle new cases. +1693 #[cfg(test)] +1694 panic!("Unhandled PathEvent variant: {event:?}"); +1695 } +1696 } +1697 } +1698 } +1699 +1700 fn drive_timer(&mut self, cx: &mut Context<'_>) -> bool { +1701 // Check whether we need to (re)set the timer. If so, we must poll again to ensure the +1702 // timer is registered with the runtime (and check whether it's already +1703 // expired). +1704 match self.inner.poll_timeout() { +1705 Some(deadline) => { +1706 if let Some(delay) = &mut self.timer { +1707 // There is no need to reset the tokio timer if the deadline +1708 // did not change +1709 if self +1710 .timer_deadline +1711 .map(|current_deadline| current_deadline != deadline) +1712 .unwrap_or(true) +1713 { +1714 delay.as_mut().reset(deadline); +1715 } +1716 } else { +1717 self.timer = Some(self.runtime.new_timer(deadline)); +1718 } +1719 // Store the actual expiration time of the timer +1720 self.timer_deadline = Some(deadline); +1721 } +1722 None => { +1723 self.timer_deadline = None; +1724 return false; +1725 } +1726 } +1727 +1728 if self.timer_deadline.is_none() { +1729 return false; +1730 } +1731 +1732 let delay = self +1733 .timer +1734 .as_mut() +1735 .expect("timer must exist in this state") +1736 .as_mut(); +1737 if delay.poll(cx).is_pending() { +1738 // Since there wasn't a timeout event, there is nothing new +1739 // for the connection to do +1740 return false; 1741 } -1742 wake_all(&mut self.blocked_writers); -1743 wake_all(&mut self.blocked_readers); -1744 shared.stream_budget_available[Dir::Uni as usize].notify_waiters(); -1745 shared.stream_budget_available[Dir::Bi as usize].notify_waiters(); -1746 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); -1747 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); -1748 shared.datagram_received.notify_waiters(); -1749 shared.datagrams_unblocked.notify_waiters(); -1750 if let Some(x) = self.on_connected.take() { -1751 let _ = x.send(false); -1752 } -1753 shared.handshake_confirmed.notify_waiters(); -1754 wake_all_notify(&mut self.stopped); -1755 shared.closed.notify_waiters(); +1742 +1743 // A timer expired, so the caller needs to check for +1744 // new transmits, which might cause new timers to be set. +1745 self.inner.handle_timeout(self.runtime.now()); +1746 self.timer_deadline = None; +1747 true +1748 } +1749 +1750 /// Wake up a blocked `Driver` task to process I/O +1751 pub(crate) fn wake(&mut self) { +1752 if let Some(x) = self.driver.take() { +1753 x.wake(); +1754 } +1755 } 1756 -1757 // Send to the registered on_closed futures. -1758 if !self.on_closed.is_empty() { -1759 let closed = Closed::new(self, reason); -1760 for tx in self.on_closed.drain(..) { -1761 tx.send(closed.clone()).ok(); -1762 } -1763 } -1764 } -1765 -1766 fn close(&mut self, error_code: VarInt, reason: Bytes, shared: &Shared) { -1767 self.inner.close(self.runtime.now(), error_code, reason); -1768 self.terminate(ConnectionError::LocallyClosed, shared); -1769 self.wake(); -1770 } -1771 -1772 /// Close for a reason other than the application's explicit request -1773 pub(crate) fn implicit_close(&mut self, shared: &Shared) { -1774 self.close(0u32.into(), Bytes::new(), shared); -1775 } -1776 -1777 pub(crate) fn check_0rtt(&self) -> Result<(), ()> { -1778 if self.inner.is_handshaking() -1779 || self.inner.accepted_0rtt() -1780 || self.inner.side().is_server() -1781 { -1782 Ok(()) -1783 } else { -1784 Err(()) -1785 } -1786 } -1787 -1788 /// Returns [`PathStats`] for a path, if available. -1789 /// -1790 /// This gets the stats from [`proto::Connection`]. If that returns `None` -1791 /// it gets them from `Self::final_path_stats` instead. -1792 pub(crate) fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { -1793 self.inner -1794 .path_stats(path_id) -1795 .or_else(|| self.final_path_stats.get(&path_id).copied()) +1757 /// Used to wake up all blocked futures when the connection becomes closed for any reason +1758 fn terminate(&mut self, reason: ConnectionError, shared: &Shared) { +1759 self.error = Some(reason.clone()); +1760 if let Some(x) = self.on_handshake_data.take() { +1761 let _ = x.send(()); +1762 } +1763 wake_all(&mut self.blocked_writers); +1764 wake_all(&mut self.blocked_readers); +1765 shared.stream_budget_available[Dir::Uni as usize].notify_waiters(); +1766 shared.stream_budget_available[Dir::Bi as usize].notify_waiters(); +1767 shared.stream_incoming[Dir::Uni as usize].notify_waiters(); +1768 shared.stream_incoming[Dir::Bi as usize].notify_waiters(); +1769 shared.datagram_received.notify_waiters(); +1770 shared.datagrams_unblocked.notify_waiters(); +1771 if let Some(x) = self.on_connected.take() { +1772 let _ = x.send(false); +1773 } +1774 shared.handshake_confirmed.notify_waiters(); +1775 wake_all_notify(&mut self.stopped); +1776 shared.closed.notify_waiters(); +1777 +1778 // Send to the registered on_closed futures. +1779 if !self.on_closed.is_empty() { +1780 let closed = Closed::new(self, reason); +1781 for tx in self.on_closed.drain(..) { +1782 tx.send(closed.clone()).ok(); +1783 } +1784 } +1785 } +1786 +1787 fn close(&mut self, error_code: VarInt, reason: Bytes, shared: &Shared) { +1788 self.inner.close(self.runtime.now(), error_code, reason); +1789 self.terminate(ConnectionError::LocallyClosed, shared); +1790 self.wake(); +1791 } +1792 +1793 /// Close for a reason other than the application's explicit request +1794 pub(crate) fn implicit_close(&mut self, shared: &Shared) { +1795 self.close(0u32.into(), Bytes::new(), shared); 1796 } 1797 -1798 /// Acquire a new [`PathRef`] for a path id, bumping its reference counter by 1. -1799 /// -1800 /// The returned [`PathRef`] is intended to be stored on a [`Path`] or [`WeakPathHandle`]. -1801 /// Its reference count is automatically increased when cloned. When its owner is dropped, -1802 /// [`PathRef::on_drop`] must be called to decrement the refcount. -1803 /// -1804 /// [`WeakPathHandle`]: crate::path::WeakPathHandle -1805 pub(crate) fn acquire_path_ref(&mut self, path_id: PathId) -> PathRef { -1806 self.path_refs.entry(path_id).or_default().acquire(path_id) +1798 pub(crate) fn check_0rtt(&self) -> Result<(), ()> { +1799 if self.inner.is_handshaking() +1800 || self.inner.accepted_0rtt() +1801 || self.inner.side().is_server() +1802 { +1803 Ok(()) +1804 } else { +1805 Err(()) +1806 } 1807 } -1808} -1809 -1810impl Drop for State { -1811 fn drop(&mut self) { -1812 if !self.inner.is_drained() { -1813 // Ensure the endpoint can tidy up -1814 let _ = self -1815 .endpoint_events -1816 .send((self.handle, proto::EndpointEvent::drained())); -1817 } +1808 +1809 /// Returns [`PathStats`] for a path, if available. +1810 /// +1811 /// This gets the stats from [`proto::Connection`]. If that returns `None` +1812 /// it gets them from `Self::final_path_stats` instead. +1813 pub(crate) fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { +1814 self.inner +1815 .path_stats(path_id) +1816 .or_else(|| self.final_path_stats.get(&path_id).copied()) +1817 } 1818 -1819 if !self.on_closed.is_empty() -1820 && let Some(reason) = self.error.clone() -1821 { -1822 // Ensure that all on_closed oneshot senders are triggered before dropping. -1823 let closed = Closed::new(self, reason); -1824 for tx in self.on_closed.drain(..) { -1825 tx.send(closed.clone()).ok(); -1826 } -1827 } +1819 /// Acquire a new [`PathRef`] for a path id, bumping its reference counter by 1. +1820 /// +1821 /// The returned [`PathRef`] is intended to be stored on a [`Path`] or [`WeakPathHandle`]. +1822 /// Its reference count is automatically increased when cloned. When its owner is dropped, +1823 /// [`PathRef::on_drop`] must be called to decrement the refcount. +1824 /// +1825 /// [`WeakPathHandle`]: crate::path::WeakPathHandle +1826 pub(crate) fn acquire_path_ref(&mut self, path_id: PathId) -> PathRef { +1827 self.path_refs.entry(path_id).or_default().acquire(path_id) 1828 } 1829} 1830 -1831impl fmt::Debug for State { -1832 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -1833 f.debug_struct("State").field("inner", &self.inner).finish() -1834 } -1835} -1836 -1837fn wake_stream(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Waker>) { -1838 if let Some(waker) = wakers.remove(&stream_id) { -1839 waker.wake(); -1840 } -1841} -1842 -1843fn wake_all(wakers: &mut FxHashMap<StreamId, Waker>) { -1844 wakers.drain().for_each(|(_, waker)| waker.wake()) -1845} -1846 -1847fn wake_stream_notify(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { -1848 if let Some(notify) = wakers.remove(&stream_id) { -1849 notify.notify_waiters() -1850 } -1851} -1852 -1853fn wake_all_notify(wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { -1854 wakers -1855 .drain() -1856 .for_each(|(_, notify)| notify.notify_waiters()) -1857} -1858 -1859/// Errors that can arise when sending a datagram -1860#[derive(Debug, Error, Clone, Eq, PartialEq)] -1861pub enum SendDatagramError { -1862 /// The peer does not support receiving datagram frames -1863 #[error("datagrams not supported by peer")] -1864 UnsupportedByPeer, -1865 /// Datagram support is disabled locally -1866 #[error("datagram support disabled")] -1867 Disabled, -1868 /// The datagram is larger than the connection can currently accommodate -1869 /// -1870 /// Indicates that the path MTU minus overhead or the limit advertised by the peer has been -1871 /// exceeded. -1872 #[error("datagram too large")] -1873 TooLarge, -1874 /// The connection was lost -1875 #[error("connection lost")] -1876 ConnectionLost(#[from] ConnectionError), -1877} -1878 -1879/// The maximum amount of datagrams which will be produced in a single `drive_transmit` call -1880/// -1881/// This limits the amount of CPU resources consumed by datagram generation, -1882/// and allows other tasks (like receiving ACKs) to run in between. -1883const MAX_TRANSMIT_DATAGRAMS: usize = 20; -1884 -1885/// The maximum amount of datagrams that are sent in a single transmit -1886/// -1887/// This can be lower than the maximum platform capabilities, to avoid excessive -1888/// memory allocations when calling `poll_transmit()`. Benchmarks have shown -1889/// that numbers around 10 are a good compromise. -1890const MAX_TRANSMIT_SEGMENTS: NonZeroUsize = NonZeroUsize::new(10).expect("known");

\ No newline at end of file +1831impl Drop for State { +1832 fn drop(&mut self) { +1833 if !self.inner.is_drained() { +1834 // Ensure the endpoint can tidy up +1835 let _ = self +1836 .endpoint_events +1837 .send((self.handle, proto::EndpointEvent::drained())); +1838 } +1839 +1840 if !self.on_closed.is_empty() +1841 && let Some(reason) = self.error.clone() +1842 { +1843 // Ensure that all on_closed oneshot senders are triggered before dropping. +1844 let closed = Closed::new(self, reason); +1845 for tx in self.on_closed.drain(..) { +1846 tx.send(closed.clone()).ok(); +1847 } +1848 } +1849 } +1850} +1851 +1852impl fmt::Debug for State { +1853 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +1854 f.debug_struct("State").field("inner", &self.inner).finish() +1855 } +1856} +1857 +1858fn wake_stream(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Waker>) { +1859 if let Some(waker) = wakers.remove(&stream_id) { +1860 waker.wake(); +1861 } +1862} +1863 +1864fn wake_all(wakers: &mut FxHashMap<StreamId, Waker>) { +1865 wakers.drain().for_each(|(_, waker)| waker.wake()) +1866} +1867 +1868fn wake_stream_notify(stream_id: StreamId, wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { +1869 if let Some(notify) = wakers.remove(&stream_id) { +1870 notify.notify_waiters() +1871 } +1872} +1873 +1874fn wake_all_notify(wakers: &mut FxHashMap<StreamId, Arc<Notify>>) { +1875 wakers +1876 .drain() +1877 .for_each(|(_, notify)| notify.notify_waiters()) +1878} +1879 +1880/// Errors that can arise when sending a datagram +1881#[derive(Debug, Error, Clone, Eq, PartialEq)] +1882pub enum SendDatagramError { +1883 /// The peer does not support receiving datagram frames +1884 #[error("datagrams not supported by peer")] +1885 UnsupportedByPeer, +1886 /// Datagram support is disabled locally +1887 #[error("datagram support disabled")] +1888 Disabled, +1889 /// The datagram is larger than the connection can currently accommodate +1890 /// +1891 /// Indicates that the path MTU minus overhead or the limit advertised by the peer has been +1892 /// exceeded. +1893 #[error("datagram too large")] +1894 TooLarge, +1895 /// The connection was lost +1896 #[error("connection lost")] +1897 ConnectionLost(#[from] ConnectionError), +1898} +1899 +1900/// The maximum amount of datagrams which will be produced in a single `drive_transmit` call +1901/// +1902/// This limits the amount of CPU resources consumed by datagram generation, +1903/// and allows other tasks (like receiving ACKs) to run in between. +1904const MAX_TRANSMIT_DATAGRAMS: usize = 20; +1905 +1906/// The maximum amount of datagrams that are sent in a single transmit +1907/// +1908/// This can be lower than the maximum platform capabilities, to avoid excessive +1909/// memory allocations when calling `poll_transmit()`. Benchmarks have shown +1910/// that numbers around 10 are a good compromise. +1911const MAX_TRANSMIT_SEGMENTS: NonZeroUsize = NonZeroUsize::new(10).expect("known");
\ No newline at end of file diff --git a/pr/451/docs/src/noq/endpoint.rs.html b/pr/451/docs/src/noq/endpoint.rs.html index d03c5a42c..3a889929b 100644 --- a/pr/451/docs/src/noq/endpoint.rs.html +++ b/pr/451/docs/src/noq/endpoint.rs.html @@ -1,4 +1,4 @@ -endpoint.rs - source

noq/
endpoint.rs

1use std::{
+endpoint.rs - source

noq/
endpoint.rs

1use std::{
 2    collections::VecDeque,
 3    fmt,
 4    future::Future,
@@ -366,16 +366,16 @@
 366        }
 367    }
 368
-369    /// Wait for all connections on the endpoint to be cleanly shut down
+369    /// Waits for all connections on the endpoint to be cleanly shut down and drained.
 370    ///
-371    /// Waiting for this condition before exiting ensures that a good-faith effort is made to notify
-372    /// peers of recent connection closes, whereas exiting immediately could force them to wait out
-373    /// the idle timeout period.
-374    ///
-375    /// Does not proactively close existing connections or cause incoming connections to be
-376    /// rejected. Consider calling [`close()`] if that is desired.
-377    ///
-378    /// [`close()`]: Endpoint::close
+371    /// This is equivalent to [`wait_all_draining()`] with additionally waiting for the connections to be
+372    /// drained. Please see its documentation for more information.
+373    ///
+374    /// Use `wait_idle()` in favor of `wait_all_draining()` if you care about waiting for the
+375    /// [`Connection`] structs to be dropped.
+376    ///
+377    /// [`wait_all_draining()`]: Self::wait_all_draining
+378    /// [`Connection`]: crate::Connection
 379    pub async fn wait_idle(&self) {
 380        loop {
 381            {
@@ -389,594 +389,654 @@
 389            .await;
 390        }
 391    }
-392}
-393
-394/// Statistics on [Endpoint] activity
-395#[non_exhaustive]
-396#[derive(Debug, Default, Copy, Clone)]
-397pub struct EndpointStats {
-398    /// Cumulative number of Quic handshakes accepted by this [Endpoint]
-399    pub accepted_handshakes: u64,
-400    /// Cumulative number of Quic handshakes sent from this [Endpoint]
-401    pub outgoing_handshakes: u64,
-402    /// Cumulative number of Quic handshakes refused on this [Endpoint]
-403    pub refused_handshakes: u64,
-404    /// Cumulative number of Quic handshakes ignored on this [Endpoint]
-405    pub ignored_handshakes: u64,
-406}
-407
-408/// A future that drives IO on an endpoint
-409///
-410/// This task functions as the switch point between the UDP socket object and the
-411/// `Endpoint` responsible for routing datagrams to their owning `Connection`.
-412/// In order to do so, it also facilitates the exchange of different types of events
-413/// flowing between the `Endpoint` and the tasks managing `Connection`s. As such,
-414/// running this task is necessary to keep the endpoint's connections running.
-415///
-416/// `EndpointDriver` futures terminate when all clones of the `Endpoint` have been dropped, or when
-417/// an I/O error occurs.
-418#[must_use = "endpoint drivers must be spawned for I/O to occur"]
-419#[derive(Debug)]
-420pub(crate) struct EndpointDriver(pub(crate) EndpointRef);
-421
-422impl Future for EndpointDriver {
-423    type Output = Result<(), io::Error>;
+392
+393    /// Waits for all connections on the endpoint to be ready for shutting down.
+394    ///
+395    /// Waiting for this condition before exiting ensures that a good-faith effort is made to notify
+396    /// peers of recent connection closes, whereas exiting immediately could force them to wait out
+397    /// the idle timeout period.
+398    ///
+399    /// Does not proactively close existing connections or cause incoming connections to be
+400    /// rejected. Consider calling [`close()`] if that is desired.
+401    ///
+402    /// Unlike [`wait_idle()`], this doesn't wait for the full draining period, so it can't be
+403    /// used to wait for all now-idle [`Connection`]s to be dropped.
+404    ///
+405    /// See also this section in the QUIC RFC: <https://datatracker.ietf.org/doc/html/rfc9000#section-10.2-6>
+406    ///
+407    /// [`close()`]: Self::close
+408    /// [`wait_idle()`]: Self::wait_idle
+409    /// [`Connection`]: crate::Connection
+410    pub async fn wait_all_draining(&self) {
+411        loop {
+412            {
+413                let endpoint = &mut *self.inner.state.lock().unwrap();
+414                if endpoint.recv_state.connections.active_connections == 0 {
+415                    break;
+416                }
+417                // Construct future while lock is held to avoid race
+418                self.inner.shared.all_draining.notified()
+419            }
+420            .await;
+421        }
+422    }
+423}
 424
-425    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-426        let mut endpoint = self.0.state.lock().unwrap();
-427        if endpoint.driver.is_none() {
-428            endpoint.driver = Some(cx.waker().clone());
-429        }
-430
-431        let now = endpoint.runtime.now();
-432        let mut keep_going = false;
-433        keep_going |= endpoint.drive_recv(cx, now)?;
-434        keep_going |= endpoint.handle_events(cx, &self.0.shared);
-435
-436        if !endpoint.recv_state.incoming.is_empty() {
-437            self.0.shared.incoming.notify_waiters();
-438        }
-439
-440        // Stop the driver if either:
-441        // - all `Endpoint` structs are dropped and all connections are drained,
-442        // - or `Endpoint::close` has been called and all connections are drained.
-443        if endpoint.recv_state.connections.is_empty()
-444            && (self.0.shared.ref_count.load(Ordering::Relaxed) == 0
-445                || endpoint.recv_state.connections.close.is_some())
-446        {
-447            trace!("endpoint driver stopping");
-448            Poll::Ready(Ok(()))
-449        } else {
-450            drop(endpoint);
-451            // If there is more work to do schedule the endpoint task again.
-452            // `wake_by_ref()` is called outside the lock to minimize
-453            // lock contention on a multithreaded runtime.
-454            if keep_going {
-455                cx.waker().wake_by_ref();
-456            }
-457            Poll::Pending
-458        }
-459    }
-460}
+425/// Statistics on [Endpoint] activity
+426#[non_exhaustive]
+427#[derive(Debug, Default, Copy, Clone)]
+428pub struct EndpointStats {
+429    /// Cumulative number of Quic handshakes accepted by this [Endpoint]
+430    pub accepted_handshakes: u64,
+431    /// Cumulative number of Quic handshakes sent from this [Endpoint]
+432    pub outgoing_handshakes: u64,
+433    /// Cumulative number of Quic handshakes refused on this [Endpoint]
+434    pub refused_handshakes: u64,
+435    /// Cumulative number of Quic handshakes ignored on this [Endpoint]
+436    pub ignored_handshakes: u64,
+437}
+438
+439/// A future that drives IO on an endpoint
+440///
+441/// This task functions as the switch point between the UDP socket object and the
+442/// `Endpoint` responsible for routing datagrams to their owning `Connection`.
+443/// In order to do so, it also facilitates the exchange of different types of events
+444/// flowing between the `Endpoint` and the tasks managing `Connection`s. As such,
+445/// running this task is necessary to keep the endpoint's connections running.
+446///
+447/// `EndpointDriver` futures terminate when all clones of the `Endpoint` have been dropped, or when
+448/// an I/O error occurs.
+449#[must_use = "endpoint drivers must be spawned for I/O to occur"]
+450#[derive(Debug)]
+451pub(crate) struct EndpointDriver(pub(crate) EndpointRef);
+452
+453impl Future for EndpointDriver {
+454    type Output = Result<(), io::Error>;
+455
+456    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+457        let mut endpoint = self.0.state.lock().unwrap();
+458        if endpoint.driver.is_none() {
+459            endpoint.driver = Some(cx.waker().clone());
+460        }
 461
-462impl Drop for EndpointDriver {
-463    fn drop(&mut self) {
-464        let mut endpoint = self.0.state.lock().unwrap();
-465        endpoint.driver_lost = true;
-466        self.0.shared.incoming.notify_waiters();
-467        // Drop all outgoing channels, signaling the termination of the endpoint to the associated
-468        // connections.
-469        endpoint.recv_state.connections.senders.clear();
-470    }
-471}
-472
-473#[derive(Debug)]
-474pub(crate) struct EndpointInner {
-475    pub(crate) state: Mutex<State>,
-476    pub(crate) shared: Shared,
-477}
-478
-479impl EndpointInner {
-480    pub(crate) fn accept(
-481        &self,
-482        incoming: proto::Incoming,
-483        server_config: Option<Arc<ServerConfig>>,
-484    ) -> Result<Connecting, ConnectionError> {
-485        let mut state = self.state.lock().unwrap();
-486        let mut response_buffer = Vec::new();
-487        let now = state.runtime.now();
-488        match state
-489            .inner
-490            .accept(incoming, now, &mut response_buffer, server_config)
-491        {
-492            Ok((handle, conn)) => {
-493                state.stats.accepted_handshakes += 1;
-494                let sender = state.socket.create_sender();
-495                let runtime = state.runtime.clone();
-496                Ok(state
-497                    .recv_state
-498                    .connections
-499                    .insert(handle, conn, sender, runtime))
-500            }
-501            Err(error) => {
-502                if let Some(transmit) = error.response {
-503                    respond(transmit, &response_buffer, &mut state.sender);
-504                }
-505                Err(error.cause)
-506            }
-507        }
-508    }
-509
-510    pub(crate) fn refuse(&self, incoming: proto::Incoming) {
-511        let mut state = self.state.lock().unwrap();
-512        state.stats.refused_handshakes += 1;
-513        let mut response_buffer = Vec::new();
-514        let transmit = state.inner.refuse(incoming, &mut response_buffer);
-515        respond(transmit, &response_buffer, &mut state.sender);
-516    }
-517
-518    pub(crate) fn retry(&self, incoming: proto::Incoming) -> Result<(), proto::RetryError> {
-519        let mut state = self.state.lock().unwrap();
-520        let mut response_buffer = Vec::new();
-521        let transmit = state.inner.retry(incoming, &mut response_buffer)?;
-522        respond(transmit, &response_buffer, &mut state.sender);
-523        Ok(())
-524    }
-525
-526    pub(crate) fn ignore(&self, incoming: proto::Incoming) {
-527        let mut state = self.state.lock().unwrap();
-528        state.stats.ignored_handshakes += 1;
-529        state.inner.ignore(incoming);
-530    }
-531}
-532
-533#[derive(Debug)]
-534pub(crate) struct State {
-535    socket: Box<dyn AsyncUdpSocket>,
-536    sender: Pin<Box<dyn UdpSender>>,
-537    /// During an active migration, abandoned_socket receives traffic
-538    /// until the first packet arrives on the new socket.
-539    prev_socket: Option<Box<dyn AsyncUdpSocket>>,
-540    inner: proto::Endpoint,
-541    recv_state: RecvState,
-542    driver: Option<Waker>,
-543    ipv6: bool,
-544    events: mpsc::UnboundedReceiver<(ConnectionHandle, EndpointEvent)>,
-545    driver_lost: bool,
-546    runtime: Arc<dyn Runtime>,
-547    stats: EndpointStats,
-548    default_client_config: Option<ClientConfig>,
-549}
-550
-551#[derive(Debug)]
-552pub(crate) struct Shared {
-553    incoming: Notify,
-554    idle: Notify,
-555    /// Number of live handles that can be used to initiate or handle I/O; excludes the driver
-556    ref_count: AtomicUsize,
-557}
-558
-559impl State {
-560    fn drive_recv(&mut self, cx: &mut Context<'_>, now: Instant) -> Result<bool, io::Error> {
-561        let get_time = || self.runtime.now();
-562        self.recv_state.recv_limiter.start_cycle(get_time);
-563        if let Some(socket) = &mut self.prev_socket {
-564            // We don't care about the `PollProgress` from old sockets.
-565            let poll_res = self.recv_state.poll_socket(
-566                cx,
-567                &mut self.inner,
-568                &mut **socket,
-569                &mut self.sender,
-570                &*self.runtime,
-571                now,
-572            );
-573            if poll_res.is_err() {
-574                self.prev_socket = None;
-575            }
-576        };
-577        let poll_res = self.recv_state.poll_socket(
-578            cx,
-579            &mut self.inner,
-580            &mut *self.socket,
-581            &mut self.sender,
-582            &*self.runtime,
-583            now,
-584        );
-585        self.recv_state.recv_limiter.finish_cycle(get_time);
-586        let poll_res = poll_res?;
-587        if poll_res.received_connection_packet {
-588            // Traffic has arrived on self.socket, therefore there is no need for the abandoned
-589            // one anymore. TODO: Account for multiple outgoing connections.
-590            self.prev_socket = None;
-591        }
-592        Ok(poll_res.keep_going)
-593    }
-594
-595    fn handle_events(&mut self, cx: &mut Context<'_>, shared: &Shared) -> bool {
-596        for _ in 0..IO_LOOP_BOUND {
-597            let (ch, event) = match self.events.poll_recv(cx) {
-598                Poll::Ready(Some(x)) => x,
-599                Poll::Ready(None) => unreachable!("EndpointInner owns one sender"),
-600                Poll::Pending => {
-601                    return false;
-602                }
-603            };
-604
-605            if event.is_drained() {
-606                self.recv_state.connections.senders.remove(&ch);
-607                if self.recv_state.connections.is_empty() {
-608                    shared.idle.notify_waiters();
-609                }
-610            }
-611            let Some(event) = self.inner.handle_event(ch, event) else {
-612                continue;
-613            };
-614            // Ignoring errors from dropped connections that haven't yet been cleaned up
-615            let _ = self
-616                .recv_state
-617                .connections
-618                .senders
-619                .get_mut(&ch)
-620                .unwrap()
-621                .send(ConnectionEvent::Proto(event));
-622        }
-623
-624        true
-625    }
-626}
-627
-628impl Drop for State {
-629    fn drop(&mut self) {
-630        for incoming in self.recv_state.incoming.drain(..) {
-631            self.inner.ignore(incoming);
-632        }
-633    }
-634}
-635
-636fn respond(
-637    transmit: proto::Transmit,
-638    response_buffer: &[u8],
-639    sender: &mut Pin<Box<dyn UdpSender>>,
-640) {
-641    // Send if there's kernel buffer space; otherwise, drop it
-642    //
-643    // As an endpoint-generated packet, we know this is an
-644    // immediate, stateless response to an unconnected peer,
-645    // one of:
-646    //
-647    // - A version negotiation response due to an unknown version
-648    // - A `CLOSE` due to a malformed or unwanted connection attempt
-649    // - A stateless reset due to an unrecognized connection
-650    // - A `Retry` packet due to a connection attempt when
-651    //   `use_retry` is set
-652    //
-653    // In each case, a well-behaved peer can be trusted to retry a
-654    // few times, which is guaranteed to produce the same response
-655    // from us. Repeated failures might at worst cause a peer's new
-656    // connection attempt to time out, which is acceptable if we're
-657    // under such heavy load that there's never room for this code
-658    // to transmit. This is morally equivalent to the packet getting
-659    // lost due to congestion further along the link, which
-660    // similarly relies on peer retries for recovery.
-661
-662    // Copied from rust 1.85's std::task::Waker::noop() implementation for backwards compatibility
-663    const NOOP: RawWaker = {
-664        const VTABLE: RawWakerVTable = RawWakerVTable::new(
-665            // Cloning just returns a new no-op raw waker
-666            |_| NOOP,
-667            // `wake` does nothing
-668            |_| {},
-669            // `wake_by_ref` does nothing
-670            |_| {},
-671            // Dropping does nothing as we don't allocate anything
-672            |_| {},
-673        );
-674        RawWaker::new(std::ptr::null(), &VTABLE)
-675    };
-676    // SAFETY: Copied from rust stdlib, the NOOP waker is thread-safe and doesn't violate the RawWakerVTable contract,
-677    // it doesn't access the data pointer at all.
-678    let waker = unsafe { Waker::from_raw(NOOP) };
-679    let mut cx = Context::from_waker(&waker);
-680    _ = sender.as_mut().poll_send(
-681        &udp_transmit(&transmit, &response_buffer[..transmit.size]),
-682        &mut cx,
-683    );
-684}
-685
-686#[inline]
-687fn proto_ecn(ecn: udp::EcnCodepoint) -> proto::EcnCodepoint {
-688    match ecn {
-689        udp::EcnCodepoint::Ect0 => proto::EcnCodepoint::Ect0,
-690        udp::EcnCodepoint::Ect1 => proto::EcnCodepoint::Ect1,
-691        udp::EcnCodepoint::Ce => proto::EcnCodepoint::Ce,
-692    }
-693}
-694
-695#[derive(Debug)]
-696struct ConnectionSet {
-697    /// Senders for communicating with the endpoint's connections
-698    senders: FxHashMap<ConnectionHandle, mpsc::UnboundedSender<ConnectionEvent>>,
-699    /// Stored to give out clones to new ConnectionInners
-700    sender: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>,
-701    /// Set if the endpoint has been manually closed
-702    close: Option<(VarInt, Bytes)>,
-703}
-704
-705impl ConnectionSet {
-706    fn insert(
-707        &mut self,
-708        handle: ConnectionHandle,
-709        conn: proto::Connection,
-710        sender: Pin<Box<dyn UdpSender>>,
-711        runtime: Arc<dyn Runtime>,
-712    ) -> Connecting {
-713        let (send, recv) = mpsc::unbounded_channel();
-714        if let Some((error_code, ref reason)) = self.close {
-715            send.send(ConnectionEvent::Close {
-716                error_code,
-717                reason: reason.clone(),
-718            })
-719            .unwrap();
-720        }
-721        self.senders.insert(handle, send);
-722        Connecting::new(handle, conn, self.sender.clone(), recv, sender, runtime)
-723    }
-724
-725    fn is_empty(&self) -> bool {
-726        self.senders.is_empty()
-727    }
-728}
-729
-730pub(crate) fn ensure_ipv6(x: SocketAddr) -> SocketAddrV6 {
-731    match x {
-732        SocketAddr::V6(x) => x,
-733        SocketAddr::V4(x) => SocketAddrV6::new(x.ip().to_ipv6_mapped(), x.port(), 0, 0),
-734    }
-735}
-736
-737pin_project! {
-738    /// Future produced by [`Endpoint::accept`]
-739    pub struct Accept<'a> {
-740        endpoint: &'a Endpoint,
-741        #[pin]
-742        notify: Notified<'a>,
-743    }
-744}
-745
-746impl Future for Accept<'_> {
-747    type Output = Option<Incoming>;
-748    fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
-749        let mut this = self.project();
-750        let mut endpoint = this.endpoint.inner.state.lock().unwrap();
-751        if endpoint.driver_lost {
-752            return Poll::Ready(None);
-753        }
-754        if let Some(incoming) = endpoint.recv_state.incoming.pop_front() {
-755            // Release the mutex lock on endpoint so cloning it doesn't deadlock
-756            drop(endpoint);
-757            let incoming = Incoming::new(incoming, this.endpoint.inner.clone());
-758            return Poll::Ready(Some(incoming));
-759        }
-760        if endpoint.recv_state.connections.close.is_some() {
-761            return Poll::Ready(None);
-762        }
-763        loop {
-764            match this.notify.as_mut().poll(ctx) {
-765                // `state` lock ensures we didn't race with readiness
-766                Poll::Pending => return Poll::Pending,
-767                // Spurious wakeup, get a new future
-768                Poll::Ready(()) => this
-769                    .notify
-770                    .set(this.endpoint.inner.shared.incoming.notified()),
-771            }
-772        }
-773    }
-774}
-775
-776#[derive(Debug)]
-777pub(crate) struct EndpointRef(Arc<EndpointInner>);
-778
-779impl EndpointRef {
-780    pub(crate) fn new(
-781        socket: Box<dyn AsyncUdpSocket>,
-782        inner: proto::Endpoint,
-783        ipv6: bool,
-784        runtime: Arc<dyn Runtime>,
-785    ) -> Self {
-786        let (sender, events) = mpsc::unbounded_channel();
-787        let recv_state = RecvState::new(sender, socket.max_receive_segments(), &inner);
-788        let sender = socket.create_sender();
-789        Self(Arc::new(EndpointInner {
-790            shared: Shared {
-791                incoming: Notify::new(),
-792                idle: Notify::new(),
-793                ref_count: AtomicUsize::new(0),
-794            },
-795            state: Mutex::new(State {
-796                socket,
-797                sender,
-798                prev_socket: None,
-799                inner,
-800                ipv6,
-801                events,
-802                driver: None,
-803                driver_lost: false,
-804                recv_state,
-805                runtime,
-806                stats: EndpointStats::default(),
-807                default_client_config: None,
-808            }),
-809        }))
-810    }
-811}
-812
-813impl Clone for EndpointRef {
-814    fn clone(&self) -> Self {
-815        self.0.shared.ref_count.fetch_add(1, Ordering::Relaxed);
-816        Self(self.0.clone())
-817    }
-818}
-819
-820impl Drop for EndpointRef {
-821    fn drop(&mut self) {
-822        if self.0.shared.ref_count.fetch_sub(1, Ordering::Relaxed) > 1 {
-823            return;
-824        }
-825
-826        let endpoint = &mut *self.0.state.lock().unwrap();
-827        // If the driver is about to be on its own, ensure it can shut down if the last
-828        // connection is gone.
-829        if let Some(task) = endpoint.driver.take() {
-830            task.wake();
-831        }
-832    }
-833}
-834
-835impl std::ops::Deref for EndpointRef {
-836    type Target = EndpointInner;
-837    fn deref(&self) -> &Self::Target {
-838        &self.0
-839    }
-840}
-841
-842/// State directly involved in handling incoming packets
-843struct RecvState {
-844    incoming: VecDeque<proto::Incoming>,
-845    connections: ConnectionSet,
-846    recv_buf: Box<[u8]>,
-847    recv_limiter: WorkLimiter,
-848}
-849
-850impl RecvState {
-851    fn new(
-852        sender: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>,
-853        max_receive_segments: NonZeroUsize,
-854        endpoint: &proto::Endpoint,
-855    ) -> Self {
-856        let recv_buf = vec![
-857            0;
-858            endpoint.config().get_max_udp_payload_size().min(64 * 1024) as usize
-859                * max_receive_segments.get()
-860                * BATCH_SIZE
-861        ];
-862        Self {
-863            connections: ConnectionSet {
-864                senders: FxHashMap::default(),
-865                sender,
-866                close: None,
-867            },
-868            incoming: VecDeque::new(),
-869            recv_buf: recv_buf.into(),
-870            recv_limiter: WorkLimiter::new(RECV_TIME_BOUND),
-871        }
-872    }
-873
-874    fn poll_socket(
-875        &mut self,
-876        cx: &mut Context<'_>,
-877        endpoint: &mut proto::Endpoint,
-878        socket: &mut dyn AsyncUdpSocket,
-879        sender: &mut Pin<Box<dyn UdpSender>>,
-880        runtime: &dyn Runtime,
-881        now: Instant,
-882    ) -> Result<PollProgress, io::Error> {
-883        let mut received_connection_packet = false;
-884        let mut metas = [RecvMeta::default(); BATCH_SIZE];
-885        let mut iovs: [IoSliceMut<'_>; BATCH_SIZE] = {
-886            let mut bufs = self
-887                .recv_buf
-888                .chunks_mut(self.recv_buf.len() / BATCH_SIZE)
-889                .map(IoSliceMut::new);
-890
-891            // expect() safe as self.recv_buf is chunked into BATCH_SIZE items
-892            // and iovs will be of size BATCH_SIZE, thus from_fn is called
-893            // exactly BATCH_SIZE times.
-894            std::array::from_fn(|_| bufs.next().expect("BATCH_SIZE elements"))
-895        };
-896        loop {
-897            match socket.poll_recv(cx, &mut iovs, &mut metas) {
-898                Poll::Ready(Ok(msgs)) => {
-899                    self.recv_limiter.record_work(msgs);
-900                    for (meta, buf) in metas.iter().zip(iovs.iter()).take(msgs) {
-901                        let mut data: BytesMut = buf[0..meta.len].into();
-902                        while !data.is_empty() {
-903                            let buf = data.split_to(meta.stride.min(data.len()));
-904                            let mut response_buffer = Vec::new();
-905                            let addresses = FourTuple::new(meta.addr, meta.dst_ip);
-906                            match endpoint.handle(
-907                                now,
-908                                addresses,
-909                                meta.ecn.map(proto_ecn),
-910                                buf,
-911                                &mut response_buffer,
-912                            ) {
-913                                Some(DatagramEvent::NewConnection(incoming)) => {
-914                                    if self.connections.close.is_none() {
-915                                        self.incoming.push_back(incoming);
-916                                    } else {
-917                                        let transmit =
-918                                            endpoint.refuse(incoming, &mut response_buffer);
-919                                        respond(transmit, &response_buffer, sender);
-920                                    }
-921                                }
-922                                Some(DatagramEvent::ConnectionEvent(handle, event)) => {
-923                                    // Ignoring errors from dropped connections that haven't yet been cleaned up
-924                                    received_connection_packet = true;
-925                                    let _ = self
-926                                        .connections
-927                                        .senders
-928                                        .get_mut(&handle)
-929                                        .unwrap()
-930                                        .send(ConnectionEvent::Proto(event));
-931                                }
-932                                Some(DatagramEvent::Response(transmit)) => {
-933                                    respond(transmit, &response_buffer, sender);
-934                                }
-935                                None => {}
-936                            }
-937                        }
-938                    }
-939                }
-940                Poll::Pending => {
-941                    return Ok(PollProgress {
-942                        received_connection_packet,
-943                        keep_going: false,
-944                    });
-945                }
-946                // Ignore ECONNRESET as it's undefined in QUIC and may be injected by an
-947                // attacker
-948                Poll::Ready(Err(ref e)) if e.kind() == io::ErrorKind::ConnectionReset => {
-949                    continue;
-950                }
-951                Poll::Ready(Err(e)) => {
-952                    return Err(e);
-953                }
-954            }
-955            if !self.recv_limiter.allow_work(|| runtime.now()) {
-956                return Ok(PollProgress {
-957                    received_connection_packet,
-958                    keep_going: true,
-959                });
-960            }
-961        }
-962    }
-963}
-964
-965impl fmt::Debug for RecvState {
-966    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-967        f.debug_struct("RecvState")
-968            .field("incoming", &self.incoming)
-969            .field("connections", &self.connections)
-970            // recv_buf too large
-971            .field("recv_limiter", &self.recv_limiter)
-972            .finish_non_exhaustive()
-973    }
-974}
-975
-976#[derive(Default)]
-977struct PollProgress {
-978    /// Whether a datagram was routed to an existing connection
-979    received_connection_packet: bool,
-980    /// Whether datagram handling was interrupted early by the work limiter for fairness
-981    keep_going: bool,
-982}
\ No newline at end of file +462 let now = endpoint.runtime.now(); +463 let mut keep_going = false; +464 keep_going |= endpoint.drive_recv(cx, now)?; +465 keep_going |= endpoint.handle_events(cx, &self.0.shared); +466 +467 if !endpoint.recv_state.incoming.is_empty() { +468 self.0.shared.incoming.notify_waiters(); +469 } +470 +471 // Stop the driver if either: +472 // - all `Endpoint` structs are dropped and all connections are drained, +473 // - or `Endpoint::close` has been called and all connections are drained. +474 if endpoint.recv_state.connections.is_empty() +475 && (self.0.shared.ref_count.load(Ordering::Relaxed) == 0 +476 || endpoint.recv_state.connections.close.is_some()) +477 { +478 trace!("endpoint driver stopping"); +479 Poll::Ready(Ok(())) +480 } else { +481 drop(endpoint); +482 // If there is more work to do schedule the endpoint task again. +483 // `wake_by_ref()` is called outside the lock to minimize +484 // lock contention on a multithreaded runtime. +485 if keep_going { +486 cx.waker().wake_by_ref(); +487 } +488 Poll::Pending +489 } +490 } +491} +492 +493impl Drop for EndpointDriver { +494 fn drop(&mut self) { +495 let mut endpoint = self.0.state.lock().unwrap(); +496 endpoint.driver_lost = true; +497 self.0.shared.incoming.notify_waiters(); +498 // Drop all outgoing channels, signaling the termination of the endpoint to the associated +499 // connections. +500 endpoint.recv_state.connections.senders.clear(); +501 endpoint.recv_state.connections.active_connections = 0; +502 } +503} +504 +505#[derive(Debug)] +506pub(crate) struct EndpointInner { +507 pub(crate) state: Mutex<State>, +508 pub(crate) shared: Shared, +509} +510 +511impl EndpointInner { +512 pub(crate) fn accept( +513 &self, +514 incoming: proto::Incoming, +515 server_config: Option<Arc<ServerConfig>>, +516 ) -> Result<Connecting, ConnectionError> { +517 let mut state = self.state.lock().unwrap(); +518 let mut response_buffer = Vec::new(); +519 let now = state.runtime.now(); +520 match state +521 .inner +522 .accept(incoming, now, &mut response_buffer, server_config) +523 { +524 Ok((handle, conn)) => { +525 state.stats.accepted_handshakes += 1; +526 let sender = state.socket.create_sender(); +527 let runtime = state.runtime.clone(); +528 Ok(state +529 .recv_state +530 .connections +531 .insert(handle, conn, sender, runtime)) +532 } +533 Err(error) => { +534 if let Some(transmit) = error.response { +535 respond(transmit, &response_buffer, &mut state.sender); +536 } +537 Err(error.cause) +538 } +539 } +540 } +541 +542 pub(crate) fn refuse(&self, incoming: proto::Incoming) { +543 let mut state = self.state.lock().unwrap(); +544 state.stats.refused_handshakes += 1; +545 let mut response_buffer = Vec::new(); +546 let transmit = state.inner.refuse(incoming, &mut response_buffer); +547 respond(transmit, &response_buffer, &mut state.sender); +548 } +549 +550 pub(crate) fn retry(&self, incoming: proto::Incoming) -> Result<(), proto::RetryError> { +551 let mut state = self.state.lock().unwrap(); +552 let mut response_buffer = Vec::new(); +553 let transmit = state.inner.retry(incoming, &mut response_buffer)?; +554 respond(transmit, &response_buffer, &mut state.sender); +555 Ok(()) +556 } +557 +558 pub(crate) fn ignore(&self, incoming: proto::Incoming) { +559 let mut state = self.state.lock().unwrap(); +560 state.stats.ignored_handshakes += 1; +561 state.inner.ignore(incoming); +562 } +563} +564 +565#[derive(Debug)] +566pub(crate) struct State { +567 socket: Box<dyn AsyncUdpSocket>, +568 sender: Pin<Box<dyn UdpSender>>, +569 /// During an active migration, abandoned_socket receives traffic +570 /// until the first packet arrives on the new socket. +571 prev_socket: Option<Box<dyn AsyncUdpSocket>>, +572 inner: proto::Endpoint, +573 recv_state: RecvState, +574 driver: Option<Waker>, +575 ipv6: bool, +576 events: mpsc::UnboundedReceiver<(ConnectionHandle, EndpointEvent)>, +577 driver_lost: bool, +578 runtime: Arc<dyn Runtime>, +579 stats: EndpointStats, +580 default_client_config: Option<ClientConfig>, +581} +582 +583#[derive(Debug)] +584pub(crate) struct Shared { +585 /// Notifies subscribers of new incoming connections. +586 /// +587 /// This enables the `Endpoint::accept` API. +588 incoming: Notify, +589 /// Notifies subscribers when *all* connections have entered the draining state. +590 /// +591 /// This powers the `Endpoint::wait_idle` API. +592 all_draining: Notify, +593 /// Notifies subscribesr when *all* connections have been dropped. +594 /// +595 /// This powers the `Endpoint::wait_drained` API. +596 idle: Notify, +597 /// Number of live handles that can be used to initiate or handle I/O; excludes the driver +598 ref_count: AtomicUsize, +599} +600 +601impl State { +602 fn drive_recv(&mut self, cx: &mut Context<'_>, now: Instant) -> Result<bool, io::Error> { +603 let get_time = || self.runtime.now(); +604 self.recv_state.recv_limiter.start_cycle(get_time); +605 if let Some(socket) = &mut self.prev_socket { +606 // We don't care about the `PollProgress` from old sockets. +607 let poll_res = self.recv_state.poll_socket( +608 cx, +609 &mut self.inner, +610 &mut **socket, +611 &mut self.sender, +612 &*self.runtime, +613 now, +614 ); +615 if poll_res.is_err() { +616 self.prev_socket = None; +617 } +618 }; +619 let poll_res = self.recv_state.poll_socket( +620 cx, +621 &mut self.inner, +622 &mut *self.socket, +623 &mut self.sender, +624 &*self.runtime, +625 now, +626 ); +627 self.recv_state.recv_limiter.finish_cycle(get_time); +628 let poll_res = poll_res?; +629 if poll_res.received_connection_packet { +630 // Traffic has arrived on self.socket, therefore there is no need for the abandoned +631 // one anymore. TODO: Account for multiple outgoing connections. +632 self.prev_socket = None; +633 } +634 Ok(poll_res.keep_going) +635 } +636 +637 fn handle_events(&mut self, cx: &mut Context<'_>, shared: &Shared) -> bool { +638 for _ in 0..IO_LOOP_BOUND { +639 let (ch, event) = match self.events.poll_recv(cx) { +640 Poll::Ready(Some(x)) => x, +641 Poll::Ready(None) => unreachable!("EndpointInner owns one sender"), +642 Poll::Pending => { +643 return false; +644 } +645 }; +646 +647 if event.is_draining() { +648 self.recv_state.connections.active_connections -= 1; +649 if self.recv_state.connections.active_connections == 0 { +650 shared.all_draining.notify_waiters(); +651 } +652 } else if event.is_drained() { +653 self.recv_state.connections.senders.remove(&ch); +654 if self.recv_state.connections.is_empty() { +655 shared.idle.notify_waiters(); +656 } +657 } +658 let Some(event) = self.inner.handle_event(ch, event) else { +659 continue; +660 }; +661 // Ignoring errors from dropped connections that haven't yet been cleaned up +662 let _ = self +663 .recv_state +664 .connections +665 .senders +666 .get_mut(&ch) +667 .unwrap() +668 .send(ConnectionEvent::Proto(event)); +669 } +670 +671 true +672 } +673} +674 +675impl Drop for State { +676 fn drop(&mut self) { +677 for incoming in self.recv_state.incoming.drain(..) { +678 self.inner.ignore(incoming); +679 } +680 } +681} +682 +683fn respond( +684 transmit: proto::Transmit, +685 response_buffer: &[u8], +686 sender: &mut Pin<Box<dyn UdpSender>>, +687) { +688 // Send if there's kernel buffer space; otherwise, drop it +689 // +690 // As an endpoint-generated packet, we know this is an +691 // immediate, stateless response to an unconnected peer, +692 // one of: +693 // +694 // - A version negotiation response due to an unknown version +695 // - A `CLOSE` due to a malformed or unwanted connection attempt +696 // - A stateless reset due to an unrecognized connection +697 // - A `Retry` packet due to a connection attempt when +698 // `use_retry` is set +699 // +700 // In each case, a well-behaved peer can be trusted to retry a +701 // few times, which is guaranteed to produce the same response +702 // from us. Repeated failures might at worst cause a peer's new +703 // connection attempt to time out, which is acceptable if we're +704 // under such heavy load that there's never room for this code +705 // to transmit. This is morally equivalent to the packet getting +706 // lost due to congestion further along the link, which +707 // similarly relies on peer retries for recovery. +708 +709 // Copied from rust 1.85's std::task::Waker::noop() implementation for backwards compatibility +710 const NOOP: RawWaker = { +711 const VTABLE: RawWakerVTable = RawWakerVTable::new( +712 // Cloning just returns a new no-op raw waker +713 |_| NOOP, +714 // `wake` does nothing +715 |_| {}, +716 // `wake_by_ref` does nothing +717 |_| {}, +718 // Dropping does nothing as we don't allocate anything +719 |_| {}, +720 ); +721 RawWaker::new(std::ptr::null(), &VTABLE) +722 }; +723 // SAFETY: Copied from rust stdlib, the NOOP waker is thread-safe and doesn't violate the RawWakerVTable contract, +724 // it doesn't access the data pointer at all. +725 let waker = unsafe { Waker::from_raw(NOOP) }; +726 let mut cx = Context::from_waker(&waker); +727 _ = sender.as_mut().poll_send( +728 &udp_transmit(&transmit, &response_buffer[..transmit.size]), +729 &mut cx, +730 ); +731} +732 +733#[inline] +734fn proto_ecn(ecn: udp::EcnCodepoint) -> proto::EcnCodepoint { +735 match ecn { +736 udp::EcnCodepoint::Ect0 => proto::EcnCodepoint::Ect0, +737 udp::EcnCodepoint::Ect1 => proto::EcnCodepoint::Ect1, +738 udp::EcnCodepoint::Ce => proto::EcnCodepoint::Ce, +739 } +740} +741 +742#[derive(Debug)] +743struct ConnectionSet { +744 /// Senders for communicating with the endpoint's connections +745 senders: FxHashMap<ConnectionHandle, mpsc::UnboundedSender<ConnectionEvent>>, +746 /// Stored to give out clones to new ConnectionInners +747 sender: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, +748 /// Set if the endpoint has been manually closed +749 close: Option<(VarInt, Bytes)>, +750 /// Counter for all active (non-draining/drained) connections. +751 /// +752 /// This is directly related to the QUIC connection states "Initial", "Handshake", +753 /// "Established", "Closed", "Draining" and "Drained" (see also `proto/src/connection/state.rs`). +754 /// +755 /// Any connection state that is not "Draining" or "Drained" is considered active. +756 /// +757 /// This counter is updated when new connections are added ([`ConnectionSet::insert`]) and when +758 /// a connection informs us about entering the draining state ([`State::handle_events`]). +759 active_connections: u64, +760} +761 +762impl ConnectionSet { +763 fn insert( +764 &mut self, +765 handle: ConnectionHandle, +766 conn: proto::Connection, +767 sender: Pin<Box<dyn UdpSender>>, +768 runtime: Arc<dyn Runtime>, +769 ) -> Connecting { +770 let (send, recv) = mpsc::unbounded_channel(); +771 if let Some((error_code, ref reason)) = self.close { +772 send.send(ConnectionEvent::Close { +773 error_code, +774 reason: reason.clone(), +775 }) +776 .unwrap(); +777 } +778 self.senders.insert(handle, send); +779 self.active_connections += 1; +780 Connecting::new(handle, conn, self.sender.clone(), recv, sender, runtime) +781 } +782 +783 fn is_empty(&self) -> bool { +784 self.senders.is_empty() +785 } +786} +787 +788pub(crate) fn ensure_ipv6(x: SocketAddr) -> SocketAddrV6 { +789 match x { +790 SocketAddr::V6(x) => x, +791 SocketAddr::V4(x) => SocketAddrV6::new(x.ip().to_ipv6_mapped(), x.port(), 0, 0), +792 } +793} +794 +795pin_project! { +796 /// Future produced by [`Endpoint::accept`] +797 pub struct Accept<'a> { +798 endpoint: &'a Endpoint, +799 #[pin] +800 notify: Notified<'a>, +801 } +802} +803 +804impl Future for Accept<'_> { +805 type Output = Option<Incoming>; +806 fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> { +807 let mut this = self.project(); +808 let mut endpoint = this.endpoint.inner.state.lock().unwrap(); +809 if endpoint.driver_lost { +810 return Poll::Ready(None); +811 } +812 if let Some(incoming) = endpoint.recv_state.incoming.pop_front() { +813 // Release the mutex lock on endpoint so cloning it doesn't deadlock +814 drop(endpoint); +815 let incoming = Incoming::new(incoming, this.endpoint.inner.clone()); +816 return Poll::Ready(Some(incoming)); +817 } +818 if endpoint.recv_state.connections.close.is_some() { +819 return Poll::Ready(None); +820 } +821 loop { +822 match this.notify.as_mut().poll(ctx) { +823 // `state` lock ensures we didn't race with readiness +824 Poll::Pending => return Poll::Pending, +825 // Spurious wakeup, get a new future +826 Poll::Ready(()) => this +827 .notify +828 .set(this.endpoint.inner.shared.incoming.notified()), +829 } +830 } +831 } +832} +833 +834#[derive(Debug)] +835pub(crate) struct EndpointRef(Arc<EndpointInner>); +836 +837impl EndpointRef { +838 pub(crate) fn new( +839 socket: Box<dyn AsyncUdpSocket>, +840 inner: proto::Endpoint, +841 ipv6: bool, +842 runtime: Arc<dyn Runtime>, +843 ) -> Self { +844 let (sender, events) = mpsc::unbounded_channel(); +845 let recv_state = RecvState::new(sender, socket.max_receive_segments(), &inner); +846 let sender = socket.create_sender(); +847 Self(Arc::new(EndpointInner { +848 shared: Shared { +849 incoming: Notify::new(), +850 all_draining: Notify::new(), +851 idle: Notify::new(), +852 ref_count: AtomicUsize::new(0), +853 }, +854 state: Mutex::new(State { +855 socket, +856 sender, +857 prev_socket: None, +858 inner, +859 ipv6, +860 events, +861 driver: None, +862 driver_lost: false, +863 recv_state, +864 runtime, +865 stats: EndpointStats::default(), +866 default_client_config: None, +867 }), +868 })) +869 } +870} +871 +872impl Clone for EndpointRef { +873 fn clone(&self) -> Self { +874 self.0.shared.ref_count.fetch_add(1, Ordering::Relaxed); +875 Self(self.0.clone()) +876 } +877} +878 +879impl Drop for EndpointRef { +880 fn drop(&mut self) { +881 if self.0.shared.ref_count.fetch_sub(1, Ordering::Relaxed) > 1 { +882 return; +883 } +884 +885 let endpoint = &mut *self.0.state.lock().unwrap(); +886 // If the driver is about to be on its own, ensure it can shut down if the last +887 // connection is gone. +888 if let Some(task) = endpoint.driver.take() { +889 task.wake(); +890 } +891 } +892} +893 +894impl std::ops::Deref for EndpointRef { +895 type Target = EndpointInner; +896 fn deref(&self) -> &Self::Target { +897 &self.0 +898 } +899} +900 +901/// State directly involved in handling incoming packets +902struct RecvState { +903 incoming: VecDeque<proto::Incoming>, +904 connections: ConnectionSet, +905 recv_buf: Box<[u8]>, +906 recv_limiter: WorkLimiter, +907} +908 +909impl RecvState { +910 fn new( +911 sender: mpsc::UnboundedSender<(ConnectionHandle, EndpointEvent)>, +912 max_receive_segments: NonZeroUsize, +913 endpoint: &proto::Endpoint, +914 ) -> Self { +915 let recv_buf = vec![ +916 0; +917 endpoint.config().get_max_udp_payload_size().min(64 * 1024) as usize +918 * max_receive_segments.get() +919 * BATCH_SIZE +920 ]; +921 Self { +922 connections: ConnectionSet { +923 senders: FxHashMap::default(), +924 sender, +925 close: None, +926 active_connections: 0, +927 }, +928 incoming: VecDeque::new(), +929 recv_buf: recv_buf.into(), +930 recv_limiter: WorkLimiter::new(RECV_TIME_BOUND), +931 } +932 } +933 +934 fn poll_socket( +935 &mut self, +936 cx: &mut Context<'_>, +937 endpoint: &mut proto::Endpoint, +938 socket: &mut dyn AsyncUdpSocket, +939 sender: &mut Pin<Box<dyn UdpSender>>, +940 runtime: &dyn Runtime, +941 now: Instant, +942 ) -> Result<PollProgress, io::Error> { +943 let mut received_connection_packet = false; +944 let mut metas = [RecvMeta::default(); BATCH_SIZE]; +945 let mut iovs: [IoSliceMut<'_>; BATCH_SIZE] = { +946 let mut bufs = self +947 .recv_buf +948 .chunks_mut(self.recv_buf.len() / BATCH_SIZE) +949 .map(IoSliceMut::new); +950 +951 // expect() safe as self.recv_buf is chunked into BATCH_SIZE items +952 // and iovs will be of size BATCH_SIZE, thus from_fn is called +953 // exactly BATCH_SIZE times. +954 std::array::from_fn(|_| bufs.next().expect("BATCH_SIZE elements")) +955 }; +956 loop { +957 match socket.poll_recv(cx, &mut iovs, &mut metas) { +958 Poll::Ready(Ok(msgs)) => { +959 self.recv_limiter.record_work(msgs); +960 for (meta, buf) in metas.iter().zip(iovs.iter()).take(msgs) { +961 let mut data: BytesMut = buf[0..meta.len].into(); +962 while !data.is_empty() { +963 let buf = data.split_to(meta.stride.min(data.len())); +964 let mut response_buffer = Vec::new(); +965 let addresses = FourTuple::new(meta.addr, meta.dst_ip); +966 match endpoint.handle( +967 now, +968 addresses, +969 meta.ecn.map(proto_ecn), +970 buf, +971 &mut response_buffer, +972 ) { +973 Some(DatagramEvent::NewConnection(incoming)) => { +974 if self.connections.close.is_none() { +975 self.incoming.push_back(incoming); +976 } else { +977 let transmit = +978 endpoint.refuse(incoming, &mut response_buffer); +979 respond(transmit, &response_buffer, sender); +980 } +981 } +982 Some(DatagramEvent::ConnectionEvent(handle, event)) => { +983 // Ignoring errors from dropped connections that haven't yet been cleaned up +984 received_connection_packet = true; +985 let _ = self +986 .connections +987 .senders +988 .get_mut(&handle) +989 .unwrap() +990 .send(ConnectionEvent::Proto(event)); +991 } +992 Some(DatagramEvent::Response(transmit)) => { +993 respond(transmit, &response_buffer, sender); +994 } +995 None => {} +996 } +997 } +998 } +999 } +1000 Poll::Pending => { +1001 return Ok(PollProgress { +1002 received_connection_packet, +1003 keep_going: false, +1004 }); +1005 } +1006 // Ignore ECONNRESET as it's undefined in QUIC and may be injected by an +1007 // attacker +1008 Poll::Ready(Err(ref e)) if e.kind() == io::ErrorKind::ConnectionReset => { +1009 continue; +1010 } +1011 Poll::Ready(Err(e)) => { +1012 return Err(e); +1013 } +1014 } +1015 if !self.recv_limiter.allow_work(|| runtime.now()) { +1016 return Ok(PollProgress { +1017 received_connection_packet, +1018 keep_going: true, +1019 }); +1020 } +1021 } +1022 } +1023} +1024 +1025impl fmt::Debug for RecvState { +1026 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +1027 f.debug_struct("RecvState") +1028 .field("incoming", &self.incoming) +1029 .field("connections", &self.connections) +1030 // recv_buf too large +1031 .field("recv_limiter", &self.recv_limiter) +1032 .finish_non_exhaustive() +1033 } +1034} +1035 +1036#[derive(Default)] +1037struct PollProgress { +1038 /// Whether a datagram was routed to an existing connection +1039 received_connection_packet: bool, +1040 /// Whether datagram handling was interrupted early by the work limiter for fairness +1041 keep_going: bool, +1042}
\ No newline at end of file diff --git a/pr/451/docs/src/noq/lib.rs.html b/pr/451/docs/src/noq/lib.rs.html index 92dbf5471..8dccaeb02 100644 --- a/pr/451/docs/src/noq/lib.rs.html +++ b/pr/451/docs/src/noq/lib.rs.html @@ -64,80 +64,81 @@ 64 AckFrequencyConfig, ApplicationClose, Chunk, ClientConfig, ClosePathError, ClosedPath, 65 ClosedStream, ConfigError, ConnectError, ConnectionClose, ConnectionError, ConnectionId, 66 ConnectionIdGenerator, ConnectionStats, DecryptedInitial, Dir, EcnCodepoint, EndpointConfig, -67 FrameStats, FrameType, IdleTimeout, InvalidCid, MtuDiscoveryConfig, NetworkChangeHint, -68 NoneTokenLog, NoneTokenStore, PathError, PathEvent, PathId, PathStats, PathStatus, -69 ServerConfig, SetPathStatusError, Side, StdSystemTime, StreamId, TimeSource, TokenLog, -70 TokenMemoryCache, TokenReuseError, TokenStore, Transmit, TransportConfig, TransportErrorCode, -71 UdpStats, ValidationTokenConfig, VarInt, VarIntBoundsExceeded, congestion, crypto, -72}; -73#[cfg(feature = "qlog")] -74pub use proto::{QlogConfig, QlogFactory, QlogFileFactory}; -75#[cfg(feature = "rustls")] -76pub use rustls; -77pub use udp; -78 -79pub use crate::connection::{ -80 AcceptBi, AcceptUni, Closed, Connecting, Connection, OnClosed, OpenBi, OpenUni, ReadDatagram, -81 SendDatagram, SendDatagramError, WeakConnectionHandle, ZeroRttAccepted, -82}; -83pub use crate::endpoint::{Accept, Endpoint, EndpointStats}; -84pub use crate::event_stream::{Lagged, NatTraversalUpdates, ObservedExternalAddr, PathEvents}; -85pub use crate::incoming::{Incoming, IncomingFuture, RetryError}; -86pub use crate::path::{AddressDiscovery, OpenPath, Path, WeakPathHandle}; -87pub use crate::recv_stream::{ -88 ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError, UnorderedRecvStream, -89}; -90#[cfg(feature = "runtime-smol")] -91pub use crate::runtime::SmolRuntime; -92#[cfg(feature = "runtime-tokio")] -93pub use crate::runtime::TokioRuntime; -94#[cfg(any(feature = "runtime-tokio", feature = "runtime-smol"))] -95pub use crate::runtime::default_runtime; -96pub use crate::runtime::{AsyncTimer, AsyncUdpSocket, Runtime, UdpSender}; -97pub use crate::send_stream::{SendStream, Stopped, StoppedError, WriteError}; -98 -99#[cfg(test)] -100mod tests; -101 -102#[derive(Debug)] -103enum ConnectionEvent { -104 Close { -105 error_code: VarInt, -106 reason: bytes::Bytes, -107 }, -108 Proto(proto::ConnectionEvent), -109 Rebind(Pin<Box<dyn UdpSender>>), -110 LocalAddressChanged(Option<Arc<dyn NetworkChangeHint + Sync + Send>>), -111} -112 -113fn udp_transmit<'a>(t: &proto::Transmit, buffer: &'a [u8]) -> udp::Transmit<'a> { -114 udp::Transmit { -115 destination: t.destination, -116 ecn: t.ecn.map(udp_ecn), -117 contents: buffer, -118 segment_size: t.segment_size, -119 src_ip: t.src_ip, -120 } -121} -122 -123fn udp_ecn(ecn: proto::EcnCodepoint) -> udp::EcnCodepoint { -124 match ecn { -125 proto::EcnCodepoint::Ect0 => udp::EcnCodepoint::Ect0, -126 proto::EcnCodepoint::Ect1 => udp::EcnCodepoint::Ect1, -127 proto::EcnCodepoint::Ce => udp::EcnCodepoint::Ce, -128 } -129} -130 -131/// Maximum number of datagrams processed in send/recv calls to make before moving on to other processing -132/// -133/// This helps ensure we don't starve anything when the CPU is slower than the link. -134/// Value is selected by picking a low number which didn't degrade throughput in benchmarks. -135const IO_LOOP_BOUND: usize = 160; -136 -137/// The maximum amount of time that should be spent in `recvmsg()` calls per endpoint iteration -138/// -139/// 50us are chosen so that an endpoint iteration with a 50us sendmsg limit blocks -140/// the runtime for a maximum of about 100us. -141/// Going much lower does not yield any noticeable difference, since a single `recvmmsg` -142/// batch of size 32 was observed to take 30us on some systems. -143const RECV_TIME_BOUND: Duration = Duration::from_micros(50);
\ No newline at end of file +67 FourTuple, FrameStats, FrameType, IdleTimeout, InvalidCid, MtuDiscoveryConfig, +68 NetworkChangeHint, NoneTokenLog, NoneTokenStore, PathError, PathEvent, PathId, PathStats, +69 PathStatus, ServerConfig, SetPathStatusError, Side, StdSystemTime, StreamId, TimeSource, +70 TokenLog, TokenMemoryCache, TokenReuseError, TokenStore, Transmit, TransportConfig, +71 TransportErrorCode, UdpStats, ValidationTokenConfig, VarInt, VarIntBoundsExceeded, congestion, +72 crypto, +73}; +74#[cfg(feature = "qlog")] +75pub use proto::{QlogConfig, QlogFactory, QlogFileFactory}; +76#[cfg(feature = "rustls")] +77pub use rustls; +78pub use udp; +79 +80pub use crate::connection::{ +81 AcceptBi, AcceptUni, Closed, Connecting, Connection, OnClosed, OpenBi, OpenUni, ReadDatagram, +82 SendDatagram, SendDatagramError, WeakConnectionHandle, ZeroRttAccepted, +83}; +84pub use crate::endpoint::{Accept, Endpoint, EndpointStats}; +85pub use crate::event_stream::{Lagged, NatTraversalUpdates, ObservedExternalAddr, PathEvents}; +86pub use crate::incoming::{Incoming, IncomingFuture, RetryError}; +87pub use crate::path::{AddressDiscovery, OpenPath, Path, WeakPathHandle}; +88pub use crate::recv_stream::{ +89 ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError, UnorderedRecvStream, +90}; +91#[cfg(feature = "runtime-smol")] +92pub use crate::runtime::SmolRuntime; +93#[cfg(feature = "runtime-tokio")] +94pub use crate::runtime::TokioRuntime; +95#[cfg(any(feature = "runtime-tokio", feature = "runtime-smol"))] +96pub use crate::runtime::default_runtime; +97pub use crate::runtime::{AsyncTimer, AsyncUdpSocket, Runtime, UdpSender}; +98pub use crate::send_stream::{SendStream, Stopped, StoppedError, WriteError}; +99 +100#[cfg(test)] +101mod tests; +102 +103#[derive(Debug)] +104enum ConnectionEvent { +105 Close { +106 error_code: VarInt, +107 reason: bytes::Bytes, +108 }, +109 Proto(proto::ConnectionEvent), +110 Rebind(Pin<Box<dyn UdpSender>>), +111 LocalAddressChanged(Option<Arc<dyn NetworkChangeHint + Sync + Send>>), +112} +113 +114fn udp_transmit<'a>(t: &proto::Transmit, buffer: &'a [u8]) -> udp::Transmit<'a> { +115 udp::Transmit { +116 destination: t.destination, +117 ecn: t.ecn.map(udp_ecn), +118 contents: buffer, +119 segment_size: t.segment_size, +120 src_ip: t.src_ip, +121 } +122} +123 +124fn udp_ecn(ecn: proto::EcnCodepoint) -> udp::EcnCodepoint { +125 match ecn { +126 proto::EcnCodepoint::Ect0 => udp::EcnCodepoint::Ect0, +127 proto::EcnCodepoint::Ect1 => udp::EcnCodepoint::Ect1, +128 proto::EcnCodepoint::Ce => udp::EcnCodepoint::Ce, +129 } +130} +131 +132/// Maximum number of datagrams processed in send/recv calls to make before moving on to other processing +133/// +134/// This helps ensure we don't starve anything when the CPU is slower than the link. +135/// Value is selected by picking a low number which didn't degrade throughput in benchmarks. +136const IO_LOOP_BOUND: usize = 160; +137 +138/// The maximum amount of time that should be spent in `recvmsg()` calls per endpoint iteration +139/// +140/// 50us are chosen so that an endpoint iteration with a 50us sendmsg limit blocks +141/// the runtime for a maximum of about 100us. +142/// Going much lower does not yield any noticeable difference, since a single `recvmmsg` +143/// batch of size 32 was observed to take 30us on some systems. +144const RECV_TIME_BOUND: Duration = Duration::from_micros(50);
\ No newline at end of file diff --git a/pr/451/docs/src/noq/path.rs.html b/pr/451/docs/src/noq/path.rs.html index f9db9f0fe..03d09aa49 100644 --- a/pr/451/docs/src/noq/path.rs.html +++ b/pr/451/docs/src/noq/path.rs.html @@ -7,7 +7,7 @@ 7use std::time::Duration; 8 9use proto::{ -10 ClosePathError, ClosedPath, PathError, PathEvent, PathId, PathStats, PathStatus, +10 ClosePathError, ClosedPath, FourTuple, PathError, PathEvent, PathId, PathStats, PathStatus, 11 SetPathStatusError, TransportErrorCode, 12}; 13use tokio::sync::watch; @@ -269,200 +269,210 @@ 269 Ok(state.inner.network_path(self.id())?.local_ip()) 270 } 271 -272 /// Ping the remote endpoint over this path. -273 pub fn ping(&self) -> Result<(), ClosedPath> { -274 let mut state = self.conn.lock_and_wake("ping"); -275 state.inner.ping_path(self.id()) -276 } -277} -278 -279impl Drop for Path { -280 fn drop(&mut self) { -281 self.path_ref.on_drop(&self.conn); -282 } -283} -284 -285impl PartialEq for Path { -286 fn eq(&self, other: &Self) -> bool { -287 self.id() == other.id() && self.conn.stable_id() == other.conn.stable_id() -288 } -289} -290 -291/// Weak handle for a [`Path`] that does not keep the connection alive. -292/// -293/// As long as a [`WeakPathHandle`] for a path exists, that path's final stats will not be dropped even if -294/// the path was abandoned. -295/// -296/// The [`WeakPathHandle`] can be upgraded to a [`Path`] as long as its [`Connection`] has not been dropped. -297/// -298/// [`Connection`]: crate::Connection -299#[derive(Debug, Clone)] -300pub struct WeakPathHandle { -301 conn: WeakConnectionHandle, -302 path_ref: PathRef, -303} -304 -305impl PartialEq for WeakPathHandle { -306 fn eq(&self, other: &Self) -> bool { -307 self.id() == other.id() && self.conn.is_same_connection(&other.conn) -308 } -309} -310 -311impl Eq for WeakPathHandle {} +272 /// The network path used for this path. +273 /// +274 /// Returns a [`FourTuple`], combining [`Self::remote_address`] and [`Self::local_ip`]. +275 pub fn network_path(&self) -> Result<FourTuple, ClosedPath> { +276 let state = self.conn.lock_without_waking("per_path_local_ip"); +277 state.inner.network_path(self.id()) +278 } +279 +280 /// Ping the remote endpoint over this path. +281 pub fn ping(&self) -> Result<(), ClosedPath> { +282 let mut state = self.conn.lock_and_wake("ping"); +283 state.inner.ping_path(self.id()) +284 } +285} +286 +287impl Drop for Path { +288 fn drop(&mut self) { +289 self.path_ref.on_drop(&self.conn); +290 } +291} +292 +293impl PartialEq for Path { +294 fn eq(&self, other: &Self) -> bool { +295 self.id() == other.id() && self.conn.stable_id() == other.conn.stable_id() +296 } +297} +298 +299/// Weak handle for a [`Path`] that does not keep the connection alive. +300/// +301/// As long as a [`WeakPathHandle`] for a path exists, that path's final stats will not be dropped even if +302/// the path was abandoned. +303/// +304/// The [`WeakPathHandle`] can be upgraded to a [`Path`] as long as its [`Connection`] has not been dropped. +305/// +306/// [`Connection`]: crate::Connection +307#[derive(Debug, Clone)] +308pub struct WeakPathHandle { +309 conn: WeakConnectionHandle, +310 path_ref: PathRef, +311} 312 -313impl WeakPathHandle { -314 /// Returns the [`PathId`] of this path. -315 pub fn id(&self) -> PathId { -316 self.path_ref.id -317 } +313impl PartialEq for WeakPathHandle { +314 fn eq(&self, other: &Self) -> bool { +315 self.id() == other.id() && self.conn.is_same_connection(&other.conn) +316 } +317} 318 -319 /// Upgrades to a [`Path`]. -320 /// -321 /// Returns `None` if the connection was dropped. -322 pub fn upgrade(&self) -> Option<Path> { -323 let conn = self.conn.upgrade_to_ref()?; -324 Some(Path { -325 conn, -326 path_ref: self.path_ref.clone(), -327 }) -328 } -329} -330 -331impl Drop for WeakPathHandle { -332 fn drop(&mut self) { -333 if let Some(conn) = self.conn.upgrade_to_ref() { -334 self.path_ref.on_drop(&conn); -335 } +319impl Eq for WeakPathHandle {} +320 +321impl WeakPathHandle { +322 /// Returns the [`PathId`] of this path. +323 pub fn id(&self) -> PathId { +324 self.path_ref.id +325 } +326 +327 /// Upgrades to a [`Path`]. +328 /// +329 /// Returns `None` if the connection was dropped. +330 pub fn upgrade(&self) -> Option<Path> { +331 let conn = self.conn.upgrade_to_ref()?; +332 Some(Path { +333 conn, +334 path_ref: self.path_ref.clone(), +335 }) 336 } 337} 338 -339/// Owner side of a path's reference counter, stored in [`State::path_refs`]. -340/// -341/// Holds the shared [`AtomicUsize`] but does not itself contribute to the count. -342/// Hands out [`PathRef`] handles via [`Self::acquire`]. -343/// -344/// [`State::path_refs`]: crate::connection::State::path_refs -345#[derive(Debug, Default)] -346pub(crate) struct PathRefOwner { -347 ref_count: Arc<AtomicUsize>, -348} -349 -350impl PathRefOwner { -351 /// Acquire a new [`PathRef`] handle, bumping the reference counter by 1. -352 pub(crate) fn acquire(&self, path_id: PathId) -> PathRef { -353 self.ref_count.fetch_add(1, Ordering::Relaxed); -354 PathRef { -355 id: path_id, -356 ref_count: self.ref_count.clone(), -357 } -358 } -359} -360 -361/// Handle side of a path's reference counter, held by [`Path`] and [`WeakPathHandle`]. -362/// -363/// Cloning bumps the counter automatically. When dropping a `PathRef`, holders must call -364/// [`Self::on_drop`] to decrement the counter and clear the corresponding entries -365/// from the connection state if the counter reaches zero. -366/// -367/// [`WeakPathHandle`]: crate::path::WeakPathHandle -368#[derive(Debug)] -369pub(crate) struct PathRef { -370 id: PathId, -371 ref_count: Arc<AtomicUsize>, -372} -373 -374impl Clone for PathRef { -375 fn clone(&self) -> Self { -376 self.ref_count.fetch_add(1, Ordering::Relaxed); -377 Self { -378 id: self.id, -379 ref_count: self.ref_count.clone(), -380 } -381 } -382} -383 -384impl PathRef { -385 /// Decreases the refcount and clears state once the counter reaches zero. -386 /// -387 /// This must be called before dropping a [`PathRef`], i.e. in the [`Drop`] impl -388 /// of its holder. -389 fn on_drop(&self, conn: &ConnectionRef) { -390 if self.ref_count.fetch_sub(1, Ordering::Relaxed) > 1 { -391 return; -392 } -393 let mut state = conn.lock_without_waking("PathRef::drop"); -394 // Re-check under the lock: a concurrent `Path::new` may have bumped -395 // the counter back up between our `fetch_sub` and the lock. -396 if self.ref_count.load(Ordering::Relaxed) > 0 { -397 return; -398 } -399 state.path_refs.remove(&self.id); -400 state.final_path_stats.remove(&self.id); -401 } -402} -403 -404/// Stream produced by [`Path::observed_external_addr`] -405/// -406/// This will always return the external address most recently reported by the remote over this -407/// path. If the extension is not negotiated, this stream will never return. -408// TODO(@divma): provide a way to check if the extension is negotiated. -409pub struct AddressDiscovery { -410 watcher: WatchStream<SocketAddr>, -411} -412 -413impl AddressDiscovery { -414 pub(super) fn new( -415 path_id: PathId, -416 mut path_events: tokio::sync::broadcast::Receiver<PathEvent>, -417 initial_value: Option<SocketAddr>, -418 runtime: Arc<dyn Runtime>, -419 ) -> Self { -420 let (tx, rx) = watch::channel(initial_value.unwrap_or_else(|| -421 // if the dummy value is used, it will be ignored -422 SocketAddr::new([0, 0, 0, 0].into(), 0))); -423 let filter = async move { -424 loop { -425 match path_events.recv().await { -426 Ok(PathEvent::ObservedAddr { id, addr: observed }) if id == path_id => { -427 tx.send_if_modified(|addr| { -428 let old = std::mem::replace(addr, observed); -429 old != *addr -430 }); -431 } -432 Ok(PathEvent::Discarded { id, .. }) if id == path_id => { -433 // If the path is closed, terminate the stream -434 break; -435 } -436 Ok(_) => { -437 // ignore any other event -438 } -439 Err(_) => { -440 // A lagged error should never happen since this (detached) task is -441 // constantly reading from the channel. Therefore, if an error does happen, -442 // the stream can terminate -443 break; -444 } -445 } -446 } -447 }; -448 -449 let watcher = if initial_value.is_some() { -450 WatchStream::new(rx) -451 } else { -452 WatchStream::from_changes(rx) -453 }; -454 -455 runtime.spawn(Box::pin(filter)); -456 // TODO(@divma): check if there's a way to ensure the future ends. AbortHandle is not an -457 // option -458 Self { watcher } -459 } -460} -461 -462impl Stream for AddressDiscovery { -463 type Item = SocketAddr; +339impl Drop for WeakPathHandle { +340 fn drop(&mut self) { +341 if let Some(conn) = self.conn.upgrade_to_ref() { +342 self.path_ref.on_drop(&conn); +343 } +344 } +345} +346 +347/// Owner side of a path's reference counter, stored in [`State::path_refs`]. +348/// +349/// Holds the shared [`AtomicUsize`] but does not itself contribute to the count. +350/// Hands out [`PathRef`] handles via [`Self::acquire`]. +351/// +352/// [`State::path_refs`]: crate::connection::State::path_refs +353#[derive(Debug, Default)] +354pub(crate) struct PathRefOwner { +355 ref_count: Arc<AtomicUsize>, +356} +357 +358impl PathRefOwner { +359 /// Acquire a new [`PathRef`] handle, bumping the reference counter by 1. +360 pub(crate) fn acquire(&self, path_id: PathId) -> PathRef { +361 self.ref_count.fetch_add(1, Ordering::Relaxed); +362 PathRef { +363 id: path_id, +364 ref_count: self.ref_count.clone(), +365 } +366 } +367} +368 +369/// Handle side of a path's reference counter, held by [`Path`] and [`WeakPathHandle`]. +370/// +371/// Cloning bumps the counter automatically. When dropping a `PathRef`, holders must call +372/// [`Self::on_drop`] to decrement the counter and clear the corresponding entries +373/// from the connection state if the counter reaches zero. +374/// +375/// [`WeakPathHandle`]: crate::path::WeakPathHandle +376#[derive(Debug)] +377pub(crate) struct PathRef { +378 id: PathId, +379 ref_count: Arc<AtomicUsize>, +380} +381 +382impl Clone for PathRef { +383 fn clone(&self) -> Self { +384 self.ref_count.fetch_add(1, Ordering::Relaxed); +385 Self { +386 id: self.id, +387 ref_count: self.ref_count.clone(), +388 } +389 } +390} +391 +392impl PathRef { +393 /// Decreases the refcount and clears state once the counter reaches zero. +394 /// +395 /// This must be called before dropping a [`PathRef`], i.e. in the [`Drop`] impl +396 /// of its holder. +397 fn on_drop(&self, conn: &ConnectionRef) { +398 if self.ref_count.fetch_sub(1, Ordering::Relaxed) > 1 { +399 return; +400 } +401 let mut state = conn.lock_without_waking("PathRef::drop"); +402 // Re-check under the lock: a concurrent `Path::new` may have bumped +403 // the counter back up between our `fetch_sub` and the lock. +404 if self.ref_count.load(Ordering::Relaxed) > 0 { +405 return; +406 } +407 state.path_refs.remove(&self.id); +408 state.final_path_stats.remove(&self.id); +409 } +410} +411 +412/// Stream produced by [`Path::observed_external_addr`] +413/// +414/// This will always return the external address most recently reported by the remote over this +415/// path. If the extension is not negotiated, this stream will never return. +416// TODO(@divma): provide a way to check if the extension is negotiated. +417pub struct AddressDiscovery { +418 watcher: WatchStream<SocketAddr>, +419} +420 +421impl AddressDiscovery { +422 pub(super) fn new( +423 path_id: PathId, +424 mut path_events: tokio::sync::broadcast::Receiver<PathEvent>, +425 initial_value: Option<SocketAddr>, +426 runtime: Arc<dyn Runtime>, +427 ) -> Self { +428 let (tx, rx) = watch::channel(initial_value.unwrap_or_else(|| +429 // if the dummy value is used, it will be ignored +430 SocketAddr::new([0, 0, 0, 0].into(), 0))); +431 let filter = async move { +432 loop { +433 match path_events.recv().await { +434 Ok(PathEvent::ObservedAddr { +435 id, addr: observed, .. +436 }) if id == path_id => { +437 tx.send_if_modified(|addr| { +438 let old = std::mem::replace(addr, observed); +439 old != *addr +440 }); +441 } +442 Ok(PathEvent::Discarded { id, .. }) if id == path_id => { +443 // If the path is closed, terminate the stream +444 break; +445 } +446 Ok(_) => { +447 // ignore any other event +448 } +449 Err(_) => { +450 // A lagged error should never happen since this (detached) task is +451 // constantly reading from the channel. Therefore, if an error does happen, +452 // the stream can terminate +453 break; +454 } +455 } +456 } +457 }; +458 +459 let watcher = if initial_value.is_some() { +460 WatchStream::new(rx) +461 } else { +462 WatchStream::from_changes(rx) +463 }; 464 -465 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { -466 Pin::new(&mut self.watcher).poll_next(cx) -467 } -468}
\ No newline at end of file +465 runtime.spawn(Box::pin(filter)); +466 // TODO(@divma): check if there's a way to ensure the future ends. AbortHandle is not an +467 // option +468 Self { watcher } +469 } +470} +471 +472impl Stream for AddressDiscovery { +473 type Item = SocketAddr; +474 +475 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { +476 Pin::new(&mut self.watcher).poll_next(cx) +477 } +478}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/address_discovery.rs.html b/pr/451/docs/src/noq_proto/address_discovery.rs.html index ecc530e19..33dc7e4d8 100644 --- a/pr/451/docs/src/noq_proto/address_discovery.rs.html +++ b/pr/451/docs/src/noq_proto/address_discovery.rs.html @@ -1,4 +1,4 @@ -address_discovery.rs - source

noq_proto/
address_discovery.rs

1//! Address discovery types from
+address_discovery.rs - source

noq_proto/
address_discovery.rs

1//! Address discovery types from
 2//! <https://datatracker.ietf.org/doc/draft-seemann-quic-address-discovery/>
 3
 4use crate::VarInt;
@@ -7,120 +7,67 @@
 7///
 8/// When enabled, this is reported as a transport parameter.
 9#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
-10pub(crate) enum Role {
-11    /// Is able to report observer addresses to other peers, but it's not interested in receiving
-12    /// reports about its own address.
-13    SendOnly,
-14    /// Is interested on reports about its own observed address, but will not report back to other
-15    /// peers.
-16    ReceiveOnly,
-17    /// Will both report and receive reports of observed addresses.
-18    Both,
-19    /// Address discovery is disabled.
-20    #[default]
-21    Disabled,
-22}
-23
-24impl TryFrom<VarInt> for Role {
-25    type Error = crate::transport_parameters::Error;
-26
-27    fn try_from(value: VarInt) -> Result<Self, Self::Error> {
-28        match value.0 {
-29            0 => Ok(Self::SendOnly),
-30            1 => Ok(Self::ReceiveOnly),
-31            2 => Ok(Self::Both),
-32            _ => Err(crate::transport_parameters::Error::IllegalValue),
-33        }
-34    }
-35}
-36
-37impl Role {
-38    /// Whether address discovery is disabled.
-39    pub(crate) fn is_disabled(&self) -> bool {
-40        matches!(self, Self::Disabled)
-41    }
+10pub(crate) struct Role {
+11    /// Whether this peer reports observed addresses to other peers.
+12    pub(crate) send: bool,
+13    /// Whether this peer wants to receive observed address reports from other peers.
+14    pub(crate) receive: bool,
+15}
+16
+17impl Role {
+18    pub(crate) const fn send_only() -> Self {
+19        Self {
+20            send: true,
+21            receive: false,
+22        }
+23    }
+24
+25    pub(crate) const fn receive_only() -> Self {
+26        Self {
+27            send: false,
+28            receive: true,
+29        }
+30    }
+31
+32    pub(crate) const fn both() -> Self {
+33        Self {
+34            send: true,
+35            receive: true,
+36        }
+37    }
+38}
+39
+40impl TryFrom<VarInt> for Role {
+41    type Error = crate::transport_parameters::Error;
 42
-43    /// Whether this peer's role allows for address reporting to other peers.
-44    fn is_reporter(&self) -> bool {
-45        matches!(self, Self::SendOnly | Self::Both)
-46    }
-47
-48    /// Whether this peer's role accepts observed address reports.
-49    fn receives_reports(&self) -> bool {
-50        matches!(self, Self::ReceiveOnly | Self::Both)
-51    }
+43    fn try_from(value: VarInt) -> Result<Self, Self::Error> {
+44        match value.0 {
+45            0 => Ok(Self::send_only()),
+46            1 => Ok(Self::receive_only()),
+47            2 => Ok(Self::both()),
+48            _ => Err(crate::transport_parameters::Error::IllegalValue),
+49        }
+50    }
+51}
 52
-53    /// Whether this peer should report observed addresses to the other peer.
-54    pub(crate) fn should_report(&self, other: &Self) -> bool {
-55        self.is_reporter() && other.receives_reports()
-56    }
-57
-58    /// Sets whether this peer should provide observed addresses to other peers.
-59    pub(crate) fn send_reports_to_peers(&mut self, provide: bool) {
-60        if provide {
-61            self.enable_sending_reports_to_peers()
-62        } else {
-63            self.disable_sending_reports_to_peers()
-64        }
-65    }
-66
-67    /// Enables sending reports of observed addresses to other peers.
-68    fn enable_sending_reports_to_peers(&mut self) {
-69        match self {
-70            Self::SendOnly => {} // already enabled
-71            Self::ReceiveOnly => *self = Self::Both,
-72            Self::Both => {} // already enabled
-73            Self::Disabled => *self = Self::SendOnly,
-74        }
-75    }
-76
-77    /// Disables sending reports of observed addresses to other peers.
-78    fn disable_sending_reports_to_peers(&mut self) {
-79        match self {
-80            Self::SendOnly => *self = Self::Disabled,
-81            Self::ReceiveOnly => {} // already disabled
-82            Self::Both => *self = Self::ReceiveOnly,
-83            Self::Disabled => {} // already disabled
-84        }
-85    }
-86
-87    /// Sets whether this peer should accept received reports of observed addresses from other
-88    /// peers.
-89    pub(crate) fn receive_reports_from_peers(&mut self, receive: bool) {
-90        if receive {
-91            self.enable_receiving_reports_from_peers()
-92        } else {
-93            self.disable_receiving_reports_from_peers()
-94        }
-95    }
-96
-97    /// Enables receiving reports of observed addresses from other peers.
-98    fn enable_receiving_reports_from_peers(&mut self) {
-99        match self {
-100            Self::SendOnly => *self = Self::Both,
-101            Self::ReceiveOnly => {} // already enabled
-102            Self::Both => {}        // already enabled
-103            Self::Disabled => *self = Self::ReceiveOnly,
-104        }
-105    }
-106
-107    /// Disables receiving reports of observed addresses from other peers.
-108    fn disable_receiving_reports_from_peers(&mut self) {
-109        match self {
-110            Self::SendOnly => {} // already disabled
-111            Self::ReceiveOnly => *self = Self::Disabled,
-112            Self::Both => *self = Self::SendOnly,
-113            Self::Disabled => {} // already disabled
-114        }
-115    }
-116
-117    /// Gives the [`VarInt`] representing this [`Role`] as a transport parameter.
-118    pub(crate) fn as_transport_parameter(&self) -> Option<VarInt> {
-119        match self {
-120            Self::SendOnly => Some(VarInt(0)),
-121            Self::ReceiveOnly => Some(VarInt(1)),
-122            Self::Both => Some(VarInt(2)),
-123            Self::Disabled => None,
-124        }
-125    }
-126}
\ No newline at end of file +53impl Role { +54 /// Whether address discovery is disabled. +55 pub(crate) fn is_disabled(&self) -> bool { +56 !self.send && !self.receive +57 } +58 +59 /// Whether this peer should report observed addresses to the other peer. +60 pub(crate) fn should_report(&self, other: &Self) -> bool { +61 self.send && other.receive +62 } +63 +64 /// Gives the [`VarInt`] representing this [`Role`] as a transport parameter. +65 pub(crate) fn as_transport_parameter(&self) -> Option<VarInt> { +66 match (self.send, self.receive) { +67 (false, false) => None, +68 (true, false) => Some(VarInt(0)), +69 (false, true) => Some(VarInt(1)), +70 (true, true) => Some(VarInt(2)), +71 } +72 } +73}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/config/transport.rs.html b/pr/451/docs/src/noq_proto/config/transport.rs.html index 2f2e14d12..ce25864bd 100644 --- a/pr/451/docs/src/noq_proto/config/transport.rs.html +++ b/pr/451/docs/src/noq_proto/config/transport.rs.html @@ -67,321 +67,321 @@ 67 pub(crate) default_path_keep_alive_interval: Option<Duration>, 68 69 pub(crate) max_remote_nat_traversal_addresses: Option<NonZeroU8>, -70 -71 #[cfg(feature = "qlog")] -72 pub(crate) qlog_factory: Option<Arc<dyn QlogFactory>>, -73} -74 -75impl TransportConfig { -76 /// Maximum number of incoming bidirectional streams that may be open concurrently -77 /// -78 /// Must be nonzero for the peer to open any bidirectional streams. -79 /// -80 /// Worst-case memory use is directly proportional to `max_concurrent_bidi_streams * -81 /// stream_receive_window`, with an upper bound proportional to `receive_window`. -82 pub fn max_concurrent_bidi_streams(&mut self, value: VarInt) -> &mut Self { -83 self.max_concurrent_bidi_streams = value; -84 self -85 } -86 -87 /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams -88 pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self { -89 self.max_concurrent_uni_streams = value; -90 self -91 } -92 -93 /// Maximum duration of inactivity to accept before timing out the connection. -94 /// -95 /// The true idle timeout is the minimum of this and the peer's own max idle timeout. `None` -96 /// represents an infinite timeout. Defaults to 30 seconds. -97 /// -98 /// **WARNING**: If a peer or its network path malfunctions or acts maliciously, an infinite -99 /// idle timeout can result in permanently hung futures! -100 /// -101 /// ``` -102 /// # use std::{convert::TryInto, time::Duration}; -103 /// # use noq_proto::{TransportConfig, VarInt, VarIntBoundsExceeded}; -104 /// # fn main() -> Result<(), VarIntBoundsExceeded> { -105 /// let mut config = TransportConfig::default(); -106 /// -107 /// // Set the idle timeout as `VarInt`-encoded milliseconds -108 /// config.max_idle_timeout(Some(VarInt::from_u32(10_000).into())); -109 /// -110 /// // Set the idle timeout as a `Duration` -111 /// config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?)); -112 /// # Ok(()) -113 /// # } -114 /// ``` -115 pub fn max_idle_timeout(&mut self, value: Option<IdleTimeout>) -> &mut Self { -116 self.max_idle_timeout = value.map(|t| t.0); -117 self -118 } -119 -120 /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream -121 /// before becoming blocked. -122 /// -123 /// This should be set to at least the expected connection latency multiplied by the maximum -124 /// desired throughput. Setting this smaller than `receive_window` helps ensure that a single -125 /// stream doesn't monopolize receive buffers, which may otherwise occur if the application -126 /// chooses not to read from a large stream for a time while still requiring data on other -127 /// streams. -128 pub fn stream_receive_window(&mut self, value: VarInt) -> &mut Self { -129 self.stream_receive_window = value; -130 self -131 } -132 -133 /// Maximum number of bytes the peer may transmit across all streams of a connection before -134 /// becoming blocked. -135 /// -136 /// This should be set to at least the expected connection latency multiplied by the maximum -137 /// desired throughput. Larger values can be useful to allow maximum throughput within a -138 /// stream while another is blocked. -139 pub fn receive_window(&mut self, value: VarInt) -> &mut Self { -140 self.receive_window = value; -141 self -142 } -143 -144 /// Maximum number of bytes to transmit to a peer without acknowledgment -145 /// -146 /// Provides an upper bound on memory when communicating with peers that issue large amounts of -147 /// flow control credit. Endpoints that wish to handle large numbers of connections robustly -148 /// should take care to set this low enough to guarantee memory exhaustion does not occur if -149 /// every connection uses the entire window. -150 pub fn send_window(&mut self, value: u64) -> &mut Self { -151 self.send_window = value; -152 self -153 } -154 -155 /// Whether to implement fair queuing for send streams having the same priority. -156 /// -157 /// When enabled, connections schedule data from outgoing streams having the same priority in a -158 /// round-robin fashion. When disabled, streams are scheduled in the order they are written to. -159 /// -160 /// Note that this only affects streams with the same priority. Higher priority streams always -161 /// take precedence over lower priority streams. -162 /// -163 /// Disabling fairness can reduce fragmentation and protocol overhead for workloads that use -164 /// many small streams. -165 pub fn send_fairness(&mut self, value: bool) -> &mut Self { -166 self.send_fairness = value; -167 self -168 } -169 -170 /// Maximum reordering in packet number space before FACK style loss detection considers a -171 /// packet lost. Should not be less than 3, per RFC5681. -172 pub fn packet_threshold(&mut self, value: u32) -> &mut Self { -173 self.packet_threshold = value; -174 self -175 } -176 -177 /// Maximum reordering in time space before time based loss detection considers a packet lost, -178 /// as a factor of RTT -179 pub fn time_threshold(&mut self, value: f32) -> &mut Self { -180 self.time_threshold = value; -181 self -182 } -183 -184 /// The RTT used before an RTT sample is taken -185 pub fn initial_rtt(&mut self, value: Duration) -> &mut Self { -186 self.initial_rtt = value; -187 self -188 } -189 -190 /// The initial value to be used as the maximum UDP payload size before running MTU discovery -191 /// (see [`TransportConfig::mtu_discovery_config`]). -192 /// -193 /// Must be at least 1200, which is the default, and known to be safe for typical internet -194 /// applications. Larger values are more efficient, but increase the risk of packet loss due to -195 /// exceeding the network path's IP MTU. If the provided value is higher than what the network -196 /// path actually supports, packet loss will eventually trigger black hole detection and bring -197 /// it down to [`TransportConfig::min_mtu`]. -198 pub fn initial_mtu(&mut self, value: u16) -> &mut Self { -199 self.initial_mtu = value.max(INITIAL_MTU); -200 self -201 } -202 -203 pub(crate) fn get_initial_mtu(&self) -> u16 { -204 self.initial_mtu.max(self.min_mtu) -205 } -206 -207 /// The maximum UDP payload size guaranteed to be supported by the network. -208 /// -209 /// Must be at least 1200, which is the default, and lower than or equal to -210 /// [`TransportConfig::initial_mtu`]. -211 /// -212 /// Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links -213 /// outside of either endpoint's control. Extreme care should be used when raising this value -214 /// outside of private networks where these factors are fully controlled. If the provided value -215 /// is higher than what the network path actually supports, the result will be unpredictable and -216 /// catastrophic packet loss, without a possibility of repair. Prefer -217 /// [`TransportConfig::initial_mtu`] together with -218 /// [`TransportConfig::mtu_discovery_config`] to set a maximum UDP payload size that robustly -219 /// adapts to the network. -220 pub fn min_mtu(&mut self, value: u16) -> &mut Self { -221 self.min_mtu = value.max(INITIAL_MTU); -222 self -223 } -224 -225 /// Specifies the MTU discovery config (see [`MtuDiscoveryConfig`] for details). -226 /// -227 /// Enabled by default. -228 pub fn mtu_discovery_config(&mut self, value: Option<MtuDiscoveryConfig>) -> &mut Self { -229 self.mtu_discovery_config = value; -230 self -231 } -232 -233 /// Pad UDP datagrams carrying application data to current maximum UDP payload size -234 /// -235 /// Disabled by default. UDP datagrams containing loss probes are exempt from padding. -236 /// -237 /// Enabling this helps mitigate traffic analysis by network observers, but it increases -238 /// bandwidth usage. Without this mitigation precise plain text size of application datagrams as -239 /// well as the total size of stream write bursts can be inferred by observers under certain -240 /// conditions. This analysis requires either an uncongested connection or application datagrams -241 /// too large to be coalesced. -242 pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self { -243 self.pad_to_mtu = value; -244 self -245 } -246 -247 /// Specifies the ACK frequency config (see [`AckFrequencyConfig`] for details) -248 /// -249 /// The provided configuration will be ignored if the peer does not support the acknowledgement -250 /// frequency QUIC extension. -251 /// -252 /// Defaults to `None`, which disables controlling the peer's acknowledgement frequency. Even -253 /// if set to `None`, the local side still supports the acknowledgement frequency QUIC -254 /// extension and may use it in other ways. -255 pub fn ack_frequency_config(&mut self, value: Option<AckFrequencyConfig>) -> &mut Self { -256 self.ack_frequency_config = value; -257 self -258 } -259 -260 /// Configures an outbound rate limit (in bytes per second) for each connection. -261 /// -262 /// Defaults to `None`, which disables rate limiting. -263 pub fn max_outgoing_bytes_per_second(&mut self, value: Option<u64>) -> &mut Self { -264 self.max_outgoing_bytes_per_second = value; -265 self -266 } -267 -268 /// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion. -269 pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self { -270 self.persistent_congestion_threshold = value; -271 self -272 } -273 -274 /// Period of inactivity before sending a keep-alive packet -275 /// -276 /// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out. -277 /// -278 /// `None` to disable, which is the default. Only one side of any given connection needs keep-alive -279 /// enabled for the connection to be preserved. Must be set lower than the idle_timeout of both -280 /// peers to be effective. -281 pub fn keep_alive_interval(&mut self, value: Option<Duration>) -> &mut Self { -282 self.keep_alive_interval = value; -283 self -284 } -285 -286 /// Maximum quantity of out-of-order crypto layer data to buffer -287 pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self { -288 self.crypto_buffer_size = value; -289 self -290 } -291 -292 /// Whether the implementation is permitted to set the spin bit on this connection -293 /// -294 /// This allows passive observers to easily judge the round trip time of a connection, which can -295 /// be useful for network administration but sacrifices a small amount of privacy. -296 pub fn allow_spin(&mut self, value: bool) -> &mut Self { -297 self.allow_spin = value; -298 self -299 } -300 -301 /// Maximum number of incoming application datagram bytes to buffer, or None to disable -302 /// incoming datagrams -303 /// -304 /// The peer is forbidden to send single datagrams larger than this size. If the aggregate size -305 /// of all datagrams that have been received from the peer but not consumed by the application -306 /// exceeds this value, old datagrams are dropped until it is no longer exceeded. -307 pub fn datagram_receive_buffer_size(&mut self, value: Option<usize>) -> &mut Self { -308 self.datagram_receive_buffer_size = value; -309 self -310 } -311 -312 /// Maximum number of outgoing application datagram bytes to buffer -313 /// -314 /// While datagrams are sent ASAP, it is possible for an application to generate data faster -315 /// than the link, or even the underlying hardware, can transmit them. This limits the amount of -316 /// memory that may be consumed in that case. When the send buffer is full and a new datagram is -317 /// sent, older datagrams are dropped until sufficient space is available. -318 pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self { -319 self.datagram_send_buffer_size = value; -320 self -321 } -322 -323 /// Whether to force every packet number to be used -324 /// -325 /// By default, packet numbers are occasionally skipped to ensure peers aren't ACKing packets -326 /// before they see them. -327 #[cfg(test)] -328 pub(crate) fn deterministic_packet_numbers(&mut self, enabled: bool) -> &mut Self { -329 self.deterministic_packet_numbers = enabled; -330 self -331 } -332 -333 /// How to construct new `congestion::Controller`s -334 /// -335 /// Typically the refcounted configuration of a `congestion::Controller`, -336 /// e.g. a `congestion::NewRenoConfig`. -337 /// -338 /// # Example -339 /// ``` -340 /// # use noq_proto::*; use std::sync::Arc; -341 /// let mut config = TransportConfig::default(); -342 /// config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default())); -343 /// ``` -344 pub fn congestion_controller_factory( -345 &mut self, -346 factory: Arc<dyn congestion::ControllerFactory + Send + Sync + 'static>, -347 ) -> &mut Self { -348 self.congestion_controller_factory = factory; -349 self -350 } -351 -352 /// Whether to use "Generic Segmentation Offload" to accelerate transmits, when supported by the -353 /// environment -354 /// -355 /// Defaults to `true`. -356 /// -357 /// GSO dramatically reduces CPU consumption when sending large numbers of packets with the same -358 /// headers, such as when transmitting bulk data on a connection. However, it is not supported -359 /// by all network interface drivers or packet inspection tools. `noq-udp` will attempt to -360 /// disable GSO automatically when unavailable, but this can lead to spurious packet loss at -361 /// startup, temporarily degrading performance. -362 pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self { -363 self.enable_segmentation_offload = enabled; -364 self -365 } -366 -367 /// Whether to send observed address reports to peers. -368 /// -369 /// This will aid peers in inferring their reachable address, which in most NATd networks -370 /// will not be easily available to them. -371 pub fn send_observed_address_reports(&mut self, enabled: bool) -> &mut Self { -372 self.address_discovery_role.send_reports_to_peers(enabled); -373 self -374 } -375 -376 /// Whether to receive observed address reports from other peers. -377 /// -378 /// Peers with the address discovery extension enabled that are willing to provide observed -379 /// address reports will do so if this transport parameter is set. In general, observed address -380 /// reports cannot be trusted. This, however, can aid the current endpoint in inferring its -381 /// reachable address, which in most NATd networks will not be easily available. -382 pub fn receive_observed_address_reports(&mut self, enabled: bool) -> &mut Self { -383 self.address_discovery_role -384 .receive_reports_from_peers(enabled); +70 pub(crate) server_handshake_migration: bool, +71 +72 #[cfg(feature = "qlog")] +73 pub(crate) qlog_factory: Option<Arc<dyn QlogFactory>>, +74} +75 +76impl TransportConfig { +77 /// Maximum number of incoming bidirectional streams that may be open concurrently +78 /// +79 /// Must be nonzero for the peer to open any bidirectional streams. +80 /// +81 /// Worst-case memory use is directly proportional to `max_concurrent_bidi_streams * +82 /// stream_receive_window`, with an upper bound proportional to `receive_window`. +83 pub fn max_concurrent_bidi_streams(&mut self, value: VarInt) -> &mut Self { +84 self.max_concurrent_bidi_streams = value; +85 self +86 } +87 +88 /// Variant of `max_concurrent_bidi_streams` affecting unidirectional streams +89 pub fn max_concurrent_uni_streams(&mut self, value: VarInt) -> &mut Self { +90 self.max_concurrent_uni_streams = value; +91 self +92 } +93 +94 /// Maximum duration of inactivity to accept before timing out the connection. +95 /// +96 /// The true idle timeout is the minimum of this and the peer's own max idle timeout. `None` +97 /// represents an infinite timeout. Defaults to 30 seconds. +98 /// +99 /// **WARNING**: If a peer or its network path malfunctions or acts maliciously, an infinite +100 /// idle timeout can result in permanently hung futures! +101 /// +102 /// ``` +103 /// # use std::{convert::TryInto, time::Duration}; +104 /// # use noq_proto::{TransportConfig, VarInt, VarIntBoundsExceeded}; +105 /// # fn main() -> Result<(), VarIntBoundsExceeded> { +106 /// let mut config = TransportConfig::default(); +107 /// +108 /// // Set the idle timeout as `VarInt`-encoded milliseconds +109 /// config.max_idle_timeout(Some(VarInt::from_u32(10_000).into())); +110 /// +111 /// // Set the idle timeout as a `Duration` +112 /// config.max_idle_timeout(Some(Duration::from_secs(10).try_into()?)); +113 /// # Ok(()) +114 /// # } +115 /// ``` +116 pub fn max_idle_timeout(&mut self, value: Option<IdleTimeout>) -> &mut Self { +117 self.max_idle_timeout = value.map(|t| t.0); +118 self +119 } +120 +121 /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream +122 /// before becoming blocked. +123 /// +124 /// This should be set to at least the expected connection latency multiplied by the maximum +125 /// desired throughput. Setting this smaller than `receive_window` helps ensure that a single +126 /// stream doesn't monopolize receive buffers, which may otherwise occur if the application +127 /// chooses not to read from a large stream for a time while still requiring data on other +128 /// streams. +129 pub fn stream_receive_window(&mut self, value: VarInt) -> &mut Self { +130 self.stream_receive_window = value; +131 self +132 } +133 +134 /// Maximum number of bytes the peer may transmit across all streams of a connection before +135 /// becoming blocked. +136 /// +137 /// This should be set to at least the expected connection latency multiplied by the maximum +138 /// desired throughput. Larger values can be useful to allow maximum throughput within a +139 /// stream while another is blocked. +140 pub fn receive_window(&mut self, value: VarInt) -> &mut Self { +141 self.receive_window = value; +142 self +143 } +144 +145 /// Maximum number of bytes to transmit to a peer without acknowledgment +146 /// +147 /// Provides an upper bound on memory when communicating with peers that issue large amounts of +148 /// flow control credit. Endpoints that wish to handle large numbers of connections robustly +149 /// should take care to set this low enough to guarantee memory exhaustion does not occur if +150 /// every connection uses the entire window. +151 pub fn send_window(&mut self, value: u64) -> &mut Self { +152 self.send_window = value; +153 self +154 } +155 +156 /// Whether to implement fair queuing for send streams having the same priority. +157 /// +158 /// When enabled, connections schedule data from outgoing streams having the same priority in a +159 /// round-robin fashion. When disabled, streams are scheduled in the order they are written to. +160 /// +161 /// Note that this only affects streams with the same priority. Higher priority streams always +162 /// take precedence over lower priority streams. +163 /// +164 /// Disabling fairness can reduce fragmentation and protocol overhead for workloads that use +165 /// many small streams. +166 pub fn send_fairness(&mut self, value: bool) -> &mut Self { +167 self.send_fairness = value; +168 self +169 } +170 +171 /// Maximum reordering in packet number space before FACK style loss detection considers a +172 /// packet lost. Should not be less than 3, per RFC5681. +173 pub fn packet_threshold(&mut self, value: u32) -> &mut Self { +174 self.packet_threshold = value; +175 self +176 } +177 +178 /// Maximum reordering in time space before time based loss detection considers a packet lost, +179 /// as a factor of RTT +180 pub fn time_threshold(&mut self, value: f32) -> &mut Self { +181 self.time_threshold = value; +182 self +183 } +184 +185 /// The RTT used before an RTT sample is taken +186 pub fn initial_rtt(&mut self, value: Duration) -> &mut Self { +187 self.initial_rtt = value; +188 self +189 } +190 +191 /// The initial value to be used as the maximum UDP payload size before running MTU discovery +192 /// (see [`TransportConfig::mtu_discovery_config`]). +193 /// +194 /// Must be at least 1200, which is the default, and known to be safe for typical internet +195 /// applications. Larger values are more efficient, but increase the risk of packet loss due to +196 /// exceeding the network path's IP MTU. If the provided value is higher than what the network +197 /// path actually supports, packet loss will eventually trigger black hole detection and bring +198 /// it down to [`TransportConfig::min_mtu`]. +199 pub fn initial_mtu(&mut self, value: u16) -> &mut Self { +200 self.initial_mtu = value.max(INITIAL_MTU); +201 self +202 } +203 +204 pub(crate) fn get_initial_mtu(&self) -> u16 { +205 self.initial_mtu.max(self.min_mtu) +206 } +207 +208 /// The maximum UDP payload size guaranteed to be supported by the network. +209 /// +210 /// Must be at least 1200, which is the default, and lower than or equal to +211 /// [`TransportConfig::initial_mtu`]. +212 /// +213 /// Real-world MTUs can vary according to ISP, VPN, and properties of intermediate network links +214 /// outside of either endpoint's control. Extreme care should be used when raising this value +215 /// outside of private networks where these factors are fully controlled. If the provided value +216 /// is higher than what the network path actually supports, the result will be unpredictable and +217 /// catastrophic packet loss, without a possibility of repair. Prefer +218 /// [`TransportConfig::initial_mtu`] together with +219 /// [`TransportConfig::mtu_discovery_config`] to set a maximum UDP payload size that robustly +220 /// adapts to the network. +221 pub fn min_mtu(&mut self, value: u16) -> &mut Self { +222 self.min_mtu = value.max(INITIAL_MTU); +223 self +224 } +225 +226 /// Specifies the MTU discovery config (see [`MtuDiscoveryConfig`] for details). +227 /// +228 /// Enabled by default. +229 pub fn mtu_discovery_config(&mut self, value: Option<MtuDiscoveryConfig>) -> &mut Self { +230 self.mtu_discovery_config = value; +231 self +232 } +233 +234 /// Pad UDP datagrams carrying application data to current maximum UDP payload size +235 /// +236 /// Disabled by default. UDP datagrams containing loss probes are exempt from padding. +237 /// +238 /// Enabling this helps mitigate traffic analysis by network observers, but it increases +239 /// bandwidth usage. Without this mitigation precise plain text size of application datagrams as +240 /// well as the total size of stream write bursts can be inferred by observers under certain +241 /// conditions. This analysis requires either an uncongested connection or application datagrams +242 /// too large to be coalesced. +243 pub fn pad_to_mtu(&mut self, value: bool) -> &mut Self { +244 self.pad_to_mtu = value; +245 self +246 } +247 +248 /// Specifies the ACK frequency config (see [`AckFrequencyConfig`] for details) +249 /// +250 /// The provided configuration will be ignored if the peer does not support the acknowledgement +251 /// frequency QUIC extension. +252 /// +253 /// Defaults to `None`, which disables controlling the peer's acknowledgement frequency. Even +254 /// if set to `None`, the local side still supports the acknowledgement frequency QUIC +255 /// extension and may use it in other ways. +256 pub fn ack_frequency_config(&mut self, value: Option<AckFrequencyConfig>) -> &mut Self { +257 self.ack_frequency_config = value; +258 self +259 } +260 +261 /// Configures an outbound rate limit (in bytes per second) for each connection. +262 /// +263 /// Defaults to `None`, which disables rate limiting. +264 pub fn max_outgoing_bytes_per_second(&mut self, value: Option<u64>) -> &mut Self { +265 self.max_outgoing_bytes_per_second = value; +266 self +267 } +268 +269 /// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion. +270 pub fn persistent_congestion_threshold(&mut self, value: u32) -> &mut Self { +271 self.persistent_congestion_threshold = value; +272 self +273 } +274 +275 /// Period of inactivity before sending a keep-alive packet +276 /// +277 /// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out. +278 /// +279 /// `None` to disable, which is the default. Only one side of any given connection needs keep-alive +280 /// enabled for the connection to be preserved. Must be set lower than the idle_timeout of both +281 /// peers to be effective. +282 pub fn keep_alive_interval(&mut self, value: Option<Duration>) -> &mut Self { +283 self.keep_alive_interval = value; +284 self +285 } +286 +287 /// Maximum quantity of out-of-order crypto layer data to buffer +288 pub fn crypto_buffer_size(&mut self, value: usize) -> &mut Self { +289 self.crypto_buffer_size = value; +290 self +291 } +292 +293 /// Whether the implementation is permitted to set the spin bit on this connection +294 /// +295 /// This allows passive observers to easily judge the round trip time of a connection, which can +296 /// be useful for network administration but sacrifices a small amount of privacy. +297 pub fn allow_spin(&mut self, value: bool) -> &mut Self { +298 self.allow_spin = value; +299 self +300 } +301 +302 /// Maximum number of incoming application datagram bytes to buffer, or None to disable +303 /// incoming datagrams +304 /// +305 /// The peer is forbidden to send single datagrams larger than this size. If the aggregate size +306 /// of all datagrams that have been received from the peer but not consumed by the application +307 /// exceeds this value, old datagrams are dropped until it is no longer exceeded. +308 pub fn datagram_receive_buffer_size(&mut self, value: Option<usize>) -> &mut Self { +309 self.datagram_receive_buffer_size = value; +310 self +311 } +312 +313 /// Maximum number of outgoing application datagram bytes to buffer +314 /// +315 /// While datagrams are sent ASAP, it is possible for an application to generate data faster +316 /// than the link, or even the underlying hardware, can transmit them. This limits the amount of +317 /// memory that may be consumed in that case. When the send buffer is full and a new datagram is +318 /// sent, older datagrams are dropped until sufficient space is available. +319 pub fn datagram_send_buffer_size(&mut self, value: usize) -> &mut Self { +320 self.datagram_send_buffer_size = value; +321 self +322 } +323 +324 /// Whether to force every packet number to be used +325 /// +326 /// By default, packet numbers are occasionally skipped to ensure peers aren't ACKing packets +327 /// before they see them. +328 #[cfg(test)] +329 pub(crate) fn deterministic_packet_numbers(&mut self, enabled: bool) -> &mut Self { +330 self.deterministic_packet_numbers = enabled; +331 self +332 } +333 +334 /// How to construct new `congestion::Controller`s +335 /// +336 /// Typically the refcounted configuration of a `congestion::Controller`, +337 /// e.g. a `congestion::NewRenoConfig`. +338 /// +339 /// # Example +340 /// ``` +341 /// # use noq_proto::*; use std::sync::Arc; +342 /// let mut config = TransportConfig::default(); +343 /// config.congestion_controller_factory(Arc::new(congestion::NewRenoConfig::default())); +344 /// ``` +345 pub fn congestion_controller_factory( +346 &mut self, +347 factory: Arc<dyn congestion::ControllerFactory + Send + Sync + 'static>, +348 ) -> &mut Self { +349 self.congestion_controller_factory = factory; +350 self +351 } +352 +353 /// Whether to use "Generic Segmentation Offload" to accelerate transmits, when supported by the +354 /// environment +355 /// +356 /// Defaults to `true`. +357 /// +358 /// GSO dramatically reduces CPU consumption when sending large numbers of packets with the same +359 /// headers, such as when transmitting bulk data on a connection. However, it is not supported +360 /// by all network interface drivers or packet inspection tools. `noq-udp` will attempt to +361 /// disable GSO automatically when unavailable, but this can lead to spurious packet loss at +362 /// startup, temporarily degrading performance. +363 pub fn enable_segmentation_offload(&mut self, enabled: bool) -> &mut Self { +364 self.enable_segmentation_offload = enabled; +365 self +366 } +367 +368 /// Whether to send observed address reports to peers. +369 /// +370 /// This will aid peers in inferring their reachable address, which in most NATd networks +371 /// will not be easily available to them. +372 pub fn send_observed_address_reports(&mut self, enabled: bool) -> &mut Self { +373 self.address_discovery_role.send = enabled; +374 self +375 } +376 +377 /// Whether to receive observed address reports from other peers. +378 /// +379 /// Peers with the address discovery extension enabled that are willing to provide observed +380 /// address reports will do so if this transport parameter is set. In general, observed address +381 /// reports cannot be trusted. This, however, can aid the current endpoint in inferring its +382 /// reachable address, which in most NATd networks will not be easily available. +383 pub fn receive_observed_address_reports(&mut self, enabled: bool) -> &mut Self { +384 self.address_discovery_role.receive = enabled; 385 self 386 } 387 @@ -457,452 +457,474 @@ 457 self 458 } 459 -460 /// Configures qlog capturing by setting a [`QlogFactory`]. +460 /// Sets whether the server is allowed to migrate once during the handshake. 461 /// -462 /// This assigns a [`QlogFactory`] that produces qlog capture configurations for -463 /// individual connections. -464 #[cfg(feature = "qlog")] -465 pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self { -466 self.qlog_factory = Some(factory); -467 self -468 } -469 -470 /// Configures qlog capturing through the `QLOGDIR` environment variable. -471 /// -472 /// This uses [`QlogFileFactory::from_env`] to create a factory to write qlog traces -473 /// into the directory set through the `QLOGDIR` environment variable. -474 /// -475 /// If `QLOGDIR` is not set, no traces will be written. If `QLOGDIR` is set to a path -476 /// that does not exist, it will be created. -477 /// -478 /// The files will be prefixed with `prefix`. -479 #[cfg(feature = "qlog")] -480 pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self { -481 self.qlog_factory(Arc::new(QlogFileFactory::from_env().with_prefix(prefix))) -482 } -483 -484 /// Configures qlog capturing into a directory. -485 /// -486 /// This uses [`QlogFileFactory`] to create a factory to write qlog traces into -487 /// the specified directory. The files will be prefixed with `prefix`. -488 #[cfg(feature = "qlog")] -489 pub fn qlog_from_path(&mut self, path: impl AsRef<Path>, prefix: &str) -> &mut Self { -490 self.qlog_factory(Arc::new( -491 QlogFileFactory::new(path.as_ref().to_owned()).with_prefix(prefix), -492 )) -493 } -494 -495 pub(crate) fn create_qlog_sink( -496 &self, -497 side: Side, -498 remote: SocketAddr, -499 initial_dst_cid: ConnectionId, -500 now: Instant, -501 ) -> QlogSink { -502 #[cfg(not(feature = "qlog"))] -503 let sink = { -504 let _ = (side, remote, initial_dst_cid, now); -505 QlogSink::default() -506 }; -507 -508 #[cfg(feature = "qlog")] -509 let sink = { -510 if let Some(config) = self -511 .qlog_factory -512 .as_ref() -513 .and_then(|factory| factory.for_connection(side, remote, initial_dst_cid, now)) -514 { -515 QlogSink::new(config, initial_dst_cid, side, now) -516 } else { -517 QlogSink::default() -518 } -519 }; -520 -521 sink -522 } -523} -524 -525impl Default for TransportConfig { -526 fn default() -> Self { -527 const EXPECTED_RTT: u32 = 100; // ms -528 const MAX_STREAM_BANDWIDTH: u32 = 12500 * 1000; // bytes/s -529 // Window size needed to avoid pipeline -530 // stalls -531 const STREAM_RWND: u32 = MAX_STREAM_BANDWIDTH / 1000 * EXPECTED_RTT; -532 -533 Self { -534 max_concurrent_bidi_streams: 100u32.into(), -535 max_concurrent_uni_streams: 100u32.into(), -536 // 30 second default recommended by RFC 9308 § 3.2 -537 max_idle_timeout: Some(VarInt(30_000)), -538 stream_receive_window: STREAM_RWND.into(), -539 receive_window: VarInt::MAX, -540 send_window: (8 * STREAM_RWND).into(), -541 send_fairness: true, -542 -543 packet_threshold: 3, -544 time_threshold: 9.0 / 8.0, -545 initial_rtt: Duration::from_millis(333), // per spec, intentionally distinct from EXPECTED_RTT -546 initial_mtu: INITIAL_MTU, -547 min_mtu: INITIAL_MTU, -548 mtu_discovery_config: Some(MtuDiscoveryConfig::default()), -549 pad_to_mtu: false, -550 ack_frequency_config: None, -551 max_outgoing_bytes_per_second: None, -552 -553 persistent_congestion_threshold: 3, -554 keep_alive_interval: None, -555 crypto_buffer_size: 16 * 1024, -556 allow_spin: true, -557 datagram_receive_buffer_size: Some(STREAM_RWND as usize), -558 datagram_send_buffer_size: 1024 * 1024, -559 #[cfg(test)] -560 deterministic_packet_numbers: false, +462 /// **Enabling this is not RFC9000 compliant.** +463 /// +464 /// Defaults to `false`. +465 /// +466 /// Enabling this allows the server to migrate once during the handshake: it can send a +467 /// response from a different address than the client's initial packet was sent to. Once +468 /// an authenticated Handshake packet is received the server can no longer migrate +469 /// during the handshake (or after the handshake if not other extension enables this). +470 /// +471 /// This can be used to duplicate the client's initial packet to multiple addresses for +472 /// the server and accept the fastest response. The server will discard all but the +473 /// first such initial, considering any remaining as duplicates. +474 pub fn server_handshake_migration(&mut self, allow_migration: bool) -> &mut Self { +475 self.server_handshake_migration = allow_migration; +476 self +477 } +478 +479 /// Configures qlog capturing by setting a [`QlogFactory`]. +480 /// +481 /// This assigns a [`QlogFactory`] that produces qlog capture configurations for +482 /// individual connections. +483 #[cfg(feature = "qlog")] +484 pub fn qlog_factory(&mut self, factory: Arc<dyn QlogFactory>) -> &mut Self { +485 self.qlog_factory = Some(factory); +486 self +487 } +488 +489 /// Configures qlog capturing through the `QLOGDIR` environment variable. +490 /// +491 /// This uses [`QlogFileFactory::from_env`] to create a factory to write qlog traces +492 /// into the directory set through the `QLOGDIR` environment variable. +493 /// +494 /// If `QLOGDIR` is not set, no traces will be written. If `QLOGDIR` is set to a path +495 /// that does not exist, it will be created. +496 /// +497 /// The files will be prefixed with `prefix`. +498 #[cfg(feature = "qlog")] +499 pub fn qlog_from_env(&mut self, prefix: &str) -> &mut Self { +500 self.qlog_factory(Arc::new(QlogFileFactory::from_env().with_prefix(prefix))) +501 } +502 +503 /// Configures qlog capturing into a directory. +504 /// +505 /// This uses [`QlogFileFactory`] to create a factory to write qlog traces into +506 /// the specified directory. The files will be prefixed with `prefix`. +507 #[cfg(feature = "qlog")] +508 pub fn qlog_from_path(&mut self, path: impl AsRef<Path>, prefix: &str) -> &mut Self { +509 self.qlog_factory(Arc::new( +510 QlogFileFactory::new(path.as_ref().to_owned()).with_prefix(prefix), +511 )) +512 } +513 +514 pub(crate) fn create_qlog_sink( +515 &self, +516 side: Side, +517 remote: SocketAddr, +518 initial_dst_cid: ConnectionId, +519 now: Instant, +520 ) -> QlogSink { +521 #[cfg(not(feature = "qlog"))] +522 let sink = { +523 let _ = (side, remote, initial_dst_cid, now); +524 QlogSink::default() +525 }; +526 +527 #[cfg(feature = "qlog")] +528 let sink = { +529 if let Some(config) = self +530 .qlog_factory +531 .as_ref() +532 .and_then(|factory| factory.for_connection(side, remote, initial_dst_cid, now)) +533 { +534 QlogSink::new(config, initial_dst_cid, side, now) +535 } else { +536 QlogSink::default() +537 } +538 }; +539 +540 sink +541 } +542} +543 +544impl Default for TransportConfig { +545 fn default() -> Self { +546 const EXPECTED_RTT: u32 = 100; // ms +547 const MAX_STREAM_BANDWIDTH: u32 = 12500 * 1000; // bytes/s +548 // Window size needed to avoid pipeline +549 // stalls +550 const STREAM_RWND: u32 = MAX_STREAM_BANDWIDTH / 1000 * EXPECTED_RTT; +551 +552 Self { +553 max_concurrent_bidi_streams: 100u32.into(), +554 max_concurrent_uni_streams: 100u32.into(), +555 // 30 second default recommended by RFC 9308 § 3.2 +556 max_idle_timeout: Some(VarInt(30_000)), +557 stream_receive_window: STREAM_RWND.into(), +558 receive_window: VarInt::MAX, +559 send_window: (8 * STREAM_RWND).into(), +560 send_fairness: true, 561 -562 congestion_controller_factory: Arc::new(congestion::CubicConfig::default()), -563 -564 enable_segmentation_offload: true, -565 -566 address_discovery_role: address_discovery::Role::default(), -567 -568 // disabled multipath by default -569 max_concurrent_multipath_paths: None, -570 default_path_max_idle_timeout: None, -571 default_path_keep_alive_interval: None, -572 -573 // nat traversal disabled by default -574 max_remote_nat_traversal_addresses: None, -575 -576 #[cfg(feature = "qlog")] -577 qlog_factory: None, -578 } -579 } -580} -581 -582impl fmt::Debug for TransportConfig { -583 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -584 let Self { -585 max_concurrent_bidi_streams, -586 max_concurrent_uni_streams, -587 max_idle_timeout, -588 stream_receive_window, -589 receive_window, -590 send_window, -591 send_fairness, -592 packet_threshold, -593 time_threshold, -594 initial_rtt, -595 initial_mtu, -596 min_mtu, -597 mtu_discovery_config, -598 pad_to_mtu, -599 ack_frequency_config, -600 max_outgoing_bytes_per_second, -601 persistent_congestion_threshold, -602 keep_alive_interval, -603 crypto_buffer_size, -604 allow_spin, -605 datagram_receive_buffer_size, -606 datagram_send_buffer_size, -607 #[cfg(test)] -608 deterministic_packet_numbers: _, -609 congestion_controller_factory: _, -610 enable_segmentation_offload, -611 address_discovery_role, -612 max_concurrent_multipath_paths, -613 default_path_max_idle_timeout, -614 default_path_keep_alive_interval, -615 max_remote_nat_traversal_addresses, -616 #[cfg(feature = "qlog")] -617 qlog_factory, -618 } = self; -619 let mut s = fmt.debug_struct("TransportConfig"); -620 -621 s.field("max_concurrent_bidi_streams", max_concurrent_bidi_streams) -622 .field("max_concurrent_uni_streams", max_concurrent_uni_streams) -623 .field("max_idle_timeout", max_idle_timeout) -624 .field("stream_receive_window", stream_receive_window) -625 .field("receive_window", receive_window) -626 .field("send_window", send_window) -627 .field("send_fairness", send_fairness) -628 .field("packet_threshold", packet_threshold) -629 .field("time_threshold", time_threshold) -630 .field("initial_rtt", initial_rtt) -631 .field("initial_mtu", initial_mtu) -632 .field("min_mtu", min_mtu) -633 .field("mtu_discovery_config", mtu_discovery_config) -634 .field("pad_to_mtu", pad_to_mtu) -635 .field("ack_frequency_config", ack_frequency_config) -636 .field( -637 "max_outgoing_bytes_per_second", -638 max_outgoing_bytes_per_second, -639 ) -640 .field( -641 "persistent_congestion_threshold", -642 persistent_congestion_threshold, -643 ) -644 .field("keep_alive_interval", keep_alive_interval) -645 .field("crypto_buffer_size", crypto_buffer_size) -646 .field("allow_spin", allow_spin) -647 .field("datagram_receive_buffer_size", datagram_receive_buffer_size) -648 .field("datagram_send_buffer_size", datagram_send_buffer_size) -649 // congestion_controller_factory not debug -650 .field("enable_segmentation_offload", enable_segmentation_offload) -651 .field("address_discovery_role", address_discovery_role) -652 .field( -653 "max_concurrent_multipath_paths", -654 max_concurrent_multipath_paths, -655 ) -656 .field( -657 "default_path_max_idle_timeout", -658 default_path_max_idle_timeout, -659 ) -660 .field( -661 "default_path_keep_alive_interval", -662 default_path_keep_alive_interval, -663 ) -664 .field( -665 "max_remote_nat_traversal_addresses", -666 max_remote_nat_traversal_addresses, -667 ); -668 #[cfg(feature = "qlog")] -669 s.field("qlog_factory", &qlog_factory.is_some()); -670 -671 s.finish_non_exhaustive() -672 } -673} -674 -675/// Parameters for controlling the peer's acknowledgement frequency -676/// -677/// The parameters provided in this config will be sent to the peer at the beginning of the -678/// connection, so it can take them into account when sending acknowledgements (see each parameter's -679/// description for details on how it influences acknowledgement frequency). -680/// -681/// noq's implementation follows the fourth draft of the -682/// [QUIC Acknowledgement Frequency extension](https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency-04). -683/// The defaults produce behavior slightly different than the behavior without this extension, -684/// because they change the way reordered packets are handled (see -685/// [`AckFrequencyConfig::reordering_threshold`] for details). -686#[derive(Clone, Debug)] -687pub struct AckFrequencyConfig { -688 pub(crate) ack_eliciting_threshold: VarInt, -689 pub(crate) max_ack_delay: Option<Duration>, -690 pub(crate) reordering_threshold: VarInt, -691} +562 packet_threshold: 3, +563 time_threshold: 9.0 / 8.0, +564 initial_rtt: Duration::from_millis(333), // per spec, intentionally distinct from EXPECTED_RTT +565 initial_mtu: INITIAL_MTU, +566 min_mtu: INITIAL_MTU, +567 mtu_discovery_config: Some(MtuDiscoveryConfig::default()), +568 pad_to_mtu: false, +569 ack_frequency_config: None, +570 max_outgoing_bytes_per_second: None, +571 +572 persistent_congestion_threshold: 3, +573 keep_alive_interval: None, +574 crypto_buffer_size: 16 * 1024, +575 allow_spin: true, +576 datagram_receive_buffer_size: Some(STREAM_RWND as usize), +577 datagram_send_buffer_size: 1024 * 1024, +578 #[cfg(test)] +579 deterministic_packet_numbers: false, +580 +581 congestion_controller_factory: Arc::new(congestion::CubicConfig::default()), +582 +583 enable_segmentation_offload: true, +584 +585 address_discovery_role: address_discovery::Role::default(), +586 +587 // disabled multipath by default +588 max_concurrent_multipath_paths: None, +589 default_path_max_idle_timeout: None, +590 default_path_keep_alive_interval: None, +591 +592 // nat traversal disabled by default +593 max_remote_nat_traversal_addresses: None, +594 server_handshake_migration: false, +595 +596 #[cfg(feature = "qlog")] +597 qlog_factory: None, +598 } +599 } +600} +601 +602impl fmt::Debug for TransportConfig { +603 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { +604 let Self { +605 max_concurrent_bidi_streams, +606 max_concurrent_uni_streams, +607 max_idle_timeout, +608 stream_receive_window, +609 receive_window, +610 send_window, +611 send_fairness, +612 packet_threshold, +613 time_threshold, +614 initial_rtt, +615 initial_mtu, +616 min_mtu, +617 mtu_discovery_config, +618 pad_to_mtu, +619 ack_frequency_config, +620 max_outgoing_bytes_per_second, +621 persistent_congestion_threshold, +622 keep_alive_interval, +623 crypto_buffer_size, +624 allow_spin, +625 datagram_receive_buffer_size, +626 datagram_send_buffer_size, +627 #[cfg(test)] +628 deterministic_packet_numbers: _, +629 congestion_controller_factory: _, +630 enable_segmentation_offload, +631 address_discovery_role, +632 max_concurrent_multipath_paths, +633 default_path_max_idle_timeout, +634 default_path_keep_alive_interval, +635 max_remote_nat_traversal_addresses, +636 server_handshake_migration, +637 #[cfg(feature = "qlog")] +638 qlog_factory, +639 } = self; +640 let mut s = fmt.debug_struct("TransportConfig"); +641 +642 s.field("max_concurrent_bidi_streams", max_concurrent_bidi_streams) +643 .field("max_concurrent_uni_streams", max_concurrent_uni_streams) +644 .field("max_idle_timeout", max_idle_timeout) +645 .field("stream_receive_window", stream_receive_window) +646 .field("receive_window", receive_window) +647 .field("send_window", send_window) +648 .field("send_fairness", send_fairness) +649 .field("packet_threshold", packet_threshold) +650 .field("time_threshold", time_threshold) +651 .field("initial_rtt", initial_rtt) +652 .field("initial_mtu", initial_mtu) +653 .field("min_mtu", min_mtu) +654 .field("mtu_discovery_config", mtu_discovery_config) +655 .field("pad_to_mtu", pad_to_mtu) +656 .field("ack_frequency_config", ack_frequency_config) +657 .field( +658 "max_outgoing_bytes_per_second", +659 max_outgoing_bytes_per_second, +660 ) +661 .field( +662 "persistent_congestion_threshold", +663 persistent_congestion_threshold, +664 ) +665 .field("keep_alive_interval", keep_alive_interval) +666 .field("crypto_buffer_size", crypto_buffer_size) +667 .field("allow_spin", allow_spin) +668 .field("datagram_receive_buffer_size", datagram_receive_buffer_size) +669 .field("datagram_send_buffer_size", datagram_send_buffer_size) +670 // congestion_controller_factory not debug +671 .field("enable_segmentation_offload", enable_segmentation_offload) +672 .field("address_discovery_role", address_discovery_role) +673 .field( +674 "max_concurrent_multipath_paths", +675 max_concurrent_multipath_paths, +676 ) +677 .field( +678 "default_path_max_idle_timeout", +679 default_path_max_idle_timeout, +680 ) +681 .field( +682 "default_path_keep_alive_interval", +683 default_path_keep_alive_interval, +684 ) +685 .field( +686 "max_remote_nat_traversal_addresses", +687 max_remote_nat_traversal_addresses, +688 ) +689 .field("server_handshake_migration", server_handshake_migration); +690 #[cfg(feature = "qlog")] +691 s.field("qlog_factory", &qlog_factory.is_some()); 692 -693impl AckFrequencyConfig { -694 /// The ack-eliciting threshold we will request the peer to use -695 /// -696 /// This threshold represents the number of ack-eliciting packets an endpoint may receive -697 /// without immediately sending an ACK. -698 /// -699 /// The remote peer should send at least one ACK frame when more than this number of -700 /// ack-eliciting packets have been received. A value of 0 results in a receiver immediately -701 /// acknowledging every ack-eliciting packet. -702 /// -703 /// Defaults to 1, which sends ACK frames for every other ack-eliciting packet. -704 pub fn ack_eliciting_threshold(&mut self, value: VarInt) -> &mut Self { -705 self.ack_eliciting_threshold = value; -706 self -707 } -708 -709 /// The `max_ack_delay` we will request the peer to use -710 /// -711 /// This parameter represents the maximum amount of time that an endpoint waits before sending -712 /// an ACK when the ack-eliciting threshold hasn't been reached. -713 /// -714 /// The effective `max_ack_delay` will be clamped to be at least the peer's `min_ack_delay` -715 /// transport parameter, and at most the greater of the current path RTT or 25ms. -716 /// -717 /// Defaults to `None`, in which case the peer's original `max_ack_delay` will be used, as -718 /// obtained from its transport parameters. -719 pub fn max_ack_delay(&mut self, value: Option<Duration>) -> &mut Self { -720 self.max_ack_delay = value; -721 self -722 } -723 -724 /// The reordering threshold we will request the peer to use -725 /// -726 /// This threshold represents the amount of out-of-order packets that will trigger an endpoint -727 /// to send an ACK, without waiting for `ack_eliciting_threshold` to be exceeded or for -728 /// `max_ack_delay` to be elapsed. -729 /// -730 /// A value of 0 indicates out-of-order packets do not elicit an immediate ACK. A value of 1 -731 /// immediately acknowledges any packets that are received out of order (this is also the -732 /// behavior when the extension is disabled). -733 /// -734 /// It is recommended to set this value to [`TransportConfig::packet_threshold`] minus one. -735 /// Since the default value for [`TransportConfig::packet_threshold`] is 3, this value defaults -736 /// to 2. -737 pub fn reordering_threshold(&mut self, value: VarInt) -> &mut Self { -738 self.reordering_threshold = value; -739 self -740 } -741} -742 -743impl Default for AckFrequencyConfig { -744 fn default() -> Self { -745 Self { -746 ack_eliciting_threshold: VarInt(1), -747 max_ack_delay: None, -748 reordering_threshold: VarInt(2), -749 } -750 } -751} -752 -753/// Parameters governing MTU discovery. -754/// -755/// # The why of MTU discovery -756/// -757/// By design, QUIC ensures during the handshake that the network path between the client and the -758/// server is able to transmit unfragmented UDP packets with a body of 1200 bytes. In other words, -759/// once the connection is established, we know that the network path's maximum transmission unit -760/// (MTU) is of at least 1200 bytes (plus IP and UDP headers). Because of this, a QUIC endpoint can -761/// split outgoing data in packets of 1200 bytes, with confidence that the network will be able to -762/// deliver them (if the endpoint were to send bigger packets, they could prove too big and end up -763/// being dropped). -764/// -765/// There is, however, a significant overhead associated to sending a packet. If the same -766/// information can be sent in fewer packets, that results in higher throughput. The amount of -767/// packets that need to be sent is inversely proportional to the MTU: the higher the MTU, the -768/// bigger the packets that can be sent, and the fewer packets that are needed to transmit a given -769/// amount of bytes. -770/// -771/// Most networks have an MTU higher than 1200. Through MTU discovery, endpoints can detect the -772/// path's MTU and, if it turns out to be higher, start sending bigger packets. -773/// -774/// # MTU discovery internals -775/// -776/// noq implements MTU discovery through DPLPMTUD (Datagram Packetization Layer Path MTU -777/// Discovery), described in [section 14.3 of RFC -778/// 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-14.3). This method consists of sending -779/// QUIC packets padded to a particular size (called PMTU probes), and waiting to see if the remote -780/// peer responds with an ACK. If an ACK is received, that means the probe arrived at the remote -781/// peer, which in turn means that the network path's MTU is of at least the packet's size. If the -782/// probe is lost, it is sent another 2 times before concluding that the MTU is lower than the -783/// packet's size. -784/// -785/// MTU discovery runs on a schedule (e.g. every 600 seconds) specified through -786/// [`MtuDiscoveryConfig::interval`]. The first run happens right after the handshake, and -787/// subsequent discoveries are scheduled to run when the interval has elapsed, starting from the -788/// last time when MTU discovery completed. -789/// -790/// Since the search space for MTUs is quite big (the smallest possible MTU is 1200, and the highest -791/// is 65527), noq performs a binary search to keep the number of probes as low as possible. The -792/// lower bound of the search is equal to [`TransportConfig::initial_mtu`] in the -793/// initial MTU discovery run, and is equal to the currently discovered MTU in subsequent runs. The -794/// upper bound is determined by the minimum of [`MtuDiscoveryConfig::upper_bound`] and the -795/// `max_udp_payload_size` transport parameter received from the peer during the handshake. -796/// -797/// # Black hole detection -798/// -799/// If, at some point, the network path no longer accepts packets of the detected size, packet loss -800/// will eventually trigger black hole detection and reset the detected MTU to 1200. In that case, -801/// MTU discovery will be triggered after [`MtuDiscoveryConfig::black_hole_cooldown`] (ignoring the -802/// timer that was set based on [`MtuDiscoveryConfig::interval`]). -803/// -804/// # Interaction between peers -805/// -806/// There is no guarantee that the MTU on the path between A and B is the same as the MTU of the -807/// path between B and A. Therefore, each peer in the connection needs to run MTU discovery -808/// independently in order to discover the path's MTU. -809#[derive(Clone, Debug)] -810pub struct MtuDiscoveryConfig { -811 pub(crate) interval: Duration, -812 pub(crate) upper_bound: u16, -813 pub(crate) minimum_change: u16, -814 pub(crate) black_hole_cooldown: Duration, -815} -816 -817impl MtuDiscoveryConfig { -818 /// Specifies the time to wait after completing MTU discovery before starting a new MTU -819 /// discovery run. -820 /// -821 /// Defaults to 600 seconds, as recommended by [RFC -822 /// 8899](https://www.rfc-editor.org/rfc/rfc8899). -823 pub fn interval(&mut self, value: Duration) -> &mut Self { -824 self.interval = value; -825 self -826 } -827 -828 /// Specifies the upper bound to the max UDP payload size that MTU discovery will search for. -829 /// -830 /// Defaults to 1452, to stay within Ethernet's MTU when using IPv4 and IPv6. The highest -831 /// allowed value is 65527, which corresponds to the maximum permitted UDP payload on IPv6. -832 /// -833 /// It is safe to use an arbitrarily high upper bound, regardless of the network path's MTU. The -834 /// only drawback is that MTU discovery might take more time to finish. -835 pub fn upper_bound(&mut self, value: u16) -> &mut Self { -836 self.upper_bound = value.min(MAX_UDP_PAYLOAD); -837 self -838 } -839 -840 /// Specifies the amount of time that MTU discovery should wait after a black hole was detected -841 /// before running again. Defaults to one minute. +693 s.finish_non_exhaustive() +694 } +695} +696 +697/// Parameters for controlling the peer's acknowledgement frequency +698/// +699/// The parameters provided in this config will be sent to the peer at the beginning of the +700/// connection, so it can take them into account when sending acknowledgements (see each parameter's +701/// description for details on how it influences acknowledgement frequency). +702/// +703/// noq's implementation follows the fourth draft of the +704/// [QUIC Acknowledgement Frequency extension](https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency-04). +705/// The defaults produce behavior slightly different than the behavior without this extension, +706/// because they change the way reordered packets are handled (see +707/// [`AckFrequencyConfig::reordering_threshold`] for details). +708#[derive(Clone, Debug)] +709pub struct AckFrequencyConfig { +710 pub(crate) ack_eliciting_threshold: VarInt, +711 pub(crate) max_ack_delay: Option<Duration>, +712 pub(crate) reordering_threshold: VarInt, +713} +714 +715impl AckFrequencyConfig { +716 /// The ack-eliciting threshold we will request the peer to use +717 /// +718 /// This threshold represents the number of ack-eliciting packets an endpoint may receive +719 /// without immediately sending an ACK. +720 /// +721 /// The remote peer should send at least one ACK frame when more than this number of +722 /// ack-eliciting packets have been received. A value of 0 results in a receiver immediately +723 /// acknowledging every ack-eliciting packet. +724 /// +725 /// Defaults to 1, which sends ACK frames for every other ack-eliciting packet. +726 pub fn ack_eliciting_threshold(&mut self, value: VarInt) -> &mut Self { +727 self.ack_eliciting_threshold = value; +728 self +729 } +730 +731 /// The `max_ack_delay` we will request the peer to use +732 /// +733 /// This parameter represents the maximum amount of time that an endpoint waits before sending +734 /// an ACK when the ack-eliciting threshold hasn't been reached. +735 /// +736 /// The effective `max_ack_delay` will be clamped to be at least the peer's `min_ack_delay` +737 /// transport parameter, and at most the greater of the current path RTT or 25ms. +738 /// +739 /// Defaults to `None`, in which case the peer's original `max_ack_delay` will be used, as +740 /// obtained from its transport parameters. +741 pub fn max_ack_delay(&mut self, value: Option<Duration>) -> &mut Self { +742 self.max_ack_delay = value; +743 self +744 } +745 +746 /// The reordering threshold we will request the peer to use +747 /// +748 /// This threshold represents the amount of out-of-order packets that will trigger an endpoint +749 /// to send an ACK, without waiting for `ack_eliciting_threshold` to be exceeded or for +750 /// `max_ack_delay` to be elapsed. +751 /// +752 /// A value of 0 indicates out-of-order packets do not elicit an immediate ACK. A value of 1 +753 /// immediately acknowledges any packets that are received out of order (this is also the +754 /// behavior when the extension is disabled). +755 /// +756 /// It is recommended to set this value to [`TransportConfig::packet_threshold`] minus one. +757 /// Since the default value for [`TransportConfig::packet_threshold`] is 3, this value defaults +758 /// to 2. +759 pub fn reordering_threshold(&mut self, value: VarInt) -> &mut Self { +760 self.reordering_threshold = value; +761 self +762 } +763} +764 +765impl Default for AckFrequencyConfig { +766 fn default() -> Self { +767 Self { +768 ack_eliciting_threshold: VarInt(1), +769 max_ack_delay: None, +770 reordering_threshold: VarInt(2), +771 } +772 } +773} +774 +775/// Parameters governing MTU discovery. +776/// +777/// # The why of MTU discovery +778/// +779/// By design, QUIC ensures during the handshake that the network path between the client and the +780/// server is able to transmit unfragmented UDP packets with a body of 1200 bytes. In other words, +781/// once the connection is established, we know that the network path's maximum transmission unit +782/// (MTU) is of at least 1200 bytes (plus IP and UDP headers). Because of this, a QUIC endpoint can +783/// split outgoing data in packets of 1200 bytes, with confidence that the network will be able to +784/// deliver them (if the endpoint were to send bigger packets, they could prove too big and end up +785/// being dropped). +786/// +787/// There is, however, a significant overhead associated to sending a packet. If the same +788/// information can be sent in fewer packets, that results in higher throughput. The amount of +789/// packets that need to be sent is inversely proportional to the MTU: the higher the MTU, the +790/// bigger the packets that can be sent, and the fewer packets that are needed to transmit a given +791/// amount of bytes. +792/// +793/// Most networks have an MTU higher than 1200. Through MTU discovery, endpoints can detect the +794/// path's MTU and, if it turns out to be higher, start sending bigger packets. +795/// +796/// # MTU discovery internals +797/// +798/// noq implements MTU discovery through DPLPMTUD (Datagram Packetization Layer Path MTU +799/// Discovery), described in [section 14.3 of RFC +800/// 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-14.3). This method consists of sending +801/// QUIC packets padded to a particular size (called PMTU probes), and waiting to see if the remote +802/// peer responds with an ACK. If an ACK is received, that means the probe arrived at the remote +803/// peer, which in turn means that the network path's MTU is of at least the packet's size. If the +804/// probe is lost, it is sent another 2 times before concluding that the MTU is lower than the +805/// packet's size. +806/// +807/// MTU discovery runs on a schedule (e.g. every 600 seconds) specified through +808/// [`MtuDiscoveryConfig::interval`]. The first run happens right after the handshake, and +809/// subsequent discoveries are scheduled to run when the interval has elapsed, starting from the +810/// last time when MTU discovery completed. +811/// +812/// Since the search space for MTUs is quite big (the smallest possible MTU is 1200, and the highest +813/// is 65527), noq performs a binary search to keep the number of probes as low as possible. The +814/// lower bound of the search is equal to [`TransportConfig::initial_mtu`] in the +815/// initial MTU discovery run, and is equal to the currently discovered MTU in subsequent runs. The +816/// upper bound is determined by the minimum of [`MtuDiscoveryConfig::upper_bound`] and the +817/// `max_udp_payload_size` transport parameter received from the peer during the handshake. +818/// +819/// # Black hole detection +820/// +821/// If, at some point, the network path no longer accepts packets of the detected size, packet loss +822/// will eventually trigger black hole detection and reset the detected MTU to 1200. In that case, +823/// MTU discovery will be triggered after [`MtuDiscoveryConfig::black_hole_cooldown`] (ignoring the +824/// timer that was set based on [`MtuDiscoveryConfig::interval`]). +825/// +826/// # Interaction between peers +827/// +828/// There is no guarantee that the MTU on the path between A and B is the same as the MTU of the +829/// path between B and A. Therefore, each peer in the connection needs to run MTU discovery +830/// independently in order to discover the path's MTU. +831#[derive(Clone, Debug)] +832pub struct MtuDiscoveryConfig { +833 pub(crate) interval: Duration, +834 pub(crate) upper_bound: u16, +835 pub(crate) minimum_change: u16, +836 pub(crate) black_hole_cooldown: Duration, +837} +838 +839impl MtuDiscoveryConfig { +840 /// Specifies the time to wait after completing MTU discovery before starting a new MTU +841 /// discovery run. 842 /// -843 /// Black hole detection can be spuriously triggered in case of congestion, so it makes sense to -844 /// try MTU discovery again after a short period of time. -845 pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self { -846 self.black_hole_cooldown = value; +843 /// Defaults to 600 seconds, as recommended by [RFC +844 /// 8899](https://www.rfc-editor.org/rfc/rfc8899). +845 pub fn interval(&mut self, value: Duration) -> &mut Self { +846 self.interval = value; 847 self 848 } 849 -850 /// Specifies the minimum MTU change to stop the MTU discovery phase. -851 /// Defaults to 20. -852 pub fn minimum_change(&mut self, value: u16) -> &mut Self { -853 self.minimum_change = value; -854 self -855 } -856} -857 -858impl Default for MtuDiscoveryConfig { -859 fn default() -> Self { -860 Self { -861 interval: Duration::from_secs(600), -862 upper_bound: 1452, -863 black_hole_cooldown: Duration::from_secs(60), -864 minimum_change: 20, -865 } -866 } -867} -868 -869/// Maximum duration of inactivity to accept before timing out the connection -870/// -871/// This wraps an underlying [`VarInt`], representing the duration in milliseconds. Values can be -872/// constructed by converting directly from `VarInt`, or using `TryFrom<Duration>`. -873/// -874/// ``` -875/// # use std::{convert::TryFrom, time::Duration}; -876/// # use noq_proto::{IdleTimeout, VarIntBoundsExceeded, VarInt}; -877/// # fn main() -> Result<(), VarIntBoundsExceeded> { -878/// // A `VarInt`-encoded value in milliseconds -879/// let timeout = IdleTimeout::from(VarInt::from_u32(10_000)); -880/// -881/// // Try to convert a `Duration` into a `VarInt`-encoded timeout -882/// let timeout = IdleTimeout::try_from(Duration::from_secs(10))?; -883/// # Ok(()) -884/// # } -885/// ``` -886#[derive(Default, Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] -887pub struct IdleTimeout(VarInt); -888 -889impl From<VarInt> for IdleTimeout { -890 fn from(inner: VarInt) -> Self { -891 Self(inner) -892 } -893} -894 -895impl std::convert::TryFrom<Duration> for IdleTimeout { -896 type Error = VarIntBoundsExceeded; -897 -898 fn try_from(timeout: Duration) -> Result<Self, Self::Error> { -899 let inner = VarInt::try_from(timeout.as_millis())?; -900 Ok(Self(inner)) -901 } -902} -903 -904impl fmt::Debug for IdleTimeout { -905 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -906 self.0.fmt(f) -907 } -908}
\ No newline at end of file +850 /// Specifies the upper bound to the max UDP payload size that MTU discovery will search for. +851 /// +852 /// Defaults to 1452, to stay within Ethernet's MTU when using IPv4 and IPv6. The highest +853 /// allowed value is 65527, which corresponds to the maximum permitted UDP payload on IPv6. +854 /// +855 /// It is safe to use an arbitrarily high upper bound, regardless of the network path's MTU. The +856 /// only drawback is that MTU discovery might take more time to finish. +857 pub fn upper_bound(&mut self, value: u16) -> &mut Self { +858 self.upper_bound = value.min(MAX_UDP_PAYLOAD); +859 self +860 } +861 +862 /// Specifies the amount of time that MTU discovery should wait after a black hole was detected +863 /// before running again. Defaults to one minute. +864 /// +865 /// Black hole detection can be spuriously triggered in case of congestion, so it makes sense to +866 /// try MTU discovery again after a short period of time. +867 pub fn black_hole_cooldown(&mut self, value: Duration) -> &mut Self { +868 self.black_hole_cooldown = value; +869 self +870 } +871 +872 /// Specifies the minimum MTU change to stop the MTU discovery phase. +873 /// Defaults to 20. +874 pub fn minimum_change(&mut self, value: u16) -> &mut Self { +875 self.minimum_change = value; +876 self +877 } +878} +879 +880impl Default for MtuDiscoveryConfig { +881 fn default() -> Self { +882 Self { +883 interval: Duration::from_secs(600), +884 upper_bound: 1452, +885 black_hole_cooldown: Duration::from_secs(60), +886 minimum_change: 20, +887 } +888 } +889} +890 +891/// Maximum duration of inactivity to accept before timing out the connection +892/// +893/// This wraps an underlying [`VarInt`], representing the duration in milliseconds. Values can be +894/// constructed by converting directly from `VarInt`, or using `TryFrom<Duration>`. +895/// +896/// ``` +897/// # use std::{convert::TryFrom, time::Duration}; +898/// # use noq_proto::{IdleTimeout, VarIntBoundsExceeded, VarInt}; +899/// # fn main() -> Result<(), VarIntBoundsExceeded> { +900/// // A `VarInt`-encoded value in milliseconds +901/// let timeout = IdleTimeout::from(VarInt::from_u32(10_000)); +902/// +903/// // Try to convert a `Duration` into a `VarInt`-encoded timeout +904/// let timeout = IdleTimeout::try_from(Duration::from_secs(10))?; +905/// # Ok(()) +906/// # } +907/// ``` +908#[derive(Default, Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +909pub struct IdleTimeout(VarInt); +910 +911impl From<VarInt> for IdleTimeout { +912 fn from(inner: VarInt) -> Self { +913 Self(inner) +914 } +915} +916 +917impl std::convert::TryFrom<Duration> for IdleTimeout { +918 type Error = VarIntBoundsExceeded; +919 +920 fn try_from(timeout: Duration) -> Result<Self, Self::Error> { +921 let inner = VarInt::try_from(timeout.as_millis())?; +922 Ok(Self(inner)) +923 } +924} +925 +926impl fmt::Debug for IdleTimeout { +927 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +928 self.0.fmt(f) +929 } +930}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/mod.rs.html b/pr/451/docs/src/noq_proto/connection/mod.rs.html index 70f3e2465..2af3f0bc4 100644 --- a/pr/451/docs/src/noq_proto/connection/mod.rs.html +++ b/pr/451/docs/src/noq_proto/connection/mod.rs.html @@ -12,7 +12,7 @@ 12use frame::StreamMetaVec; 13 14use rand::{RngExt, SeedableRng, rngs::StdRng}; -15use rustc_hash::{FxHashMap, FxHashSet}; +15use rustc_hash::FxHashMap; 16use thiserror::Error; 17use tracing::{debug, error, trace, trace_span, warn}; 18 @@ -25,7680 +25,7785 @@ 25 config::{ServerConfig, TransportConfig}, 26 congestion::Controller, 27 connection::{ -28 qlog::{QlogRecvPacket, QlogSink}, -29 spaces::LostPacket, -30 stats::PathStatsMap, -31 timer::{ConnTimer, PathTimer}, -32 }, -33 crypto::{self, Keys}, -34 frame::{ -35 self, Close, DataBlocked, Datagram, FrameStruct, NewToken, ObservedAddr, StreamDataBlocked, -36 StreamsBlocked, -37 }, -38 n0_nat_traversal, -39 packet::{ -40 FixedLengthConnectionIdParser, Header, InitialHeader, InitialPacket, LongType, Packet, -41 PacketNumber, PartialDecode, SpaceId, -42 }, -43 range_set::ArrayRangeSet, -44 shared::{ -45 ConnectionEvent, ConnectionEventInner, ConnectionId, DatagramConnectionEvent, EcnCodepoint, -46 EndpointEvent, EndpointEventInner, -47 }, -48 token::{ResetToken, Token, TokenPayload}, -49 transport_parameters::TransportParameters, -50}; -51 -52mod ack_frequency; -53use ack_frequency::AckFrequencyState; -54 -55mod assembler; -56pub use assembler::Chunk; -57 -58mod cid_state; -59use cid_state::CidState; -60 -61mod datagrams; -62use datagrams::DatagramState; -63pub use datagrams::{Datagrams, SendDatagramError}; -64 -65mod mtud; -66mod pacing; -67 -68mod packet_builder; -69use packet_builder::{PacketBuilder, PadDatagram}; -70 -71mod packet_crypto; -72use packet_crypto::CryptoState; -73pub(crate) use packet_crypto::EncryptionLevel; -74 -75mod paths; -76pub use paths::{ -77 ClosedPath, PathAbandonReason, PathEvent, PathId, PathStatus, RttEstimator, SetPathStatusError, -78}; -79use paths::{PathData, PathState}; -80 -81pub(crate) mod qlog; -82pub(crate) mod send_buffer; -83 -84pub(crate) mod spaces; -85#[cfg(fuzzing)] -86pub use spaces::Retransmits; -87#[cfg(not(fuzzing))] -88use spaces::Retransmits; -89pub(crate) use spaces::SpaceKind; -90use spaces::{PacketSpace, SendableFrames, SentPacket, ThinRetransmits}; -91 -92mod stats; -93pub use stats::{ConnectionStats, FrameStats, PathStats, UdpStats}; -94 -95mod streams; -96#[cfg(fuzzing)] -97pub use streams::StreamsState; -98#[cfg(not(fuzzing))] -99use streams::StreamsState; -100pub use streams::{ -101 Chunks, ClosedStream, FinishError, ReadError, ReadableError, RecvStream, SendStream, -102 ShouldTransmit, StreamEvent, Streams, WriteError, -103}; -104 -105mod timer; -106use timer::{Timer, TimerTable}; -107 -108mod transmit_buf; -109use transmit_buf::TransmitBuf; -110 -111mod state; -112 -113#[cfg(not(fuzzing))] -114use state::State; -115#[cfg(fuzzing)] -116pub use state::State; -117use state::StateType; -118 -119/// Protocol state and logic for a single QUIC connection -120/// -121/// Objects of this type receive [`ConnectionEvent`]s and emit [`EndpointEvent`]s and application -122/// [`Event`]s to make progress. To handle timeouts, a `Connection` returns timer updates and -123/// expects timeouts through various methods. A number of simple getter methods are exposed -124/// to allow callers to inspect some of the connection state. -125/// -126/// `Connection` has roughly 4 types of methods: -127/// -128/// - A. Simple getters, taking `&self` -129/// - B. Handlers for incoming events from the network or system, named `handle_*`. -130/// - C. State machine mutators, for incoming commands from the application. For convenience we -131/// refer to this as "performing I/O" below, however as per the design of this library none of the -132/// functions actually perform system-level I/O. For example, [`read`](RecvStream::read) and -133/// [`write`](SendStream::write), but also things like [`reset`](SendStream::reset). -134/// - D. Polling functions for outgoing events or actions for the caller to -135/// take, named `poll_*`. -136/// -137/// The simplest way to use this API correctly is to call (B) and (C) whenever -138/// appropriate, then after each of those calls, as soon as feasible call all -139/// polling methods (D) and deal with their outputs appropriately, e.g. by -140/// passing it to the application or by making a system-level I/O call. You -141/// should call the polling functions in this order: -142/// -143/// 1. [`poll_transmit`](Self::poll_transmit) -144/// 2. [`poll_timeout`](Self::poll_timeout) -145/// 3. [`poll_endpoint_events`](Self::poll_endpoint_events) -146/// 4. [`poll`](Self::poll) -147/// -148/// Currently the only actual dependency is from (2) to (1), however additional -149/// dependencies may be added in future, so the above order is recommended. -150/// -151/// (A) may be called whenever desired. -152/// -153/// Care should be made to ensure that the input events represent monotonically -154/// increasing time. Specifically, calling [`handle_timeout`](Self::handle_timeout) -155/// with events of the same [`Instant`] may be interleaved in any order with a -156/// call to [`handle_event`](Self::handle_event) at that same instant; however -157/// events or timeouts with different instants must not be interleaved. -158pub struct Connection { -159 endpoint_config: Arc<EndpointConfig>, -160 config: Arc<TransportConfig>, -161 rng: StdRng, -162 /// Consolidated cryptographic state -163 crypto_state: CryptoState, -164 /// The CID we initially chose, for use during the handshake -165 handshake_cid: ConnectionId, -166 /// The CID the peer initially chose, for use during the handshake -167 remote_handshake_cid: ConnectionId, -168 /// The [`PathData`] for each path -169 /// -170 /// This needs to be ordered because [`Connection::poll_transmit`] needs to -171 /// deterministically select the next PathId to send on. -172 // TODO(flub): well does it really? But deterministic is nice for now. -173 paths: BTreeMap<PathId, PathState>, -174 /// Counter to uniquely identify every [`PathData`] created in this connection. -175 /// -176 /// Each [`PathData`] gets a [`PathData::generation`] that is unique among all -177 /// [`PathData`]s created in the lifetime of this connection. This helps identify the -178 /// correct path when RFC9000-style migrations happen, even when they are -179 /// aborted. -180 /// -181 /// Multipath does not change this, each path can also undergo RFC9000-style -182 /// migrations. So a single multipath path ID could see several [`PathData`]s each with -183 /// their unique [`PathData::generation]. -184 path_generation_counter: u64, -185 /// Whether MTU detection is supported in this environment -186 allow_mtud: bool, -187 state: State, -188 side: ConnectionSide, -189 /// Transport parameters set by the peer -190 peer_params: TransportParameters, -191 /// Source ConnectionId of the first packet received from the peer -192 original_remote_cid: ConnectionId, -193 /// Destination ConnectionId sent by the client on the first Initial -194 initial_dst_cid: ConnectionId, -195 /// The value that the server included in the Source Connection ID field of a Retry packet, if -196 /// one was received -197 retry_src_cid: Option<ConnectionId>, -198 /// Events returned by [`Connection::poll`] -199 events: VecDeque<Event>, -200 endpoint_events: VecDeque<EndpointEventInner>, -201 /// Whether the spin bit is in use for this connection -202 spin_enabled: bool, -203 /// Outgoing spin bit state -204 spin: bool, -205 /// Packet number spaces: initial, handshake, 1-RTT -206 spaces: [PacketSpace; 3], -207 /// Highest usable packet space. -208 highest_space: SpaceKind, -209 /// Negotiated idle timeout -210 idle_timeout: Option<Duration>, -211 timers: TimerTable, -212 /// Number of packets received which could not be authenticated -213 authentication_failures: u64, -214 -215 // -216 // Queued non-retransmittable 1-RTT data -217 // -218 /// If the CONNECTION_CLOSE frame needs to be sent -219 connection_close_pending: bool, -220 -221 // -222 // ACK frequency -223 // -224 ack_frequency: AckFrequencyState, -225 -226 // -227 // Congestion Control -228 // -229 /// Whether the most recently received packet had an ECN codepoint set -230 receiving_ecn: bool, -231 /// Number of packets authenticated -232 total_authed_packets: u64, -233 -234 // -235 // ObservedAddr -236 // -237 /// Sequence number for the next observed address frame sent to the peer. -238 next_observed_addr_seq_no: VarInt, -239 -240 streams: StreamsState, -241 /// Active and surplus CIDs issued by the remote, for future use on new paths. -242 /// -243 /// These are given out before multiple paths exist, also for paths that will never -244 /// exist. So if multipath is supported the number of paths here will be higher than -245 /// the actual number of paths in use. -246 remote_cids: FxHashMap<PathId, CidQueue>, -247 /// Attributes of CIDs generated by local endpoint -248 /// -249 /// Any path that is allowed to be opened is present in this map, as well as the already -250 /// opened paths. However since CIDs are issued async by the endpoint driver via -251 /// connection events it can not be used to know if CIDs have been issued for a path or -252 /// not. See [`Connection::max_path_id_with_cids`] for this. -253 local_cid_state: FxHashMap<PathId, CidState>, -254 /// State of the unreliable datagram extension -255 datagrams: DatagramState, -256 /// Path level statistics. -257 path_stats: PathStatsMap, -258 /// Accumulated stats of all discarded paths. -259 /// -260 /// The connection-level stats returned by [`Self::stats`] are the sum of the stats of -261 /// all the paths. However once a path is discarded it gets added to this field instead -262 /// so we do not have to keep an ever growing number of paths stats in memory. -263 partial_stats: ConnectionStats, -264 /// QUIC version used for the connection. -265 version: u32, -266 -267 // -268 // Multipath -269 // -270 /// Maximum number of concurrent paths -271 /// -272 /// Initially set from the [`TransportConfig::max_concurrent_multipath_paths`]. Even -273 /// when multipath is disabled this will be set to 1, it is not used in that case -274 /// though. -275 max_concurrent_paths: NonZeroU32, -276 /// Local maximum [`PathId`] to be used -277 /// -278 /// This is initially set to [`TransportConfig::get_initial_max_path_id`] when multipath -279 /// is negotiated, or to [`PathId::ZERO`] otherwise. This is essentially the value of -280 /// the highest MAX_PATH_ID frame sent. -281 /// -282 /// Any path with an ID equal or below this [`PathId`] is either: -283 /// -284 /// - Abandoned, if it is also in [`Connection::abandoned_paths`]. -285 /// - Open, in this case it is present in [`Connection::paths`] -286 /// - Not yet opened, if it is in neither of these two places. -287 /// -288 /// Note that for not-yet-open there may or may not be any CIDs issued. See -289 /// [`Connection::max_path_id_with_cids`]. -290 local_max_path_id: PathId, -291 /// Remote's maximum [`PathId`] to be used -292 /// -293 /// This is initially set to the peer's [`TransportParameters::initial_max_path_id`] when -294 /// multipath is negotiated, or to [`PathId::ZERO`] otherwise. A peer may increase this limit -295 /// by sending [`Frame::MaxPathId`] frames. -296 remote_max_path_id: PathId, -297 /// The greatest [`PathId`] we have issued CIDs for -298 /// -299 /// CIDs are only issued for `min(local_max_path_id, remote_max_path_id)`. It is not -300 /// possible to use [`Connection::local_cid_state`] to know if CIDs have been issued -301 /// since they are issued asynchronously by the endpoint driver. -302 max_path_id_with_cids: PathId, -303 /// The paths already abandoned -304 /// -305 /// They may still have some state left in [`Connection::paths`] or -306 /// [`Connection::local_cid_state`] since some of this has to be kept around for some -307 /// time after a path is abandoned. -308 // TODO(flub): Make this a more efficient data structure. Like ranges of abandoned -309 // paths. Or a set together with a minimum. Or something. -310 abandoned_paths: FxHashSet<PathId>, -311 -312 /// State for n0's (<https://n0.computer>) nat traversal protocol. -313 n0_nat_traversal: n0_nat_traversal::State, -314 qlog: QlogSink, -315} -316 -317impl Connection { -318 pub(crate) fn new( -319 endpoint_config: Arc<EndpointConfig>, -320 config: Arc<TransportConfig>, -321 init_cid: ConnectionId, -322 local_cid: ConnectionId, -323 remote_cid: ConnectionId, -324 network_path: FourTuple, -325 crypto: Box<dyn crypto::Session>, -326 cid_gen: &dyn ConnectionIdGenerator, -327 now: Instant, -328 version: u32, -329 allow_mtud: bool, -330 rng_seed: [u8; 32], -331 side_args: SideArgs, -332 qlog: QlogSink, -333 ) -> Self { -334 let pref_addr_cid = side_args.pref_addr_cid(); -335 let path_validated = side_args.path_validated(); -336 let connection_side = ConnectionSide::from(side_args); -337 let side = connection_side.side(); -338 let mut rng = StdRng::from_seed(rng_seed); -339 let initial_space = PacketSpace::new(now, SpaceId::Initial, &mut rng); -340 let handshake_space = PacketSpace::new(now, SpaceId::Handshake, &mut rng); -341 #[cfg(test)] -342 let data_space = match config.deterministic_packet_numbers { -343 true => PacketSpace::new_deterministic(now, SpaceId::Data), -344 false => PacketSpace::new(now, SpaceId::Data, &mut rng), -345 }; -346 #[cfg(not(test))] -347 let data_space = PacketSpace::new(now, SpaceId::Data, &mut rng); -348 let state = State::handshake(state::Handshake { -349 remote_cid_set: side.is_server(), -350 expected_token: Bytes::new(), -351 client_hello: None, -352 allow_server_migration: side.is_client(), -353 }); -354 let local_cid_state = FxHashMap::from_iter([( -355 PathId::ZERO, -356 CidState::new( -357 cid_gen.cid_len(), -358 cid_gen.cid_lifetime(), -359 now, -360 if pref_addr_cid.is_some() { 2 } else { 1 }, -361 ), -362 )]); -363 -364 let mut path = PathData::new(network_path, allow_mtud, None, 0, now, &config); -365 path.open_status = paths::OpenStatus::Informed; -366 let mut this = Self { -367 endpoint_config, -368 crypto_state: CryptoState::new(crypto, init_cid, side, &mut rng), -369 handshake_cid: local_cid, -370 remote_handshake_cid: remote_cid, -371 local_cid_state, -372 paths: BTreeMap::from_iter([( -373 PathId::ZERO, -374 PathState { -375 data: path, -376 prev: None, -377 }, -378 )]), -379 path_generation_counter: 0, -380 allow_mtud, -381 state, -382 side: connection_side, -383 peer_params: TransportParameters::default(), -384 original_remote_cid: remote_cid, -385 initial_dst_cid: init_cid, -386 retry_src_cid: None, -387 events: VecDeque::new(), -388 endpoint_events: VecDeque::new(), -389 spin_enabled: config.allow_spin && rng.random_ratio(7, 8), -390 spin: false, -391 spaces: [initial_space, handshake_space, data_space], -392 highest_space: SpaceKind::Initial, -393 idle_timeout: match config.max_idle_timeout { -394 None | Some(VarInt(0)) => None, -395 Some(dur) => Some(Duration::from_millis(dur.0)), -396 }, -397 timers: TimerTable::default(), -398 authentication_failures: 0, -399 connection_close_pending: false, -400 -401 ack_frequency: AckFrequencyState::new(get_max_ack_delay( -402 &TransportParameters::default(), -403 )), -404 -405 receiving_ecn: false, -406 total_authed_packets: 0, +28 paths::PathRetransmits, +29 qlog::{QlogRecvPacket, QlogSink}, +30 spaces::LostPacket, +31 stats::PathStatsMap, +32 timer::{ConnTimer, PathTimer}, +33 }, +34 crypto::{self, Keys}, +35 frame::{ +36 self, Close, DataBlocked, Datagram, FrameStruct, NewToken, ObservedAddr, StreamDataBlocked, +37 StreamsBlocked, +38 }, +39 n0_nat_traversal, +40 packet::{ +41 FixedLengthConnectionIdParser, Header, InitialHeader, InitialPacket, LongType, Packet, +42 PacketNumber, PartialDecode, SpaceId, +43 }, +44 range_set::ArrayRangeSet, +45 shared::{ +46 ConnectionEvent, ConnectionEventInner, ConnectionId, DatagramConnectionEvent, EcnCodepoint, +47 EndpointEvent, EndpointEventInner, +48 }, +49 token::{ResetToken, Token, TokenPayload}, +50 transport_parameters::TransportParameters, +51}; +52 +53mod ack_frequency; +54use ack_frequency::AckFrequencyState; +55 +56mod assembler; +57pub use assembler::Chunk; +58 +59mod cid_state; +60use cid_state::CidState; +61 +62mod datagrams; +63use datagrams::DatagramState; +64pub use datagrams::{Datagrams, SendDatagramError}; +65 +66mod mtud; +67mod pacing; +68 +69mod packet_builder; +70use packet_builder::{PacketBuilder, PadDatagram}; +71 +72mod packet_crypto; +73use packet_crypto::CryptoState; +74pub(crate) use packet_crypto::EncryptionLevel; +75 +76mod paths; +77pub use paths::{ +78 ClosedPath, PathAbandonReason, PathEvent, PathId, PathStatus, RttEstimator, SetPathStatusError, +79}; +80use paths::{PathData, PathState}; +81 +82pub(crate) mod qlog; +83pub(crate) mod send_buffer; +84 +85pub(crate) mod spaces; +86#[cfg(fuzzing)] +87pub use spaces::Retransmits; +88#[cfg(not(fuzzing))] +89use spaces::Retransmits; +90pub(crate) use spaces::SpaceKind; +91use spaces::{OpenStatus, PacketSpace, SendableFrames, SentPacket, ThinRetransmits}; +92 +93mod stats; +94pub use stats::{ConnectionStats, FrameStats, PathStats, UdpStats}; +95 +96mod streams; +97#[cfg(fuzzing)] +98pub use streams::StreamsState; +99#[cfg(not(fuzzing))] +100use streams::StreamsState; +101pub use streams::{ +102 Chunks, ClosedStream, FinishError, ReadError, ReadableError, RecvStream, SendStream, +103 ShouldTransmit, StreamEvent, Streams, WriteError, +104}; +105 +106mod timer; +107use timer::{Timer, TimerTable}; +108 +109mod transmit_buf; +110use transmit_buf::TransmitBuf; +111 +112mod state; +113 +114#[cfg(not(fuzzing))] +115use state::State; +116#[cfg(fuzzing)] +117pub use state::State; +118use state::StateType; +119 +120/// Protocol state and logic for a single QUIC connection +121/// +122/// Objects of this type receive [`ConnectionEvent`]s and emit [`EndpointEvent`]s and application +123/// [`Event`]s to make progress. To handle timeouts, a `Connection` returns timer updates and +124/// expects timeouts through various methods. A number of simple getter methods are exposed +125/// to allow callers to inspect some of the connection state. +126/// +127/// `Connection` has roughly 4 types of methods: +128/// +129/// - A. Simple getters, taking `&self` +130/// - B. Handlers for incoming events from the network or system, named `handle_*`. +131/// - C. State machine mutators, for incoming commands from the application. For convenience we +132/// refer to this as "performing I/O" below, however as per the design of this library none of the +133/// functions actually perform system-level I/O. For example, [`read`](RecvStream::read) and +134/// [`write`](SendStream::write), but also things like [`reset`](SendStream::reset). +135/// - D. Polling functions for outgoing events or actions for the caller to +136/// take, named `poll_*`. +137/// +138/// The simplest way to use this API correctly is to call (B) and (C) whenever +139/// appropriate, then after each of those calls, as soon as feasible call all +140/// polling methods (D) and deal with their outputs appropriately, e.g. by +141/// passing it to the application or by making a system-level I/O call. You +142/// should call the polling functions in this order: +143/// +144/// 1. [`poll_transmit`](Self::poll_transmit) +145/// 2. [`poll_timeout`](Self::poll_timeout) +146/// 3. [`poll_endpoint_events`](Self::poll_endpoint_events) +147/// 4. [`poll`](Self::poll) +148/// +149/// Currently the only actual dependency is from (2) to (1), however additional +150/// dependencies may be added in future, so the above order is recommended. +151/// +152/// (A) may be called whenever desired. +153/// +154/// Care should be made to ensure that the input events represent monotonically +155/// increasing time. Specifically, calling [`handle_timeout`](Self::handle_timeout) +156/// with events of the same [`Instant`] may be interleaved in any order with a +157/// call to [`handle_event`](Self::handle_event) at that same instant; however +158/// events or timeouts with different instants must not be interleaved. +159pub struct Connection { +160 endpoint_config: Arc<EndpointConfig>, +161 config: Arc<TransportConfig>, +162 rng: StdRng, +163 /// Consolidated cryptographic state +164 crypto_state: CryptoState, +165 /// The CID we initially chose, for use during the handshake +166 handshake_cid: ConnectionId, +167 /// The CID the peer initially chose, for use during the handshake +168 remote_handshake_cid: ConnectionId, +169 /// The [`PathData`] for each path +170 /// +171 /// This needs to be ordered because [`Connection::poll_transmit`] needs to +172 /// deterministically select the next PathId to send on. +173 // TODO(flub): well does it really? But deterministic is nice for now. +174 paths: BTreeMap<PathId, PathState>, +175 /// Counter to uniquely identify every [`PathData`] created in this connection. +176 /// +177 /// Each [`PathData`] gets a [`PathData::generation`] that is unique among all +178 /// [`PathData`]s created in the lifetime of this connection. This helps identify the +179 /// correct path when RFC9000-style migrations happen, even when they are +180 /// aborted. +181 /// +182 /// Multipath does not change this, each path can also undergo RFC9000-style +183 /// migrations. So a single multipath path ID could see several [`PathData`]s each with +184 /// their unique [`PathData::generation]. +185 path_generation_counter: u64, +186 /// Whether MTU detection is supported in this environment +187 allow_mtud: bool, +188 state: State, +189 side: ConnectionSide, +190 /// Transport parameters set by the peer +191 peer_params: TransportParameters, +192 /// Source ConnectionId of the first packet received from the peer +193 original_remote_cid: ConnectionId, +194 /// Destination ConnectionId sent by the client on the first Initial +195 initial_dst_cid: ConnectionId, +196 /// The value that the server included in the Source Connection ID field of a Retry packet, if +197 /// one was received +198 retry_src_cid: Option<ConnectionId>, +199 /// Events returned by [`Connection::poll`] +200 events: VecDeque<Event>, +201 endpoint_events: VecDeque<EndpointEventInner>, +202 /// Whether the spin bit is in use for this connection +203 spin_enabled: bool, +204 /// Outgoing spin bit state +205 spin: bool, +206 /// Packet number spaces: initial, handshake, 1-RTT +207 spaces: [PacketSpace; 3], +208 /// Highest usable packet space. +209 highest_space: SpaceKind, +210 /// Negotiated idle timeout +211 idle_timeout: Option<Duration>, +212 timers: TimerTable, +213 /// Number of packets received which could not be authenticated +214 authentication_failures: u64, +215 +216 // +217 // Queued non-retransmittable 1-RTT data +218 // +219 /// If the CONNECTION_CLOSE frame needs to be sent +220 connection_close_pending: bool, +221 +222 // +223 // ACK frequency +224 // +225 ack_frequency: AckFrequencyState, +226 +227 // +228 // Congestion Control +229 // +230 /// Whether the most recently received packet had an ECN codepoint set +231 receiving_ecn: bool, +232 /// Number of packets authenticated +233 total_authed_packets: u64, +234 +235 // +236 // ObservedAddr +237 // +238 /// Sequence number for the next observed address frame sent to the peer. +239 next_observed_addr_seq_no: VarInt, +240 +241 streams: StreamsState, +242 /// Active and surplus CIDs issued by the remote, for future use on new paths. +243 /// +244 /// These are given out before multiple paths exist, also for paths that will never +245 /// exist. So if multipath is supported the number of paths here will be higher than +246 /// the actual number of paths in use. +247 remote_cids: FxHashMap<PathId, CidQueue>, +248 /// Attributes of CIDs generated by local endpoint +249 /// +250 /// Any path that is allowed to be opened is present in this map, as well as the already +251 /// opened paths. However since CIDs are issued async by the endpoint driver via +252 /// connection events it can not be used to know if CIDs have been issued for a path or +253 /// not. See [`Connection::max_path_id_with_cids`] for this. +254 local_cid_state: FxHashMap<PathId, CidState>, +255 /// State of the unreliable datagram extension +256 datagrams: DatagramState, +257 /// Path level statistics. +258 path_stats: PathStatsMap, +259 /// Accumulated stats of all discarded paths. +260 /// +261 /// The connection-level stats returned by [`Self::stats`] are the sum of the stats of +262 /// all the paths. However once a path is discarded it gets added to this field instead +263 /// so we do not have to keep an ever growing number of paths stats in memory. +264 partial_stats: ConnectionStats, +265 /// QUIC version used for the connection. +266 version: u32, +267 +268 // +269 // Multipath +270 // +271 /// Maximum number of concurrent paths +272 /// +273 /// Initially set from the [`TransportConfig::max_concurrent_multipath_paths`]. Even +274 /// when multipath is disabled this will be set to 1, it is not used in that case +275 /// though. +276 max_concurrent_paths: NonZeroU32, +277 /// Local maximum [`PathId`] to be used +278 /// +279 /// This is initially set to [`TransportConfig::get_initial_max_path_id`] when multipath +280 /// is negotiated, or to [`PathId::ZERO`] otherwise. This is essentially the value of +281 /// the highest MAX_PATH_ID frame sent. +282 /// +283 /// Any path with an ID equal or below this [`PathId`] is either: +284 /// +285 /// - Abandoned, if it is also in [`Connection::abandoned_paths`]. +286 /// - Open, in this case it is present in [`Connection::paths`] +287 /// - Not yet opened, if it is in neither of these two places. +288 /// +289 /// Note that for not-yet-open there may or may not be any CIDs issued. See +290 /// [`Connection::max_path_id_with_cids`]. +291 local_max_path_id: PathId, +292 /// Remote's maximum [`PathId`] to be used +293 /// +294 /// This is initially set to the peer's [`TransportParameters::initial_max_path_id`] when +295 /// multipath is negotiated, or to [`PathId::ZERO`] otherwise. A peer may increase this limit +296 /// by sending [`Frame::MaxPathId`] frames. +297 remote_max_path_id: PathId, +298 /// The greatest [`PathId`] we have issued CIDs for +299 /// +300 /// CIDs are only issued for `min(local_max_path_id, remote_max_path_id)`. It is not +301 /// possible to use [`Connection::local_cid_state`] to know if CIDs have been issued +302 /// since they are issued asynchronously by the endpoint driver. +303 max_path_id_with_cids: PathId, +304 /// The paths already abandoned +305 /// +306 /// They may still have some state left in [`Connection::paths`] or +307 /// [`Connection::local_cid_state`] since some of this has to be kept around for some +308 /// time after a path is abandoned. +309 abandoned_paths: AbandonedPaths, +310 +311 /// State for n0's (<https://n0.computer>) nat traversal protocol. +312 n0_nat_traversal: n0_nat_traversal::State, +313 qlog: QlogSink, +314} +315 +316impl Connection { +317 pub(crate) fn new( +318 endpoint_config: Arc<EndpointConfig>, +319 config: Arc<TransportConfig>, +320 init_cid: ConnectionId, +321 local_cid: ConnectionId, +322 remote_cid: ConnectionId, +323 network_path: FourTuple, +324 crypto: Box<dyn crypto::Session>, +325 cid_gen: &dyn ConnectionIdGenerator, +326 now: Instant, +327 version: u32, +328 allow_mtud: bool, +329 rng_seed: [u8; 32], +330 side_args: SideArgs, +331 qlog: QlogSink, +332 ) -> Self { +333 let pref_addr_cid = side_args.pref_addr_cid(); +334 let path_validated = side_args.path_validated(); +335 let connection_side = ConnectionSide::from(side_args); +336 let side = connection_side.side(); +337 let mut rng = StdRng::from_seed(rng_seed); +338 let mut initial_space = PacketSpace::new(now, SpaceId::Initial, &mut rng); +339 let mut handshake_space = PacketSpace::new(now, SpaceId::Handshake, &mut rng); +340 #[cfg(test)] +341 let mut data_space = match config.deterministic_packet_numbers { +342 true => PacketSpace::new_deterministic(now, SpaceId::Data), +343 false => PacketSpace::new(now, SpaceId::Data, &mut rng), +344 }; +345 #[cfg(not(test))] +346 let mut data_space = PacketSpace::new(now, SpaceId::Data, &mut rng); +347 +348 // The spaces for PathId::ZERO do not need the PathEvent::Established event. +349 initial_space.for_path(PathId::ZERO).open_status = OpenStatus::Informed; +350 handshake_space.for_path(PathId::ZERO).open_status = OpenStatus::Informed; +351 data_space.for_path(PathId::ZERO).open_status = OpenStatus::Informed; +352 +353 let state = State::handshake(state::Handshake { +354 remote_cid_set: side.is_server(), +355 expected_token: Bytes::new(), +356 client_hello: None, +357 allow_server_migration: side.is_client() && config.server_handshake_migration, +358 }); +359 let local_cid_state = FxHashMap::from_iter([( +360 PathId::ZERO, +361 CidState::new( +362 cid_gen.cid_len(), +363 cid_gen.cid_lifetime(), +364 now, +365 if pref_addr_cid.is_some() { 2 } else { 1 }, +366 ), +367 )]); +368 +369 let mut this = Self { +370 endpoint_config, +371 crypto_state: CryptoState::new(crypto, init_cid, side, &mut rng), +372 handshake_cid: local_cid, +373 remote_handshake_cid: remote_cid, +374 local_cid_state, +375 paths: BTreeMap::from_iter([( +376 PathId::ZERO, +377 PathState { +378 data: PathData::new(network_path, allow_mtud, None, 0, now, &config), +379 prev: None, +380 }, +381 )]), +382 path_generation_counter: 0, +383 allow_mtud, +384 state, +385 side: connection_side, +386 peer_params: TransportParameters::default(), +387 original_remote_cid: remote_cid, +388 initial_dst_cid: init_cid, +389 retry_src_cid: None, +390 events: VecDeque::new(), +391 endpoint_events: VecDeque::new(), +392 spin_enabled: config.allow_spin && rng.random_ratio(7, 8), +393 spin: false, +394 spaces: [initial_space, handshake_space, data_space], +395 highest_space: SpaceKind::Initial, +396 idle_timeout: match config.max_idle_timeout { +397 None | Some(VarInt(0)) => None, +398 Some(dur) => Some(Duration::from_millis(dur.0)), +399 }, +400 timers: TimerTable::default(), +401 authentication_failures: 0, +402 connection_close_pending: false, +403 +404 ack_frequency: AckFrequencyState::new(get_max_ack_delay( +405 &TransportParameters::default(), +406 )), 407 -408 next_observed_addr_seq_no: 0u32.into(), -409 -410 streams: StreamsState::new( -411 side, -412 config.max_concurrent_uni_streams, -413 config.max_concurrent_bidi_streams, -414 config.send_window, -415 config.receive_window, -416 config.stream_receive_window, -417 ), -418 datagrams: DatagramState::default(), -419 config, -420 remote_cids: FxHashMap::from_iter([(PathId::ZERO, CidQueue::new(remote_cid))]), -421 rng, -422 path_stats: Default::default(), -423 partial_stats: ConnectionStats::default(), -424 version, -425 -426 // peer params are not yet known, so multipath is not enabled -427 max_concurrent_paths: NonZeroU32::MIN, -428 local_max_path_id: PathId::ZERO, -429 remote_max_path_id: PathId::ZERO, -430 max_path_id_with_cids: PathId::ZERO, -431 abandoned_paths: Default::default(), -432 -433 n0_nat_traversal: Default::default(), -434 qlog, -435 }; -436 if path_validated { -437 this.on_path_validated(PathId::ZERO); -438 } -439 if side.is_client() { -440 // Kick off the connection -441 this.write_crypto(); -442 this.init_0rtt(now); -443 } -444 this.qlog -445 .emit_tuple_assigned(PathId::ZERO, network_path, now); -446 this -447 } -448 -449 /// Returns the next time at which `handle_timeout` should be called -450 /// -451 /// The value returned may change after: -452 /// - the application performed some I/O on the connection -453 /// - a call was made to `handle_event` -454 /// - a call to `poll_transmit` returned `Some` -455 /// - a call was made to `handle_timeout` -456 #[must_use] -457 pub fn poll_timeout(&mut self) -> Option<Instant> { -458 self.timers.peek() -459 } -460 -461 /// Returns application-facing events -462 /// -463 /// Connections should be polled for events after: -464 /// - a call was made to `handle_event` -465 /// - a call was made to `handle_timeout` -466 #[must_use] -467 pub fn poll(&mut self) -> Option<Event> { -468 if let Some(x) = self.events.pop_front() { -469 return Some(x); -470 } -471 -472 if let Some(event) = self.streams.poll() { -473 return Some(Event::Stream(event)); -474 } -475 -476 if let Some(reason) = self.state.take_error() { -477 return Some(Event::ConnectionLost { reason }); -478 } -479 -480 None -481 } +408 receiving_ecn: false, +409 total_authed_packets: 0, +410 +411 next_observed_addr_seq_no: 0u32.into(), +412 +413 streams: StreamsState::new( +414 side, +415 config.max_concurrent_uni_streams, +416 config.max_concurrent_bidi_streams, +417 config.send_window, +418 config.receive_window, +419 config.stream_receive_window, +420 ), +421 datagrams: DatagramState::default(), +422 config, +423 remote_cids: FxHashMap::from_iter([(PathId::ZERO, CidQueue::new(remote_cid))]), +424 rng, +425 path_stats: Default::default(), +426 partial_stats: ConnectionStats::default(), +427 version, +428 +429 // peer params are not yet known, so multipath is not enabled +430 max_concurrent_paths: NonZeroU32::MIN, +431 local_max_path_id: PathId::ZERO, +432 remote_max_path_id: PathId::ZERO, +433 max_path_id_with_cids: PathId::ZERO, +434 abandoned_paths: Default::default(), +435 +436 n0_nat_traversal: Default::default(), +437 qlog, +438 }; +439 if path_validated { +440 this.on_path_validated(PathId::ZERO); +441 } +442 if side.is_client() { +443 // Kick off the connection +444 this.write_crypto(); +445 this.init_0rtt(now); +446 } +447 this.qlog +448 .emit_tuple_assigned(PathId::ZERO, network_path, now); +449 this +450 } +451 +452 /// Returns the next time at which `handle_timeout` should be called +453 /// +454 /// The value returned may change after: +455 /// - the application performed some I/O on the connection +456 /// - a call was made to `handle_event` +457 /// - a call to `poll_transmit` returned `Some` +458 /// - a call was made to `handle_timeout` +459 #[must_use] +460 pub fn poll_timeout(&mut self) -> Option<Instant> { +461 self.timers.peek() +462 } +463 +464 /// Returns application-facing events +465 /// +466 /// Connections should be polled for events after: +467 /// - a call was made to `handle_event` +468 /// - a call was made to `handle_timeout` +469 #[must_use] +470 pub fn poll(&mut self) -> Option<Event> { +471 if let Some(x) = self.events.pop_front() { +472 return Some(x); +473 } +474 +475 if let Some(event) = self.streams.poll() { +476 return Some(Event::Stream(event)); +477 } +478 +479 if let Some(reason) = self.state.take_error() { +480 return Some(Event::ConnectionLost { reason }); +481 } 482 -483 /// Return endpoint-facing events -484 #[must_use] -485 pub fn poll_endpoint_events(&mut self) -> Option<EndpointEvent> { -486 self.endpoint_events.pop_front().map(EndpointEvent) -487 } -488 -489 /// Provide control over streams -490 #[must_use] -491 pub fn streams(&mut self) -> Streams<'_> { -492 Streams { -493 state: &mut self.streams, -494 conn_state: &self.state, -495 } -496 } -497 -498 /// Provide control over streams -499 #[must_use] -500 pub fn recv_stream(&mut self, id: StreamId) -> RecvStream<'_> { -501 assert!(id.dir() == Dir::Bi || id.initiator() != self.side.side()); -502 RecvStream { -503 id, -504 state: &mut self.streams, -505 pending: &mut self.spaces[SpaceId::Data].pending, -506 } -507 } -508 -509 /// Provide control over streams -510 #[must_use] -511 pub fn send_stream(&mut self, id: StreamId) -> SendStream<'_> { -512 assert!(id.dir() == Dir::Bi || id.initiator() == self.side.side()); -513 SendStream { -514 id, -515 state: &mut self.streams, -516 pending: &mut self.spaces[SpaceId::Data].pending, -517 conn_state: &self.state, -518 } -519 } -520 -521 /// Opens a new path only if no path on the same network path currently exists. -522 /// -523 /// This comparison will use [`FourTuple::is_probably_same_path`] on the given `network_path` -524 /// and pass it existing path's network paths. +483 None +484 } +485 +486 /// Return endpoint-facing events +487 #[must_use] +488 pub fn poll_endpoint_events(&mut self) -> Option<EndpointEvent> { +489 self.endpoint_events.pop_front().map(EndpointEvent) +490 } +491 +492 /// Provide control over streams +493 #[must_use] +494 pub fn streams(&mut self) -> Streams<'_> { +495 Streams { +496 state: &mut self.streams, +497 conn_state: &self.state, +498 } +499 } +500 +501 /// Provide control over streams +502 #[must_use] +503 pub fn recv_stream(&mut self, id: StreamId) -> RecvStream<'_> { +504 assert!(id.dir() == Dir::Bi || id.initiator() != self.side.side()); +505 RecvStream { +506 id, +507 state: &mut self.streams, +508 pending: &mut self.spaces[SpaceId::Data].pending, +509 } +510 } +511 +512 /// Provide control over streams +513 #[must_use] +514 pub fn send_stream(&mut self, id: StreamId) -> SendStream<'_> { +515 assert!(id.dir() == Dir::Bi || id.initiator() == self.side.side()); +516 SendStream { +517 id, +518 state: &mut self.streams, +519 pending: &mut self.spaces[SpaceId::Data].pending, +520 conn_state: &self.state, +521 } +522 } +523 +524 /// Opens a new path only if no path on the same network path currently exists. 525 /// -526 /// This means that you can pass `local_ip: None` to make the comparison only compare -527 /// remote addresses. +526 /// Returns `(path_id, true)` if the path already existed, or `(path_id, false)` +527 /// if was opened. 528 /// -529 /// This avoids having to guess which local interface will be used to communicate with the -530 /// remote, should it not be known yet. We assume that if we already have a path to the remote, -531 /// the OS is likely to use the same interface to talk to said remote. -532 /// -533 /// See also [`open_path`]. Returns `(path_id, true)` if the path already existed. `(path_id, -534 /// false)` if was opened. -535 /// -536 /// [`open_path`]: Connection::open_path -537 pub fn open_path_ensure( -538 &mut self, -539 network_path: FourTuple, -540 initial_status: PathStatus, -541 now: Instant, -542 ) -> Result<(PathId, bool), PathError> { -543 let existing_open_path = self.paths.iter().find(|(id, path)| { -544 network_path.is_probably_same_path(&path.data.network_path) -545 && !self.abandoned_paths.contains(*id) -546 }); -547 match existing_open_path { -548 Some((path_id, _state)) => Ok((*path_id, true)), -549 None => Ok((self.open_path(network_path, initial_status, now)?, false)), -550 } -551 } -552 -553 /// Opens a new path. -554 /// -555 /// Further errors might occur and they will be emitted in [`PathEvent::Abandoned`] -556 /// events with this path id. Once the path is opened and can carry application data it -557 /// will be reported using a [`PathEvent::Established`] event. -558 pub fn open_path( -559 &mut self, -560 network_path: FourTuple, -561 initial_status: PathStatus, -562 now: Instant, -563 ) -> Result<PathId, PathError> { -564 if !self.is_multipath_negotiated() { -565 return Err(PathError::MultipathNotNegotiated); -566 } -567 if self.side().is_server() { -568 return Err(PathError::ServerSideNotAllowed); -569 } -570 -571 let max_abandoned = self.abandoned_paths.iter().max().copied(); -572 let max_used = self.paths.keys().last().copied(); -573 let path_id = max_abandoned -574 .max(max_used) -575 .unwrap_or(PathId::ZERO) -576 .saturating_add(1u8); -577 -578 if Some(path_id) > self.max_path_id() { -579 return Err(PathError::MaxPathIdReached); -580 } -581 if path_id > self.remote_max_path_id { -582 self.spaces[SpaceId::Data].pending.paths_blocked = true; +529 /// If `network_path` has no local IP set, then this will open a new path +530 /// if no path exists for this remote address, independent of the existing +531 /// path's local IP. If a local IP is set, it will match against the full +532 /// four-tuple of existing paths. Not setting the local IP avoids having to +533 /// guess which local interface will be used to communicate with the remote, +534 /// should it not be known yet. We assume that if we already have a path to +535 /// the remote, the OS is likely to use the same interface to talk to said remote. +536 /// +537 /// See also [`open_path`]. +538 /// +539 /// [`open_path`]: Connection::open_path +540 pub fn open_path_ensure( +541 &mut self, +542 network_path: FourTuple, +543 initial_status: PathStatus, +544 now: Instant, +545 ) -> Result<(PathId, bool), PathError> { +546 let existing_open_path = self.paths.iter().find(|(id, path)| { +547 network_path.is_probably_same_path(&path.data.network_path) +548 && !self.abandoned_paths.contains(id) +549 }); +550 match existing_open_path { +551 Some((path_id, _state)) => Ok((*path_id, true)), +552 None => Ok((self.open_path(network_path, initial_status, now)?, false)), +553 } +554 } +555 +556 /// Opens a new path. +557 /// +558 /// Further errors might occur and they will be emitted in [`PathEvent::Abandoned`] +559 /// events with this path id. Once the path is opened and can carry application data it +560 /// will be reported using a [`PathEvent::Established`] event. +561 pub fn open_path( +562 &mut self, +563 network_path: FourTuple, +564 initial_status: PathStatus, +565 now: Instant, +566 ) -> Result<PathId, PathError> { +567 let Some(max_path_id) = self.max_path_id() else { +568 return Err(PathError::MultipathNotNegotiated); +569 }; +570 if self.side().is_server() { +571 return Err(PathError::ServerSideNotAllowed); +572 } +573 +574 let max_abandoned = self.abandoned_paths.max(); +575 let max_used = self.paths.keys().last().copied(); +576 let path_id = max_abandoned +577 .max(max_used) +578 .unwrap_or(PathId::ZERO) +579 .saturating_add(1u8); +580 +581 if path_id > max_path_id { +582 self.spaces[SpaceId::Data].pending.paths_blocked = Some(self.remote_max_path_id); 583 return Err(PathError::MaxPathIdReached); 584 } -585 if self -586 .remote_cids -587 .get(&path_id) -588 .map(CidQueue::active) -589 .is_none() -590 { -591 self.spaces[SpaceId::Data] -592 .pending -593 .path_cids_blocked -594 .insert(path_id); -595 return Err(PathError::RemoteCidsExhausted); -596 } -597 -598 let path = self.ensure_path(path_id, network_path, now, None); -599 path.status.local_update(initial_status); -600 -601 Ok(path_id) -602 } -603 -604 /// Closes a path and sends a PATH_ABANDON frame with the passed error code. -605 /// -606 /// Returns [`ClosePathError::LastOpenPath`] if this is the last open path. -607 /// It does allow closing paths which have not yet been opened, as e.g. is the case -608 /// when receiving a PATH_ABANDON from the peer for a path that was never opened locally. -609 pub fn close_path( -610 &mut self, -611 now: Instant, -612 path_id: PathId, -613 error_code: VarInt, -614 ) -> Result<(), ClosePathError> { -615 self.close_path_inner( -616 now, -617 path_id, -618 PathAbandonReason::ApplicationClosed { error_code }, -619 ) -620 } -621 -622 /// Closes a path and sends a PATH_ABANDON frame. -623 /// -624 /// Other than [`Self::close_path`] this allows to specify the reason for the path being closed. -625 /// Internally, this should be used over [`Self::close_path`]. -626 pub(crate) fn close_path_inner( -627 &mut self, -628 now: Instant, -629 path_id: PathId, -630 reason: PathAbandonReason, -631 ) -> Result<(), ClosePathError> { -632 if self.state.is_drained() { -633 return Ok(()); -634 } -635 -636 if !self.is_multipath_negotiated() { -637 return Err(ClosePathError::MultipathNotNegotiated); -638 } -639 if self.abandoned_paths.contains(&path_id) -640 || Some(path_id) > self.max_path_id() -641 || !self.paths.contains_key(&path_id) -642 { -643 return Err(ClosePathError::ClosedPath); -644 } +585 if !self.remote_cids.contains_key(&path_id) { +586 self.spaces[SpaceId::Data] +587 .pending +588 .path_cids_blocked +589 .insert(path_id, VarInt(0)); +590 return Err(PathError::RemoteCidsExhausted); +591 } +592 +593 let path = self.ensure_path(path_id, network_path, now, None); +594 path.status.local_update(initial_status); +595 +596 Ok(path_id) +597 } +598 +599 /// Closes a path and sends a PATH_ABANDON frame with the passed error code. +600 /// +601 /// Returns [`ClosePathError::LastOpenPath`] if this is the last open path. +602 /// It does allow closing paths which have not yet been opened, as e.g. is the case +603 /// when receiving a PATH_ABANDON from the peer for a path that was never opened locally. +604 pub fn close_path( +605 &mut self, +606 now: Instant, +607 path_id: PathId, +608 error_code: VarInt, +609 ) -> Result<(), ClosePathError> { +610 self.close_path_inner( +611 now, +612 path_id, +613 PathAbandonReason::ApplicationClosed { error_code }, +614 ) +615 } +616 +617 /// Closes a path and sends a PATH_ABANDON frame. +618 /// +619 /// Other than [`Self::close_path`] this allows to specify the reason for the path being closed. +620 /// Internally, this should be used over [`Self::close_path`]. +621 pub(crate) fn close_path_inner( +622 &mut self, +623 now: Instant, +624 path_id: PathId, +625 reason: PathAbandonReason, +626 ) -> Result<(), ClosePathError> { +627 if self.state.is_drained() { +628 return Ok(()); +629 } +630 +631 if !self.is_multipath_negotiated() { +632 return Err(ClosePathError::MultipathNotNegotiated); +633 } +634 if self.abandoned_paths.contains(&path_id) +635 || Some(path_id) > self.max_path_id() +636 || !self.paths.contains_key(&path_id) +637 { +638 return Err(ClosePathError::ClosedPath); +639 } +640 +641 let is_last_path = !self +642 .paths +643 .keys() +644 .any(|id| *id != path_id && !self.abandoned_paths.contains(id)); 645 -646 let is_last_path = !self -647 .paths -648 .keys() -649 .any(|id| *id != path_id && !self.abandoned_paths.contains(id)); -650 -651 if is_last_path && !reason.is_remote() { -652 return Err(ClosePathError::LastOpenPath); -653 } -654 -655 self.abandon_path(now, path_id, reason); -656 -657 // When the remote abandons our last path, start a grace timer to allow -658 // the application to open a replacement path. -659 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 -660 if is_last_path { -661 // The spec suggests 1 PTO, but we use 3 * PTO to account for -662 // packet loss when opening a replacement path. Uses initial RTT -663 // since the abandoned path's RTT estimate is no longer valid. -664 let rtt = RttEstimator::new(self.config.initial_rtt); -665 let pto = rtt.pto_base() + self.ack_frequency.max_ack_delay_for_pto(); -666 let grace = pto * 3; -667 self.timers.set( -668 Timer::Conn(ConnTimer::NoAvailablePath), -669 now + grace, -670 self.qlog.with_time(now), -671 ); -672 } -673 -674 Ok(()) -675 } -676 -677 /// Unconditionally abandon a path. -678 /// -679 /// Only to be called once sure this path should be abandoned, all checks -680 /// should have happened before calling this. -681 fn abandon_path(&mut self, now: Instant, path_id: PathId, reason: PathAbandonReason) { -682 trace!(%path_id, ?reason, "abandoning path"); -683 -684 let pending_space = &mut self.spaces[SpaceId::Data].pending; -685 // Send PATH_ABANDON -686 pending_space -687 .path_abandon -688 .insert(path_id, reason.error_code()); -689 -690 // Remove pending NEW CIDs for this path -691 pending_space.new_cids.retain(|cid| cid.path_id != path_id); -692 pending_space.path_cids_blocked.retain(|&id| id != path_id); -693 pending_space.path_status.retain(|&id| id != path_id); -694 -695 // Cleanup retransmits across ALL paths (CIDs for path_id may have been transmitted on other paths) -696 for space in self.spaces[SpaceId::Data].iter_paths_mut() { -697 for sent_packet in space.sent_packets.values_mut() { -698 if let Some(retransmits) = sent_packet.retransmits.get_mut() { -699 retransmits.new_cids.retain(|cid| cid.path_id != path_id); -700 retransmits.path_cids_blocked.retain(|&id| id != path_id); -701 retransmits.path_status.retain(|&id| id != path_id); -702 } -703 } -704 } -705 -706 // We can't send anything on abandoned paths, so we set -707 // tail-loss probes to zero. -708 // This likely doesn't do much, as the path won't even be tried for sending -709 // in poll_transmit after the path is abandoned. -710 self.spaces[SpaceId::Data].for_path(path_id).loss_probes = 0; +646 if is_last_path && !reason.is_remote() { +647 return Err(ClosePathError::LastOpenPath); +648 } +649 +650 self.abandon_path(now, path_id, reason); +651 +652 // When the remote abandons our last path, start a grace timer to allow +653 // the application to open a replacement path. +654 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 +655 if is_last_path { +656 // The spec suggests 1 PTO, but we use 3 * PTO to account for +657 // packet loss when opening a replacement path. Uses initial RTT +658 // since the abandoned path's RTT estimate is no longer valid. +659 let rtt = RttEstimator::new(self.config.initial_rtt); +660 let pto = rtt.pto_base() + self.ack_frequency.max_ack_delay_for_pto(); +661 let grace = pto * 3; +662 self.timers.set( +663 Timer::Conn(ConnTimer::NoAvailablePath), +664 now + grace, +665 self.qlog.with_time(now), +666 ); +667 } +668 +669 Ok(()) +670 } +671 +672 /// Unconditionally abandon a path. +673 /// +674 /// Only to be called once sure this path should be abandoned, all checks +675 /// should have happened before calling this. +676 fn abandon_path(&mut self, now: Instant, path_id: PathId, reason: PathAbandonReason) { +677 trace!(%path_id, ?reason, "abandoning path"); +678 +679 let pending_space = &mut self.spaces[SpaceId::Data].pending; +680 // Send PATH_ABANDON +681 pending_space +682 .path_abandon +683 .insert(path_id, reason.error_code()); +684 +685 // Remove pending NEW CIDs for this path +686 pending_space.new_cids.retain(|cid| cid.path_id != path_id); +687 pending_space.path_status.retain(|&id| id != path_id); +688 +689 // Cleanup retransmits across ALL paths (CIDs for path_id may have been transmitted on other paths) +690 for space in self.spaces[SpaceId::Data].iter_paths_mut() { +691 for sent_packet in space.sent_packets.values_mut() { +692 if let Some(retransmits) = sent_packet.retransmits.get_mut() { +693 retransmits.new_cids.retain(|cid| cid.path_id != path_id); +694 retransmits.path_status.retain(|&id| id != path_id); +695 } +696 } +697 } +698 +699 // We can't send anything on abandoned paths, so we set +700 // tail-loss probes to zero. +701 // This likely doesn't do much, as the path won't even be tried for sending +702 // in poll_transmit after the path is abandoned. +703 self.spaces[SpaceId::Data].for_path(path_id).loss_probes = 0; +704 +705 // Note: remote CIDs are NOT removed here. They are removed when the PATH_ABANDON +706 // frame is actually written to a packet (in populate_packet). This allows sending +707 // PATH_ABANDON on the abandoned path itself when no other path exists (#509). +708 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events, checked in `close_path_inner` +709 self.endpoint_events +710 .push_back(EndpointEventInner::RetireResetToken(path_id)); 711 -712 // Note: remote CIDs are NOT removed here. They are removed when the PATH_ABANDON -713 // frame is actually written to a packet (in populate_packet). This allows sending -714 // PATH_ABANDON on the abandoned path itself when no other path exists (#509). -715 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events, checked in `close_path_inner` -716 self.endpoint_events -717 .push_back(EndpointEventInner::RetireResetToken(path_id)); -718 -719 self.abandoned_paths.insert(path_id); -720 -721 for timer in timer::PathTimer::VALUES { -722 // match for completeness -723 let keep_timer = match timer { -724 // These timers deal with sending and receiving PATH_CHALLENGE and -725 // PATH_RESPONSE, but now that the path is abandoned, we no longer care about -726 // these frames or their timing -727 PathTimer::PathValidationFailed -728 | PathTimer::PathChallengeLost -729 | PathTimer::AbandonFromValidation => false, -730 // These timers deal with the lifetime of the path. Now that the path is abandoned, -731 // these are not relevant. -732 PathTimer::PathKeepAlive | PathTimer::PathIdle => false, -733 // The path has already been informed that outstanding acks should be sent -734 // immediately -735 PathTimer::MaxAckDelay => false, -736 // This timer should not be set, for completeness it's not kept as it's set when -737 // the PATH_ABANDON frame is sent. -738 PathTimer::PathDrained => false, -739 // Sent packets still need to be identified as lost to trigger timely -740 // retransmission. -741 PathTimer::LossDetection => true, -742 // This path should not be used for sending after the PATH_ABANDON frame is sent. -743 // However, any outstanding data that should be sent before PATH_ABANDON, should -744 // still respect pacing. -745 PathTimer::Pacing => true, -746 }; -747 -748 if !keep_timer { -749 let qlog = self.qlog.with_time(now); -750 self.timers.stop(Timer::PerPath(path_id, timer), qlog); -751 } -752 } -753 -754 // Set the loss detection timer again, as now it should only be set -755 // for time-based loss detection, not tail-loss probes, but currently it -756 // could still be set to a tail-loss probe. -757 // This will reset it to the next time-based loss time, if applicable. -758 self.set_loss_detection_timer(now, path_id); +712 self.abandoned_paths.insert(path_id); +713 +714 for timer in timer::PathTimer::VALUES { +715 // match for completeness +716 let keep_timer = match timer { +717 // These timers deal with sending and receiving PATH_CHALLENGE and +718 // PATH_RESPONSE, but now that the path is abandoned, we no longer care about +719 // these frames or their timing +720 PathTimer::PathValidationFailed +721 | PathTimer::PathChallengeLost +722 | PathTimer::AbandonFromValidation => false, +723 // These timers deal with the lifetime of the path. Now that the path is abandoned, +724 // these are not relevant. +725 PathTimer::PathKeepAlive | PathTimer::PathIdle => false, +726 // The path has already been informed that outstanding acks should be sent +727 // immediately +728 PathTimer::MaxAckDelay => false, +729 // This timer should not be set, for completeness it's not kept as it's set when +730 // the PATH_ABANDON frame is sent. +731 PathTimer::PathDrained => false, +732 // Sent packets still need to be identified as lost to trigger timely +733 // retransmission. +734 PathTimer::LossDetection => true, +735 // This path should not be used for sending after the PATH_ABANDON frame is sent. +736 // However, any outstanding data that should be sent before PATH_ABANDON, should +737 // still respect pacing. +738 PathTimer::Pacing => true, +739 }; +740 +741 if !keep_timer { +742 let qlog = self.qlog.with_time(now); +743 self.timers.stop(Timer::PerPath(path_id, timer), qlog); +744 } +745 } +746 +747 // Set the loss detection timer again, as now it should only be set +748 // for time-based loss detection, not tail-loss probes, but currently it +749 // could still be set to a tail-loss probe. +750 // This will reset it to the next time-based loss time, if applicable. +751 self.set_loss_detection_timer(now, path_id); +752 +753 // Emit event to the application. +754 self.events.push_back(Event::Path(PathEvent::Abandoned { +755 id: path_id, +756 reason, +757 })); +758 } 759 -760 // Emit event to the application. -761 self.events.push_back(Event::Path(PathEvent::Abandoned { -762 id: path_id, -763 reason, -764 })); -765 } -766 -767 /// Gets the [`PathData`] for a known [`PathId`]. -768 /// -769 /// Will panic if the path_id does not reference any known path. -770 #[track_caller] -771 fn path_data(&self, path_id: PathId) -> &PathData { -772 if let Some(data) = self.paths.get(&path_id) { -773 &data.data -774 } else { -775 panic!( -776 "unknown path: {path_id}, currently known paths: {:?}", -777 self.paths.keys().collect::<Vec<_>>() -778 ); -779 } -780 } -781 -782 /// Gets the [`PathData`] for a known [`PathId`]. -783 /// -784 /// Will panic if the path_id does not reference any known path. -785 #[track_caller] -786 fn path_data_mut(&mut self, path_id: PathId) -> &mut PathData { -787 &mut self.paths.get_mut(&path_id).expect("known path").data -788 } -789 -790 /// Gets a reference to the [`PathData`] for a [`PathId`] -791 fn path(&self, path_id: PathId) -> Option<&PathData> { -792 self.paths.get(&path_id).map(|path_state| &path_state.data) +760 /// Gets the [`PathData`] for a known [`PathId`]. +761 /// +762 /// Will panic if the path_id does not reference any known path. +763 #[track_caller] +764 fn path_data(&self, path_id: PathId) -> &PathData { +765 if let Some(data) = self.paths.get(&path_id) { +766 &data.data +767 } else { +768 panic!( +769 "unknown path: {path_id}, currently known paths: {:?}", +770 self.paths.keys().collect::<Vec<_>>() +771 ); +772 } +773 } +774 +775 /// Gets the [`PathData`] for a known [`PathId`]. +776 /// +777 /// Will panic if the path_id does not reference any known path. +778 #[track_caller] +779 fn path_data_mut(&mut self, path_id: PathId) -> &mut PathData { +780 &mut self.paths.get_mut(&path_id).expect("known path").data +781 } +782 +783 /// Gets a reference to the [`PathData`] for a [`PathId`] +784 fn path(&self, path_id: PathId) -> Option<&PathData> { +785 self.paths.get(&path_id).map(|path_state| &path_state.data) +786 } +787 +788 /// Gets a mutable reference to the [`PathData`] for a [`PathId`] +789 fn path_mut(&mut self, path_id: PathId) -> Option<&mut PathData> { +790 self.paths +791 .get_mut(&path_id) +792 .map(|path_state| &mut path_state.data) 793 } 794 -795 /// Gets a mutable reference to the [`PathData`] for a [`PathId`] -796 fn path_mut(&mut self, path_id: PathId) -> Option<&mut PathData> { -797 self.paths -798 .get_mut(&path_id) -799 .map(|path_state| &mut path_state.data) +795 /// Returns all known paths. +796 /// +797 /// There is no guarantee any of these paths are open or usable. +798 pub fn paths(&self) -> Vec<PathId> { +799 self.paths.keys().copied().collect() 800 } 801 -802 /// Returns all known paths. -803 /// -804 /// There is no guarantee any of these paths are open or usable. -805 pub fn paths(&self) -> Vec<PathId> { -806 self.paths.keys().copied().collect() +802 /// Gets the local [`PathStatus`] for a known [`PathId`] +803 pub fn path_status(&self, path_id: PathId) -> Result<PathStatus, ClosedPath> { +804 self.path(path_id) +805 .map(PathData::local_status) +806 .ok_or(ClosedPath { _private: () }) 807 } 808 -809 /// Gets the local [`PathStatus`] for a known [`PathId`] -810 pub fn path_status(&self, path_id: PathId) -> Result<PathStatus, ClosedPath> { +809 /// Returns the path's network path represented as a 4-tuple. +810 pub fn network_path(&self, path_id: PathId) -> Result<FourTuple, ClosedPath> { 811 self.path(path_id) -812 .map(PathData::local_status) +812 .map(|path| path.network_path) 813 .ok_or(ClosedPath { _private: () }) 814 } 815 -816 /// Returns the path's network path represented as a 4-tuple. -817 pub fn network_path(&self, path_id: PathId) -> Result<FourTuple, ClosedPath> { -818 self.path(path_id) -819 .map(|path| path.network_path) -820 .ok_or(ClosedPath { _private: () }) -821 } -822 -823 /// Sets the [`PathStatus`] for a known [`PathId`] -824 /// -825 /// Returns the previous path status on success. -826 pub fn set_path_status( -827 &mut self, -828 path_id: PathId, -829 status: PathStatus, -830 ) -> Result<PathStatus, SetPathStatusError> { -831 if !self.is_multipath_negotiated() { -832 return Err(SetPathStatusError::MultipathNotNegotiated); -833 } -834 let path = self -835 .path_mut(path_id) -836 .ok_or(SetPathStatusError::ClosedPath)?; -837 let prev = match path.status.local_update(status) { -838 Some(prev) => { -839 self.spaces[SpaceId::Data] -840 .pending -841 .path_status -842 .insert(path_id); -843 prev -844 } -845 None => path.local_status(), -846 }; -847 Ok(prev) -848 } -849 -850 /// Returns the remote path status -851 // TODO(flub): Probably should also be some kind of path event? Not even sure if I like -852 // this as an API, but for now it allows me to write a test easily. -853 // TODO(flub): Technically this should be a Result<Option<PathSTatus>>? -854 pub fn remote_path_status(&self, path_id: PathId) -> Option<PathStatus> { -855 self.path(path_id).and_then(|path| path.remote_status()) -856 } -857 -858 /// Sets the max_idle_timeout for a specific path. -859 /// -860 /// The PathIdle timer is immediately re-armed accounting for already-elapsed -861 /// idle time. Setting `None` disables the timeout and stops the timer. -862 /// -863 /// See [`TransportConfig::default_path_max_idle_timeout`] for details. -864 /// -865 /// Returns the previous value of the setting. -866 pub fn set_path_max_idle_timeout( -867 &mut self, -868 now: Instant, -869 path_id: PathId, -870 timeout: Option<Duration>, -871 ) -> Result<Option<Duration>, ClosedPath> { -872 let path = self -873 .paths -874 .get_mut(&path_id) -875 .ok_or(ClosedPath { _private: () })?; -876 let prev = std::mem::replace(&mut path.data.idle_timeout, timeout); -877 -878 // Adjust the PathIdle timer, accounting for already-elapsed idle time. -879 if !self.state.is_closed() { -880 if let Some(new_timeout) = timeout { -881 let timer = Timer::PerPath(path_id, PathTimer::PathIdle); -882 let deadline = match (prev, self.timers.get(timer)) { -883 (Some(old_timeout), Some(old_deadline)) => { -884 let last_activity = old_deadline.checked_sub(old_timeout).unwrap_or(now); -885 last_activity + new_timeout -886 } -887 _ => now + new_timeout, -888 }; -889 self.timers.set(timer, deadline, self.qlog.with_time(now)); -890 } else { -891 self.timers.stop( -892 Timer::PerPath(path_id, PathTimer::PathIdle), -893 self.qlog.with_time(now), -894 ); -895 } -896 } -897 -898 Ok(prev) -899 } -900 -901 /// Sets the keep_alive_interval for a specific path -902 /// -903 /// See [`TransportConfig::default_path_keep_alive_interval`] for details. -904 /// -905 /// Returns the previous value of the setting. -906 pub fn set_path_keep_alive_interval( -907 &mut self, -908 path_id: PathId, -909 interval: Option<Duration>, -910 ) -> Result<Option<Duration>, ClosedPath> { -911 let path = self -912 .paths -913 .get_mut(&path_id) -914 .ok_or(ClosedPath { _private: () })?; -915 Ok(std::mem::replace(&mut path.data.keep_alive, interval)) -916 } -917 -918 /// Find an open, validated path that's on the same network path as the given network path. -919 /// -920 /// Returns the first path matching, even if there's multiple. -921 fn find_validated_path_on_network_path( -922 &self, -923 network_path: FourTuple, -924 ) -> Option<(&PathId, &PathState)> { -925 self.paths.iter().find(|(path_id, path_state)| { -926 path_state.data.validated -927 // Would this use the same network path, if network_path were used to send right now? -928 && network_path.is_probably_same_path(&path_state.data.network_path) -929 && !self.abandoned_paths.contains(path_id) -930 }) -931 // TODO(@divma): we might want to ensure the path has been recently active to consider the -932 // address validated -933 // matheus23: Perhaps looking at !self.abandoned_paths.contains(path_id) is enough, given keep-alives? -934 } -935 -936 /// Creates the [`PathData`] for a new [`PathId`]. -937 /// -938 /// Called for incoming packets as well as when opening a new path locally. -939 fn ensure_path( -940 &mut self, -941 path_id: PathId, -942 network_path: FourTuple, -943 now: Instant, -944 pn: Option<u64>, -945 ) -> &mut PathData { -946 let valid_path = self.find_validated_path_on_network_path(network_path); -947 let validated = valid_path.is_some(); -948 let initial_rtt = valid_path.map(|(_, path)| path.data.rtt.conservative()); -949 let vacant_entry = match self.paths.entry(path_id) { -950 btree_map::Entry::Vacant(vacant_entry) => vacant_entry, -951 btree_map::Entry::Occupied(occupied_entry) => { -952 return &mut occupied_entry.into_mut().data; -953 } -954 }; -955 -956 debug!(%validated, %path_id, %network_path, "path added"); -957 -958 // A new path was added. Cancel any pending NoAvailablePath grace timer. -959 self.timers.stop( -960 Timer::Conn(ConnTimer::NoAvailablePath), -961 self.qlog.with_time(now), -962 ); -963 let peer_max_udp_payload_size = -964 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -965 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -966 let mut data = PathData::new( -967 network_path, -968 self.allow_mtud, -969 Some(peer_max_udp_payload_size), -970 self.path_generation_counter, -971 now, -972 &self.config, -973 ); -974 -975 data.validated = validated; -976 if let Some(initial_rtt) = initial_rtt { -977 data.rtt.reset_initial_rtt(initial_rtt); -978 } -979 -980 // To open a path locally we need to send a packet on the path. Sending a challenge -981 // guarantees this. -982 data.pending_on_path_challenge = true; -983 -984 let path = vacant_entry.insert(PathState { data, prev: None }); -985 -986 let mut pn_space = spaces::PacketNumberSpace::new(now, SpaceId::Data, &mut self.rng); -987 if let Some(pn) = pn { -988 pn_space.dedup.insert(pn); -989 } -990 self.spaces[SpaceId::Data] -991 .number_spaces -992 .insert(path_id, pn_space); -993 self.qlog.emit_tuple_assigned(path_id, network_path, now); -994 -995 // If the remote opened this path we may not have CIDs for it. For locally opened -996 // paths the caller should have already made sure we have CIDs and refused to open -997 // it if there were none. -998 if !self.remote_cids.contains_key(&path_id) { -999 debug!(%path_id, "Remote opened path without issuing CIDs"); -1000 self.spaces[SpaceId::Data] -1001 .pending -1002 .path_cids_blocked -1003 .insert(path_id); -1004 // Do not abandon this path right away. CIDs might be in-flight still and arrive -1005 // soon. It is up to the remote to handle this situation. -1006 } +816 /// Sets the [`PathStatus`] for a known [`PathId`] +817 /// +818 /// Returns the previous path status on success. +819 pub fn set_path_status( +820 &mut self, +821 path_id: PathId, +822 status: PathStatus, +823 ) -> Result<PathStatus, SetPathStatusError> { +824 if !self.is_multipath_negotiated() { +825 return Err(SetPathStatusError::MultipathNotNegotiated); +826 } +827 let path = self +828 .path_mut(path_id) +829 .ok_or(SetPathStatusError::ClosedPath)?; +830 let prev = match path.status.local_update(status) { +831 Some(prev) => { +832 self.spaces[SpaceId::Data] +833 .pending +834 .path_status +835 .insert(path_id); +836 prev +837 } +838 None => path.local_status(), +839 }; +840 Ok(prev) +841 } +842 +843 /// Returns the remote path status +844 // TODO(flub): Probably should also be some kind of path event? Not even sure if I like +845 // this as an API, but for now it allows me to write a test easily. +846 // TODO(flub): Technically this should be a Result<Option<PathSTatus>>? +847 pub fn remote_path_status(&self, path_id: PathId) -> Option<PathStatus> { +848 self.path(path_id).and_then(|path| path.remote_status()) +849 } +850 +851 /// Sets the max_idle_timeout for a specific path. +852 /// +853 /// The PathIdle timer is immediately re-armed accounting for already-elapsed +854 /// idle time. Setting `None` disables the timeout and stops the timer. +855 /// +856 /// See [`TransportConfig::default_path_max_idle_timeout`] for details. +857 /// +858 /// Returns the previous value of the setting. +859 pub fn set_path_max_idle_timeout( +860 &mut self, +861 now: Instant, +862 path_id: PathId, +863 timeout: Option<Duration>, +864 ) -> Result<Option<Duration>, ClosedPath> { +865 let path = self +866 .paths +867 .get_mut(&path_id) +868 .ok_or(ClosedPath { _private: () })?; +869 let prev = std::mem::replace(&mut path.data.idle_timeout, timeout); +870 +871 // Adjust the PathIdle timer, accounting for already-elapsed idle time. +872 if !self.state.is_closed() { +873 if let Some(new_timeout) = timeout { +874 let timer = Timer::PerPath(path_id, PathTimer::PathIdle); +875 let deadline = match (prev, self.timers.get(timer)) { +876 (Some(old_timeout), Some(old_deadline)) => { +877 let last_activity = old_deadline.checked_sub(old_timeout).unwrap_or(now); +878 last_activity + new_timeout +879 } +880 _ => now + new_timeout, +881 }; +882 self.timers.set(timer, deadline, self.qlog.with_time(now)); +883 } else { +884 self.timers.stop( +885 Timer::PerPath(path_id, PathTimer::PathIdle), +886 self.qlog.with_time(now), +887 ); +888 } +889 } +890 +891 Ok(prev) +892 } +893 +894 /// Sets the keep_alive_interval for a specific path +895 /// +896 /// See [`TransportConfig::default_path_keep_alive_interval`] for details. +897 /// +898 /// Returns the previous value of the setting. +899 pub fn set_path_keep_alive_interval( +900 &mut self, +901 path_id: PathId, +902 interval: Option<Duration>, +903 ) -> Result<Option<Duration>, ClosedPath> { +904 let path = self +905 .paths +906 .get_mut(&path_id) +907 .ok_or(ClosedPath { _private: () })?; +908 Ok(std::mem::replace(&mut path.data.keep_alive, interval)) +909 } +910 +911 /// Find an open, validated path that's on the same network path as the given network path. +912 /// +913 /// Returns the first path matching, even if there's multiple. +914 fn find_validated_path_on_network_path( +915 &self, +916 network_path: FourTuple, +917 ) -> Option<(&PathId, &PathState)> { +918 self.paths.iter().find(|(path_id, path_state)| { +919 path_state.data.validated +920 // Would this use the same network path, if network_path were used to send right now? +921 && network_path.is_probably_same_path(&path_state.data.network_path) +922 && !self.abandoned_paths.contains(path_id) +923 }) +924 // TODO(@divma): we might want to ensure the path has been recently active to consider the +925 // address validated +926 // matheus23: Perhaps looking at !self.abandoned_paths.contains(path_id) is enough, given keep-alives? +927 } +928 +929 /// Creates the [`PathData`] for a new [`PathId`]. +930 /// +931 /// Called for incoming packets as well as when opening a new path locally. +932 fn ensure_path( +933 &mut self, +934 path_id: PathId, +935 network_path: FourTuple, +936 now: Instant, +937 pn: Option<u64>, +938 ) -> &mut PathData { +939 let valid_path = self.find_validated_path_on_network_path(network_path); +940 let validated = valid_path.is_some(); +941 let initial_rtt = valid_path.map(|(_, path)| path.data.rtt.conservative()); +942 let vacant_entry = match self.paths.entry(path_id) { +943 btree_map::Entry::Vacant(vacant_entry) => vacant_entry, +944 btree_map::Entry::Occupied(occupied_entry) => { +945 return &mut occupied_entry.into_mut().data; +946 } +947 }; +948 +949 debug!(%validated, %path_id, %network_path, "path added"); +950 +951 // A new path was added. Cancel any pending NoAvailablePath grace timer. +952 self.timers.stop( +953 Timer::Conn(ConnTimer::NoAvailablePath), +954 self.qlog.with_time(now), +955 ); +956 let peer_max_udp_payload_size = +957 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); +958 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); +959 let mut data = PathData::new( +960 network_path, +961 self.allow_mtud, +962 Some(peer_max_udp_payload_size), +963 self.path_generation_counter, +964 now, +965 &self.config, +966 ); +967 +968 data.validated = validated; +969 if let Some(initial_rtt) = initial_rtt { +970 data.rtt.reset_initial_rtt(initial_rtt); +971 } +972 +973 // To open a path locally we need to send a packet on the path. Sending a challenge +974 // guarantees this. +975 data.pending_challenge = true; +976 data.pending.observed_address = self +977 .config +978 .address_discovery_role +979 .should_report(&self.peer_params.address_discovery_role); +980 +981 let path = vacant_entry.insert(PathState { data, prev: None }); +982 +983 let mut pn_space = spaces::PacketNumberSpace::new(now, SpaceId::Data, &mut self.rng); +984 if let Some(pn) = pn { +985 pn_space.dedup.insert(pn); +986 } +987 self.spaces[SpaceId::Data] +988 .number_spaces +989 .insert(path_id, pn_space); +990 self.qlog.emit_tuple_assigned(path_id, network_path, now); +991 +992 // If the remote opened this path we may not have CIDs for it. For locally opened +993 // paths the caller should have already made sure we have CIDs and refused to open +994 // it if there were none. +995 if !self.remote_cids.contains_key(&path_id) { +996 debug!(%path_id, "Remote opened path without issuing CIDs"); +997 self.spaces[SpaceId::Data] +998 .pending +999 .path_cids_blocked +1000 .insert(path_id, VarInt(0)); +1001 // Do not abandon this path right away. CIDs might be in-flight still and arrive +1002 // soon. It is up to the remote to handle this situation. +1003 } +1004 +1005 &mut path.data +1006 } 1007 -1008 &mut path.data -1009 } -1010 -1011 /// Returns packets to transmit -1012 /// -1013 /// Connections should be polled for transmit after: -1014 /// - the application performed some I/O on the connection -1015 /// - a call was made to `handle_event` -1016 /// - a call was made to `handle_timeout` -1017 /// -1018 /// `max_datagrams` specifies how many datagrams can be returned inside a -1019 /// single Transmit using GSO. This must be at least 1. -1020 #[must_use] -1021 pub fn poll_transmit( -1022 &mut self, -1023 now: Instant, -1024 max_datagrams: NonZeroUsize, -1025 buf: &mut Vec<u8>, -1026 ) -> Option<Transmit> { -1027 let max_datagrams = match self.config.enable_segmentation_offload { -1028 false => NonZeroUsize::MIN, -1029 true => max_datagrams, -1030 }; -1031 -1032 // Each call to poll_transmit can only send datagrams to one destination, because -1033 // all datagrams in a GSO batch are for the same destination. Therefore only -1034 // datagrams for one destination address are produced for each poll_transmit call. -1035 -1036 // Check whether we need to send a close message -1037 let connection_close_pending = match self.state.as_type() { -1038 StateType::Drained => { -1039 for path in self.paths.values_mut() { -1040 path.data.app_limited = true; -1041 } -1042 return None; -1043 } -1044 StateType::Draining | StateType::Closed => { -1045 // self.connection_close_pending is only reset once the associated packet -1046 // had been encoded successfully -1047 if !self.connection_close_pending { -1048 for path in self.paths.values_mut() { -1049 path.data.app_limited = true; -1050 } -1051 return None; -1052 } -1053 true -1054 } -1055 _ => false, -1056 }; -1057 -1058 // Schedule an ACK_FREQUENCY frame if a new one needs to be sent. -1059 if let Some(config) = &self.config.ack_frequency_config { -1060 let rtt = self -1061 .paths -1062 .values() -1063 .map(|p| p.data.rtt.get()) -1064 .min() -1065 .expect("one path exists"); -1066 self.spaces[SpaceId::Data].pending.ack_frequency = self -1067 .ack_frequency -1068 .should_send_ack_frequency(rtt, config, &self.peer_params) -1069 && self.highest_space == SpaceKind::Data -1070 && self.peer_supports_ack_frequency(); -1071 } -1072 -1073 let mut next_path_id = self.paths.first_entry().map(|e| *e.key()); -1074 while let Some(path_id) = next_path_id { -1075 if !connection_close_pending -1076 && let Some(transmit) = self.poll_transmit_off_path(now, buf, path_id) -1077 { -1078 return Some(transmit); -1079 } -1080 -1081 let info = self.scheduling_info(path_id); -1082 if let Some(transmit) = self.poll_transmit_on_path( -1083 now, -1084 buf, -1085 path_id, -1086 max_datagrams, -1087 &info, -1088 connection_close_pending, -1089 ) { -1090 return Some(transmit); -1091 } -1092 -1093 // Continue checking other paths, tail-loss probes may need to be sent -1094 // in all spaces. -1095 debug_assert!( -1096 buf.is_empty(), -1097 "nothing to send on path but buffer not empty" -1098 ); -1099 -1100 next_path_id = self.paths.keys().find(|i| **i > path_id).copied(); -1101 } -1102 -1103 // We didn't produce any application data packet -1104 debug_assert!( -1105 buf.is_empty(), -1106 "there was data in the buffer, but it was not sent" -1107 ); -1108 -1109 if self.state.is_established() { -1110 // Try MTU probing now -1111 let mut next_path_id = self.paths.first_entry().map(|e| *e.key()); -1112 while let Some(path_id) = next_path_id { -1113 if let Some(transmit) = self.poll_transmit_mtu_probe(now, buf, path_id) { -1114 return Some(transmit); -1115 } -1116 next_path_id = self.paths.keys().find(|i| **i > path_id).copied(); -1117 } -1118 } -1119 -1120 None -1121 } -1122 -1123 /// Computes the packet scheduling information for this path. -1124 /// -1125 /// While this information is only returned for a single path, it is important to know -1126 /// that this information remains static for the entire span of a single -1127 /// [`Connection::poll_transmit`] call. In other words, the return value is purely -1128 /// functional and only depends on the [`PathId`] **during a single** `poll_transmit` -1129 /// call. It can be computed up-front for all paths but we don't do that because it -1130 /// involves an allocation. -1131 /// -1132 /// See the inline comments for how the packet scheduling works. +1008 /// Returns packets to transmit +1009 /// +1010 /// Connections should be polled for transmit after: +1011 /// - the application performed some I/O on the connection +1012 /// - a call was made to `handle_event` +1013 /// - a call was made to `handle_timeout` +1014 /// +1015 /// `max_datagrams` specifies how many datagrams can be returned inside a +1016 /// single Transmit using GSO. This must be at least 1. +1017 #[must_use] +1018 pub fn poll_transmit( +1019 &mut self, +1020 now: Instant, +1021 max_datagrams: NonZeroUsize, +1022 buf: &mut Vec<u8>, +1023 ) -> Option<Transmit> { +1024 let max_datagrams = match self.config.enable_segmentation_offload { +1025 false => NonZeroUsize::MIN, +1026 true => max_datagrams, +1027 }; +1028 +1029 // Each call to poll_transmit can only send datagrams to one destination, because +1030 // all datagrams in a GSO batch are for the same destination. Therefore only +1031 // datagrams for one destination address are produced for each poll_transmit call. +1032 +1033 // Check whether we need to send a close message +1034 let connection_close_pending = match self.state.as_type() { +1035 StateType::Drained => { +1036 for path in self.paths.values_mut() { +1037 path.data.app_limited = true; +1038 } +1039 return None; +1040 } +1041 StateType::Draining | StateType::Closed => { +1042 // self.connection_close_pending is only reset once the associated packet +1043 // had been encoded successfully +1044 if !self.connection_close_pending { +1045 for path in self.paths.values_mut() { +1046 path.data.app_limited = true; +1047 } +1048 return None; +1049 } +1050 true +1051 } +1052 _ => false, +1053 }; +1054 +1055 // Schedule an ACK_FREQUENCY frame if a new one needs to be sent. +1056 if let Some(config) = &self.config.ack_frequency_config { +1057 let rtt = self +1058 .paths +1059 .values() +1060 .map(|p| p.data.rtt.get()) +1061 .min() +1062 .expect("one path exists"); +1063 self.spaces[SpaceId::Data].pending.ack_frequency = self +1064 .ack_frequency +1065 .should_send_ack_frequency(rtt, config, &self.peer_params) +1066 && self.highest_space == SpaceKind::Data +1067 && self.peer_supports_ack_frequency(); +1068 } +1069 +1070 let mut next_path_id = self.paths.first_entry().map(|e| *e.key()); +1071 while let Some(path_id) = next_path_id { +1072 if !connection_close_pending +1073 && let Some(transmit) = self.poll_transmit_off_path(now, buf, path_id) +1074 { +1075 #[cfg(test)] +1076 { +1077 self.partial_stats.transmits_tx += 1; +1078 } +1079 return Some(transmit); +1080 } +1081 +1082 let info = self.scheduling_info(path_id); +1083 if let Some(transmit) = self.poll_transmit_on_path( +1084 now, +1085 buf, +1086 path_id, +1087 max_datagrams, +1088 &info, +1089 connection_close_pending, +1090 ) { +1091 #[cfg(test)] +1092 { +1093 self.partial_stats.transmits_tx += 1; +1094 } +1095 return Some(transmit); +1096 } +1097 +1098 // Continue checking other paths, tail-loss probes may need to be sent +1099 // in all spaces. +1100 debug_assert!( +1101 buf.is_empty(), +1102 "nothing to send on path but buffer not empty" +1103 ); +1104 +1105 next_path_id = self.paths.keys().find(|i| **i > path_id).copied(); +1106 } +1107 +1108 // We didn't produce any application data packet +1109 debug_assert!( +1110 buf.is_empty(), +1111 "there was data in the buffer, but it was not sent" +1112 ); +1113 +1114 if self.state.is_established() { +1115 // Try MTU probing now +1116 let mut next_path_id = self.paths.first_entry().map(|e| *e.key()); +1117 while let Some(path_id) = next_path_id { +1118 if let Some(transmit) = self.poll_transmit_mtu_probe(now, buf, path_id) { +1119 #[cfg(test)] +1120 { +1121 self.partial_stats.transmits_tx += 1; +1122 } +1123 return Some(transmit); +1124 } +1125 next_path_id = self.paths.keys().find(|i| **i > path_id).copied(); +1126 } +1127 } +1128 +1129 None +1130 } +1131 +1132 /// Computes the packet scheduling information for this path. 1133 /// -1134 /// # Panics -1135 /// -1136 /// This will panic if called for a path for which we do not have any [`PathData`], like -1137 /// so many other functions we have. But this is the only one to document this in its -1138 /// doc comment. Maybe that should change. Eventually we'll refactor things for this -1139 /// panic to go away. -1140 fn scheduling_info(&self, path_id: PathId) -> PathSchedulingInfo { -1141 // Such a space is preferred for SpaceKind::Data frames. -1142 let have_validated_status_available_space = self.paths.iter().any(|(path_id, path)| { -1143 self.remote_cids.contains_key(path_id) -1144 && !self.abandoned_paths.contains(path_id) -1145 && path.data.validated -1146 && path.data.local_status() == PathStatus::Available -1147 }); -1148 -1149 // Such a space is able to send SpaceKind::Data frames. -1150 let have_validated_space = self.paths.iter().any(|(path_id, path)| { -1151 self.remote_cids.contains_key(path_id) -1152 && !self.abandoned_paths.contains(path_id) -1153 && path.data.validated -1154 }); -1155 -1156 let is_handshaking = self.is_handshaking(); -1157 let has_cids = self.remote_cids.contains_key(&path_id); -1158 let is_abandoned = self.abandoned_paths.contains(&path_id); -1159 let path_data = self.path_data(path_id); -1160 let validated = path_data.validated; -1161 let status = path_data.local_status(); -1162 -1163 // This is the core packet scheduling, whether this space ID may send -1164 // SpaceKind::Data frames. -1165 let may_send_data = has_cids -1166 && !is_abandoned -1167 && if is_handshaking { -1168 // There is only one path during the handshake. We want to -1169 // already send 0-RTT and 0.5-RTT (permitting anti-amplification -1170 // limit) data. -1171 true -1172 } else if !validated { -1173 // TODO(flub): When we have a network change we might end up -1174 // having to abandon all paths and re-open new ones to the -1175 // same remotes. This leaves us without any validated -1176 // path. Perhaps we should have a way to figure out if the -1177 // path is to a previously-validated remote address and allow -1178 // sending data to such remotes immediately. -1179 false -1180 } else { -1181 match status { -1182 PathStatus::Available => { -1183 // Best possible space to send data on. -1184 true -1185 } -1186 PathStatus::Backup => { -1187 // If there is a status-available path we prefer that. -1188 !have_validated_status_available_space -1189 } -1190 } -1191 }; -1192 -1193 // CONNECTION_CLOSE is allowed to be sent on a non-validated -1194 // path. Particularly during the handshake we want to send it before the -1195 // path is validated. Later if there is no validated path available we -1196 // will also accept sending it on an un-validated path. -1197 let may_send_close = has_cids -1198 && !is_abandoned -1199 && if !validated && have_validated_status_available_space { -1200 // We have a better space to send on. -1201 false -1202 } else { -1203 // No other validated space, this is as good as it gets. -1204 true -1205 }; -1206 -1207 // PATH_ABANDON is normally sent together with other SpaceKind::Data frames. But if -1208 // there is no other validated space to send it on, it can be sent on the path to be -1209 // abandoned itself if that was validated. -1210 let may_self_abandon = has_cids && validated && !have_validated_space; -1211 -1212 PathSchedulingInfo { -1213 is_abandoned, -1214 may_send_data, -1215 may_send_close, -1216 may_self_abandon, -1217 } -1218 } -1219 -1220 fn build_transmit(&mut self, path_id: PathId, transmit: TransmitBuf<'_>) -> Transmit { -1221 debug_assert!( -1222 !transmit.is_empty(), -1223 "must not be called with an empty transmit buffer" -1224 ); -1225 -1226 let network_path = self.path_data(path_id).network_path; -1227 trace!( -1228 segment_size = transmit.segment_size(), -1229 last_datagram_len = transmit.len() % transmit.segment_size(), -1230 %network_path, -1231 "sending {} bytes in {} datagrams", -1232 transmit.len(), -1233 transmit.num_datagrams() -1234 ); -1235 self.path_data_mut(path_id) -1236 .inc_total_sent(transmit.len() as u64); -1237 -1238 self.path_stats -1239 .for_path(path_id) -1240 .udp_tx -1241 .on_sent(transmit.num_datagrams() as u64, transmit.len()); -1242 -1243 Transmit { -1244 destination: network_path.remote, -1245 size: transmit.len(), -1246 ecn: if self.path_data(path_id).sending_ecn { -1247 Some(EcnCodepoint::Ect0) -1248 } else { -1249 None -1250 }, -1251 segment_size: match transmit.num_datagrams() { -1252 1 => None, -1253 _ => Some(transmit.segment_size()), -1254 }, -1255 src_ip: network_path.local_ip, -1256 } -1257 } -1258 -1259 /// poll_transmit logic for off-path data. -1260 fn poll_transmit_off_path( -1261 &mut self, -1262 now: Instant, -1263 buf: &mut Vec<u8>, -1264 path_id: PathId, -1265 ) -> Option<Transmit> { -1266 if let Some(challenge) = self.send_prev_path_challenge(now, buf, path_id) { -1267 return Some(challenge); -1268 } -1269 if let Some(response) = self.send_off_path_path_response(now, buf, path_id) { -1270 return Some(response); -1271 } -1272 if let Some(challenge) = self.send_nat_traversal_path_challenge(now, buf, path_id) { -1273 return Some(challenge); -1274 } -1275 None -1276 } -1277 -1278 /// poll_transmit logic for on-path data. -1279 /// -1280 /// This is not quite the same as for a multipath packet space, since [`PathId::ZERO`] -1281 /// has 3 packet spaces, which this handles. -1282 /// -1283 /// See [`Self::poll_transmit_off_path`] for off-path data. -1284 #[must_use] -1285 fn poll_transmit_on_path( -1286 &mut self, -1287 now: Instant, -1288 buf: &mut Vec<u8>, -1289 path_id: PathId, -1290 max_datagrams: NonZeroUsize, -1291 scheduling_info: &PathSchedulingInfo, -1292 connection_close_pending: bool, -1293 ) -> Option<Transmit> { -1294 // Check if there is at least one active CID to use for sending -1295 let Some(remote_cid) = self.remote_cids.get(&path_id).map(CidQueue::active) else { -1296 if !self.abandoned_paths.contains(&path_id) { -1297 debug!(%path_id, "no remote CIDs for path"); -1298 } -1299 return None; -1300 }; -1301 -1302 // Whether the last packet in the datagram must be padded so the datagram takes up -1303 // an exact size. An earlier space can decide to not fill an entire datagram and -1304 // require the next space to fill it further. But may need a specific size of the -1305 // datagram containing the packet. The final packet built in the datagram must pad -1306 // to this size. -1307 let mut pad_datagram = PadDatagram::No; -1308 -1309 // The packet number of the last built packet. This is kept kept across spaces. -1310 // QUIC is supposed to have a single congestion controller for the Initial, -1311 // Handshake and Data(PathId::ZERO) spaces. -1312 let mut last_packet_number = None; -1313 -1314 // If we end up not sending anything, we need to know if that was because there was -1315 // nothing to send or because we were congestion blocked. -1316 let mut congestion_blocked = false; +1134 /// While this information is only returned for a single path, it is important to know +1135 /// that this information remains static for the entire span of a single +1136 /// [`Connection::poll_transmit`] call. In other words, the return value is purely +1137 /// functional and only depends on the [`PathId`] **during a single** `poll_transmit` +1138 /// call. It can be computed up-front for all paths but we don't do that because it +1139 /// involves an allocation. +1140 /// +1141 /// See the inline comments for how the packet scheduling works. +1142 /// +1143 /// # Panics +1144 /// +1145 /// This will panic if called for a path for which we do not have any [`PathData`], like +1146 /// so many other functions we have. But this is the only one to document this in its +1147 /// doc comment. Maybe that should change. Eventually we'll refactor things for this +1148 /// panic to go away. +1149 fn scheduling_info(&self, path_id: PathId) -> PathSchedulingInfo { +1150 // Such a space is preferred for SpaceKind::Data frames. +1151 let have_validated_status_available_space = self.paths.iter().any(|(path_id, path)| { +1152 self.remote_cids.contains_key(path_id) +1153 && !self.abandoned_paths.contains(path_id) +1154 && path.data.validated +1155 && path.data.local_status() == PathStatus::Available +1156 }); +1157 +1158 // Such a space is able to send SpaceKind::Data frames. +1159 let have_validated_space = self.paths.iter().any(|(path_id, path)| { +1160 self.remote_cids.contains_key(path_id) +1161 && !self.abandoned_paths.contains(path_id) +1162 && path.data.validated +1163 }); +1164 +1165 let is_handshaking = self.is_handshaking(); +1166 let has_cids = self.remote_cids.contains_key(&path_id); +1167 let is_abandoned = self.abandoned_paths.contains(&path_id); +1168 let path_data = self.path_data(path_id); +1169 let validated = path_data.validated; +1170 let status = path_data.local_status(); +1171 +1172 // This is the core packet scheduling, whether this space ID may send +1173 // SpaceKind::Data frames. +1174 let may_send_data = has_cids +1175 && !is_abandoned +1176 && if is_handshaking { +1177 // There is only one path during the handshake. We want to +1178 // already send 0-RTT and 0.5-RTT (permitting anti-amplification +1179 // limit) data. +1180 true +1181 } else if !validated { +1182 // TODO(flub): When we have a network change we might end up +1183 // having to abandon all paths and re-open new ones to the +1184 // same remotes. This leaves us without any validated +1185 // path. Perhaps we should have a way to figure out if the +1186 // path is to a previously-validated remote address and allow +1187 // sending data to such remotes immediately. +1188 false +1189 } else { +1190 match status { +1191 PathStatus::Available => { +1192 // Best possible space to send data on. +1193 true +1194 } +1195 PathStatus::Backup => { +1196 // If there is a status-available path we prefer that. +1197 !have_validated_status_available_space +1198 } +1199 } +1200 }; +1201 +1202 // CONNECTION_CLOSE is allowed to be sent on a non-validated +1203 // path. Particularly during the handshake we want to send it before the +1204 // path is validated. Later if there is no validated path available we +1205 // will also accept sending it on an un-validated path. +1206 let may_send_close = has_cids +1207 && !is_abandoned +1208 && if !validated && have_validated_status_available_space { +1209 // We have a better space to send on. +1210 false +1211 } else { +1212 // No other validated space, this is as good as it gets. +1213 true +1214 }; +1215 +1216 // PATH_ABANDON is normally sent together with other SpaceKind::Data frames. But if +1217 // there is no other validated space to send it on, it can be sent on the path to be +1218 // abandoned itself if that was validated. +1219 let may_self_abandon = has_cids && validated && !have_validated_space; +1220 +1221 PathSchedulingInfo { +1222 is_abandoned, +1223 may_send_data, +1224 may_send_close, +1225 may_self_abandon, +1226 } +1227 } +1228 +1229 fn build_transmit(&mut self, path_id: PathId, transmit: TransmitBuf<'_>) -> Transmit { +1230 debug_assert!( +1231 !transmit.is_empty(), +1232 "must not be called with an empty transmit buffer" +1233 ); +1234 +1235 let network_path = self.path_data(path_id).network_path; +1236 trace!( +1237 segment_size = transmit.segment_size(), +1238 last_datagram_len = transmit.len() % transmit.segment_size(), +1239 %network_path, +1240 "sending {} bytes in {} datagrams", +1241 transmit.len(), +1242 transmit.num_datagrams() +1243 ); +1244 self.path_data_mut(path_id) +1245 .inc_total_sent(transmit.len() as u64); +1246 +1247 self.path_stats +1248 .get_mut(path_id) +1249 .udp_tx +1250 .on_sent(transmit.num_datagrams() as u64, transmit.len()); +1251 +1252 Transmit { +1253 destination: network_path.remote, +1254 size: transmit.len(), +1255 ecn: if self.path_data(path_id).sending_ecn { +1256 Some(EcnCodepoint::Ect0) +1257 } else { +1258 None +1259 }, +1260 segment_size: match transmit.num_datagrams() { +1261 1 => None, +1262 _ => Some(transmit.segment_size()), +1263 }, +1264 src_ip: network_path.local_ip, +1265 } +1266 } +1267 +1268 /// poll_transmit logic for off-path data. +1269 fn poll_transmit_off_path( +1270 &mut self, +1271 now: Instant, +1272 buf: &mut Vec<u8>, +1273 path_id: PathId, +1274 ) -> Option<Transmit> { +1275 if let Some(challenge) = self.send_prev_path_challenge(now, buf, path_id) { +1276 return Some(challenge); +1277 } +1278 if let Some(response) = self.send_off_path_path_response(now, buf, path_id) { +1279 return Some(response); +1280 } +1281 if let Some(challenge) = self.send_nat_traversal_path_challenge(now, buf, path_id) { +1282 return Some(challenge); +1283 } +1284 None +1285 } +1286 +1287 /// poll_transmit logic for on-path data. +1288 /// +1289 /// This is not quite the same as for a multipath packet space, since [`PathId::ZERO`] +1290 /// has 3 packet spaces, which this handles. +1291 /// +1292 /// See [`Self::poll_transmit_off_path`] for off-path data. +1293 #[must_use] +1294 fn poll_transmit_on_path( +1295 &mut self, +1296 now: Instant, +1297 buf: &mut Vec<u8>, +1298 path_id: PathId, +1299 max_datagrams: NonZeroUsize, +1300 scheduling_info: &PathSchedulingInfo, +1301 connection_close_pending: bool, +1302 ) -> Option<Transmit> { +1303 // Check if there is at least one active CID to use for sending +1304 let Some(remote_cid) = self.remote_cids.get(&path_id).map(CidQueue::active) else { +1305 if !self.abandoned_paths.contains(&path_id) { +1306 debug!(%path_id, "no remote CIDs for path"); +1307 } +1308 return None; +1309 }; +1310 +1311 // Whether the last packet in the datagram must be padded so the datagram takes up +1312 // an exact size. An earlier space can decide to not fill an entire datagram and +1313 // require the next space to fill it further. But may need a specific size of the +1314 // datagram containing the packet. The final packet built in the datagram must pad +1315 // to this size. +1316 let mut pad_datagram = PadDatagram::No; 1317 -1318 // Set the segment size to this path's MTU for on-path data. -1319 let pmtu = self.path_data(path_id).current_mtu().into(); -1320 let mut transmit = TransmitBuf::new(buf, max_datagrams, pmtu); -1321 -1322 // Iterate over the available spaces. -1323 for space_id in SpaceId::iter() { -1324 // Only PathId::ZERO uses non Data space ids. -1325 if path_id != PathId::ZERO && space_id != SpaceId::Data { -1326 continue; -1327 } -1328 match self.poll_transmit_path_space( -1329 now, -1330 &mut transmit, -1331 path_id, -1332 space_id, -1333 remote_cid, -1334 scheduling_info, -1335 connection_close_pending, -1336 pad_datagram, -1337 ) { -1338 PollPathSpaceStatus::NothingToSend { -1339 congestion_blocked: cb, -1340 } => { -1341 congestion_blocked |= cb; -1342 // Continue checking other spaces, tail-loss probes may need to be sent -1343 // in all spaces. -1344 } -1345 PollPathSpaceStatus::WrotePacket { -1346 last_packet_number: pn, -1347 pad_datagram: pad, -1348 } => { -1349 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); -1350 last_packet_number = Some(pn); -1351 pad_datagram = pad; -1352 // Always check higher spaces. If the transmit is full or they have -1353 // nothing to send they will not write packets. But if they can, they -1354 // must always be allowed to add to this transmit because coalescing may -1355 // be required. -1356 continue; -1357 } -1358 PollPathSpaceStatus::Send { -1359 last_packet_number: pn, -1360 } => { -1361 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); -1362 last_packet_number = Some(pn); -1363 break; -1364 } -1365 } -1366 } -1367 -1368 if last_packet_number.is_some() || congestion_blocked { -1369 self.qlog.emit_recovery_metrics( -1370 path_id, -1371 &mut self -1372 .paths -1373 .get_mut(&path_id) -1374 .expect("path_id was iterated from self.paths above") -1375 .data, -1376 now, -1377 ); -1378 } -1379 -1380 self.path_data_mut(path_id).app_limited = -1381 last_packet_number.is_none() && !congestion_blocked; -1382 -1383 match last_packet_number { -1384 Some(last_packet_number) => { -1385 // Note that when sending in multiple spaces the last packet number will be -1386 // the one from the highest space. -1387 self.path_data_mut(path_id).congestion.on_sent( -1388 now, -1389 transmit.len() as u64, -1390 last_packet_number, -1391 ); -1392 Some(self.build_transmit(path_id, transmit)) -1393 } -1394 None => None, -1395 } -1396 } -1397 -1398 /// poll_transmit logic for a QUIC-MULTIPATH packet number space (PathID + SpaceId). -1399 #[must_use] -1400 fn poll_transmit_path_space( -1401 &mut self, -1402 now: Instant, -1403 transmit: &mut TransmitBuf<'_>, -1404 path_id: PathId, -1405 space_id: SpaceId, -1406 remote_cid: ConnectionId, -1407 scheduling_info: &PathSchedulingInfo, -1408 // If we need to send a CONNECTION_CLOSE frame. -1409 connection_close_pending: bool, -1410 // Whether the current datagram needs to be padded to a certain size. -1411 mut pad_datagram: PadDatagram, -1412 ) -> PollPathSpaceStatus { -1413 // Keep track of the last packet number we wrote. If None we did not write any -1414 // packets. -1415 let mut last_packet_number = None; -1416 -1417 // Each loop of this may build one packet. It works logically as follows: -1418 // -1419 // - Check if something *needs* to be sent in this space and *can* be sent. -1420 // - If not, return to the caller who will call us again for the next space. -1421 // - Start a new datagram. -1422 // - Unless coalescing the packet into an existing datagram. -1423 // - Write the packet header and payload. -1424 // - Check if coalescing a next packet into the datagram is possible. -1425 // - If coalescing, finish packet without padding to leave space in the datagram. -1426 // - If not coalescing, complete the datagram: -1427 // - Finish packet with padding. -1428 // - Set the transmit segment size if this is the first datagram. -1429 // - Loop: next iteration will exit the loop if nothing more to send in this -1430 // space. The TransmitBuf will contain a started datagram with space if -1431 // coalescing, or completely filled datagram if not coalescing. -1432 loop { -1433 // Determine if anything can be sent in this packet number space. -1434 let max_packet_size = if transmit.datagram_remaining_mut() > 0 { -1435 // A datagram is started already, we are coalescing another packet into it. -1436 transmit.datagram_remaining_mut() -1437 } else { -1438 // A new datagram needs to be started. -1439 transmit.segment_size() -1440 }; -1441 let can_send = -1442 self.space_can_send(space_id, path_id, max_packet_size, connection_close_pending); -1443 let needs_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; -1444 let space_will_send = { -1445 if scheduling_info.is_abandoned { -1446 // If this path is abandoned then we might still have to send -1447 // PATH_ABANDON itself on it if there was no better space -1448 // available. Otherwise we want to send the PATH_ABANDON as permitted by -1449 // may_send_data however. -1450 scheduling_info.may_self_abandon -1451 && self.spaces[space_id] -1452 .pending -1453 .path_abandon -1454 .contains_key(&path_id) -1455 } else if can_send.close && scheduling_info.may_send_close { -1456 // This is the best path to send a CONNECTION_CLOSE on. -1457 true -1458 } else if needs_loss_probe || can_send.space_specific { -1459 // We always send a loss probe or space-specific frames if the path is -1460 // not abandoned. -1461 true -1462 } else { -1463 // Anything else we only send if we're the best path for SpaceKind::Data -1464 // frames. -1465 !can_send.is_empty() && scheduling_info.may_send_data -1466 } -1467 }; -1468 -1469 if !space_will_send { -1470 // Nothing more to send. Previous iterations of this loop may have built -1471 // packets already. -1472 return match last_packet_number { -1473 Some(pn) => PollPathSpaceStatus::WrotePacket { -1474 last_packet_number: pn, -1475 pad_datagram, -1476 }, -1477 None => { -1478 // Only log for spaces which have crypto. -1479 if self.crypto_state.has_keys(space_id.encryption_level()) -1480 || (space_id == SpaceId::Data -1481 && self.crypto_state.has_keys(EncryptionLevel::ZeroRtt)) -1482 { -1483 trace!(?space_id, %path_id, "nothing to send in space"); -1484 } -1485 PollPathSpaceStatus::NothingToSend { -1486 congestion_blocked: false, -1487 } -1488 } -1489 }; -1490 } -1491 -1492 // We want to send on this space, check congestion control if we can. But only -1493 // if we will need to start a new datagram. If we are coalescing into an already -1494 // started datagram we do not need to check congestion control again. -1495 if transmit.datagram_remaining_mut() == 0 { -1496 let congestion_blocked = -1497 self.path_congestion_check(space_id, path_id, transmit, &can_send, now); -1498 if congestion_blocked != PathBlocked::No { -1499 // Previous iterations of this loop may have built packets already. -1500 return match last_packet_number { -1501 Some(pn) => PollPathSpaceStatus::WrotePacket { -1502 last_packet_number: pn, -1503 pad_datagram, -1504 }, -1505 None => { -1506 return PollPathSpaceStatus::NothingToSend { -1507 congestion_blocked: true, -1508 }; -1509 } -1510 }; -1511 } -1512 -1513 // If the datagram is full (or there never was one started), we need to start a -1514 // new one. -1515 if transmit.num_datagrams() >= transmit.max_datagrams().get() { -1516 // No more datagrams allowed. -1517 // Previous iterations of this loop may have built packets already. -1518 return match last_packet_number { -1519 Some(pn) => PollPathSpaceStatus::WrotePacket { -1520 last_packet_number: pn, -1521 pad_datagram, -1522 }, -1523 None => { -1524 return PollPathSpaceStatus::NothingToSend { -1525 congestion_blocked: false, -1526 }; -1527 } -1528 }; -1529 } -1530 -1531 if needs_loss_probe { -1532 // Ensure we have something to send for a tail-loss probe. -1533 let request_immediate_ack = -1534 space_id == SpaceId::Data && self.peer_supports_ack_frequency(); -1535 self.spaces[space_id].queue_tail_loss_probe( -1536 path_id, -1537 request_immediate_ack, -1538 &self.streams, -1539 ); -1540 -1541 self.spaces[space_id].for_path(path_id).loss_probes -= 1; // needs_loss_probe ensures loss_probes > 0 -1542 -1543 // Clamp the datagram to at most the minimum MTU to ensure that loss -1544 // probes can get through and enable recovery even if the path MTU -1545 // has shrank unexpectedly. -1546 transmit.start_new_datagram_with_size(std::cmp::min( -1547 usize::from(INITIAL_MTU), -1548 transmit.segment_size(), -1549 )); -1550 } else { -1551 transmit.start_new_datagram(); -1552 } -1553 trace!(count = transmit.num_datagrams(), "new datagram started"); -1554 -1555 // We started a new datagram, we decide later if it needs padding. -1556 pad_datagram = PadDatagram::No; -1557 } -1558 -1559 // If coalescing another packet into the existing datagram, there should -1560 // still be enough space for a whole packet. -1561 if transmit.datagram_start_offset() < transmit.len() { -1562 debug_assert!(transmit.datagram_remaining_mut() >= MIN_PACKET_SPACE); -1563 } -1564 -1565 // -1566 // From here on, we've determined that a packet will definitely be sent. -1567 // -1568 -1569 if self.crypto_state.has_keys(EncryptionLevel::Initial) -1570 && space_id == SpaceId::Handshake -1571 && self.side.is_client() -1572 { -1573 // A client stops both sending and processing Initial packets when it -1574 // sends its first Handshake packet. -1575 self.discard_space(now, SpaceKind::Initial); -1576 } -1577 if let Some(ref mut prev) = self.crypto_state.prev_crypto { -1578 prev.update_unacked = false; -1579 } -1580 -1581 let Some(mut builder) = -1582 PacketBuilder::new(now, space_id, path_id, remote_cid, transmit, self) -1583 else { -1584 // Confidentiality limit is exceeded and the connection has been killed. We -1585 // should not send any other packets. This works in a roundabout way: We -1586 // have started a datagram but not written anything into it. So even if we -1587 // get called again for another space we will see an already started -1588 // datagram and try and start another packet here. Then be stopped by the -1589 // same confidentiality limit. -1590 return PollPathSpaceStatus::NothingToSend { -1591 congestion_blocked: false, -1592 }; -1593 }; -1594 last_packet_number = Some(builder.packet_number); -1595 -1596 if space_id == SpaceId::Initial -1597 && (self.side.is_client() || can_send.is_ack_eliciting()) -1598 { -1599 // https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1 -1600 pad_datagram |= PadDatagram::ToMinMtu; -1601 } -1602 if space_id == SpaceId::Data && self.config.pad_to_mtu { -1603 pad_datagram |= PadDatagram::ToSegmentSize; -1604 } -1605 -1606 if scheduling_info.may_send_close && can_send.close { -1607 trace!("sending CONNECTION_CLOSE"); -1608 // Encode ACKs before the ConnectionClose message, to give the receiver -1609 // a better approximate on what data has been processed. This is -1610 // especially important with ack delay, since the peer might not -1611 // have gotten any other ACK for the data earlier on. -1612 let is_multipath_negotiated = self.is_multipath_negotiated(); -1613 for path_id in self.spaces[space_id] -1614 .number_spaces -1615 .iter() -1616 .filter(|(_, pns)| !pns.pending_acks.ranges().is_empty()) -1617 .map(|(&path_id, _)| path_id) -1618 .collect::<Vec<_>>() -1619 { -1620 Self::populate_acks( -1621 now, -1622 self.receiving_ecn, -1623 path_id, -1624 space_id, -1625 &mut self.spaces[space_id], -1626 is_multipath_negotiated, -1627 &mut builder, -1628 &mut self.path_stats.for_path(path_id).frame_tx, -1629 self.crypto_state.has_keys(space_id.encryption_level()), -1630 ); -1631 } -1632 -1633 // Since there only 64 ACK frames there will always be enough space -1634 // to encode the ConnectionClose frame too. However we still have the -1635 // check here to prevent crashes if something changes. -1636 -1637 // TODO(flub): This needs fixing for multipath, to ensure we can always -1638 // write the CONNECTION_CLOSE even if we have many PATH_ACKs to send: -1639 // https://github.com/n0-computer/noq/issues/367. -1640 debug_assert!( -1641 builder.frame_space_remaining() > frame::ConnectionClose::SIZE_BOUND, -1642 "ACKs should leave space for ConnectionClose" -1643 ); -1644 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -1645 if frame::ConnectionClose::SIZE_BOUND < builder.frame_space_remaining() { -1646 let max_frame_size = builder.frame_space_remaining(); -1647 let close: Close = match self.state.as_type() { -1648 StateType::Closed => { -1649 let reason: Close = -1650 self.state.as_closed().expect("checked").clone().into(); -1651 if space_id == SpaceId::Data || reason.is_transport_layer() { -1652 reason -1653 } else { -1654 TransportError::APPLICATION_ERROR("").into() -1655 } -1656 } -1657 StateType::Draining => TransportError::NO_ERROR("").into(), -1658 _ => unreachable!( -1659 "tried to make a close packet when the connection wasn't closed" -1660 ), -1661 }; -1662 builder.write_frame(close.encoder(max_frame_size), stats); -1663 } -1664 let last_pn = builder.packet_number; -1665 builder.finish_and_track(now, self, path_id, pad_datagram); -1666 if space_id.kind() == self.highest_space { -1667 // Don't send another close packet. Even with multipath we only send -1668 // CONNECTION_CLOSE on a single path since we expect our paths to work. -1669 self.connection_close_pending = false; -1670 } -1671 // Send a close frame in every possible space for robustness, per -1672 // RFC9000 "Immediate Close during the Handshake". Don't bother trying -1673 // to send anything else. -1674 // TODO(flub): This breaks during the handshake if we can not coalesce -1675 // packets due to space reasons: the next space would either fail a -1676 // debug_assert checking for enough packet space or produce an invalid -1677 // packet. We need to keep track of per-space pending CONNECTION_CLOSE to -1678 // be able to send these across multiple calls to poll_transmit. Then -1679 // check for coalescing space here because initial packets need to be in -1680 // padded datagrams. And also add space checks for CONNECTION_CLOSE in -1681 // space_can_send so it would stop a GSO batch if the datagram is too -1682 // small for another CONNECTION_CLOSE packet. -1683 return PollPathSpaceStatus::WrotePacket { -1684 last_packet_number: last_pn, -1685 pad_datagram, -1686 }; -1687 } -1688 -1689 self.populate_packet(now, space_id, path_id, scheduling_info, &mut builder); -1690 -1691 // ACK-only packets should only be sent when explicitly allowed. If we write them due to -1692 // any other reason, there is a bug which leads to one component announcing write -1693 // readiness while not writing any data. This degrades performance. The condition is -1694 // only checked if the full MTU is available and when potentially large fixed-size -1695 // frames aren't queued, so that lack of space in the datagram isn't the reason for just -1696 // writing ACKs. -1697 debug_assert!( -1698 !(builder.sent_frames().is_ack_only(&self.streams) -1699 && !can_send.acks -1700 && (can_send.other || can_send.space_specific) -1701 && builder.buf.segment_size() -1702 == self.path_data(path_id).current_mtu() as usize -1703 && self.datagrams.outgoing.is_empty()), -1704 "SendableFrames was {can_send:?}, but only ACKs have been written" -1705 ); -1706 if builder.sent_frames().requires_padding { -1707 pad_datagram |= PadDatagram::ToMinMtu; -1708 } -1709 -1710 for (path_id, _pn) in builder.sent_frames().largest_acked.iter() { -1711 self.spaces[space_id] -1712 .for_path(*path_id) -1713 .pending_acks -1714 .acks_sent(); -1715 self.timers.stop( -1716 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -1717 self.qlog.with_time(now), -1718 ); -1719 } -1720 -1721 // Now we need to finish the packet. Before we do so we need to know if we will -1722 // be coalescing the next packet into this one, or will be ending the datagram -1723 // as well. Because if this is the last packet in the datagram more padding -1724 // might be needed because of the packet type, or to fill the GSO segment size. -1725 -1726 // Are we allowed to coalesce AND is there enough space for another *packet* in -1727 // this datagram AND will we definitely send another packet? -1728 if builder.can_coalesce && path_id == PathId::ZERO && { -1729 let max_packet_size = builder -1730 .buf -1731 .datagram_remaining_mut() -1732 .saturating_sub(builder.predict_packet_end()); -1733 max_packet_size > MIN_PACKET_SPACE -1734 && self.has_pending_packet(space_id, max_packet_size, connection_close_pending) -1735 } { -1736 // We can append/coalesce the next packet into the current -1737 // datagram. Finish the current packet without adding extra padding. -1738 trace!("will coalesce with next packet"); -1739 builder.finish_and_track(now, self, path_id, PadDatagram::No); -1740 } else { -1741 // We need a new datagram for the next packet. Finish the current -1742 // packet with padding. -1743 // TODO(flub): if there isn't any more data to be sent, this will still pad -1744 // to the segment size and only discover there is nothing to send before -1745 // starting the next packet. That is wasting up to 32 bytes. -1746 if builder.buf.num_datagrams() > 1 && matches!(pad_datagram, PadDatagram::No) { -1747 // If too many padding bytes would be required to continue the -1748 // GSO batch after this packet, end the GSO batch here. Ensures -1749 // that fixed-size frames with heterogeneous sizes -1750 // (e.g. application datagrams) won't inadvertently waste large -1751 // amounts of bandwidth. The exact threshold is a bit arbitrary -1752 // and might benefit from further tuning, though there's no -1753 // universally optimal value. -1754 const MAX_PADDING: usize = 32; -1755 if builder.buf.datagram_remaining_mut() -1756 > builder.predict_packet_end() + MAX_PADDING -1757 { -1758 trace!( -1759 "GSO truncated by demand for {} padding bytes", -1760 builder.buf.datagram_remaining_mut() - builder.predict_packet_end() -1761 ); -1762 let last_pn = builder.packet_number; -1763 builder.finish_and_track(now, self, path_id, PadDatagram::No); -1764 return PollPathSpaceStatus::Send { -1765 last_packet_number: last_pn, -1766 }; -1767 } -1768 -1769 // Pad the current datagram to GSO segment size so it can be -1770 // included in the GSO batch. -1771 builder.finish_and_track(now, self, path_id, PadDatagram::ToSegmentSize); -1772 } else { -1773 builder.finish_and_track(now, self, path_id, pad_datagram); -1774 } -1775 -1776 // If this is the first datagram we set the segment size to the size of the -1777 // first datagram. -1778 if transmit.num_datagrams() == 1 { -1779 transmit.clip_segment_size(); -1780 } -1781 } -1782 } -1783 } +1318 // The packet number of the last built packet. This is kept kept across spaces. +1319 // QUIC is supposed to have a single congestion controller for the Initial, +1320 // Handshake and Data(PathId::ZERO) spaces. +1321 let mut last_packet_number = None; +1322 +1323 // If we end up not sending anything, we need to know if that was because there was +1324 // nothing to send or because we were congestion blocked. +1325 let mut congestion_blocked = false; +1326 +1327 // Set the segment size to this path's MTU for on-path data. +1328 let pmtu = self.path_data(path_id).current_mtu().into(); +1329 let mut transmit = TransmitBuf::new(buf, max_datagrams, pmtu); +1330 +1331 // Iterate over the available spaces. +1332 for space_id in SpaceId::iter() { +1333 // Only PathId::ZERO uses non Data space ids. +1334 if path_id != PathId::ZERO && space_id != SpaceId::Data { +1335 continue; +1336 } +1337 match self.poll_transmit_path_space( +1338 now, +1339 &mut transmit, +1340 path_id, +1341 space_id, +1342 remote_cid, +1343 scheduling_info, +1344 connection_close_pending, +1345 pad_datagram, +1346 ) { +1347 PollPathSpaceStatus::NothingToSend { +1348 congestion_blocked: cb, +1349 } => { +1350 congestion_blocked |= cb; +1351 // Continue checking other spaces, tail-loss probes may need to be sent +1352 // in all spaces. +1353 } +1354 PollPathSpaceStatus::WrotePacket { +1355 last_packet_number: pn, +1356 pad_datagram: pad, +1357 } => { +1358 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); +1359 last_packet_number = Some(pn); +1360 pad_datagram = pad; +1361 // Always check higher spaces. If the transmit is full or they have +1362 // nothing to send they will not write packets. But if they can, they +1363 // must always be allowed to add to this transmit because coalescing may +1364 // be required. +1365 continue; +1366 } +1367 PollPathSpaceStatus::Send { +1368 last_packet_number: pn, +1369 } => { +1370 debug_assert!(!transmit.is_empty(), "transmit must contain packets"); +1371 last_packet_number = Some(pn); +1372 break; +1373 } +1374 } +1375 } +1376 +1377 if last_packet_number.is_some() || congestion_blocked { +1378 self.qlog.emit_recovery_metrics( +1379 path_id, +1380 &mut self +1381 .paths +1382 .get_mut(&path_id) +1383 .expect("path_id was iterated from self.paths above") +1384 .data, +1385 now, +1386 ); +1387 } +1388 +1389 self.path_data_mut(path_id).app_limited = +1390 last_packet_number.is_none() && !congestion_blocked; +1391 +1392 match last_packet_number { +1393 Some(last_packet_number) => { +1394 // Note that when sending in multiple spaces the last packet number will be +1395 // the one from the highest space. +1396 self.path_data_mut(path_id).congestion.on_sent( +1397 now, +1398 transmit.len() as u64, +1399 last_packet_number, +1400 ); +1401 Some(self.build_transmit(path_id, transmit)) +1402 } +1403 None => None, +1404 } +1405 } +1406 +1407 /// poll_transmit logic for a QUIC-MULTIPATH packet number space (PathID + SpaceId). +1408 #[must_use] +1409 fn poll_transmit_path_space( +1410 &mut self, +1411 now: Instant, +1412 transmit: &mut TransmitBuf<'_>, +1413 path_id: PathId, +1414 space_id: SpaceId, +1415 remote_cid: ConnectionId, +1416 scheduling_info: &PathSchedulingInfo, +1417 // If we need to send a CONNECTION_CLOSE frame. +1418 connection_close_pending: bool, +1419 // Whether the current datagram needs to be padded to a certain size. +1420 mut pad_datagram: PadDatagram, +1421 ) -> PollPathSpaceStatus { +1422 // Keep track of the last packet number we wrote. If None we did not write any +1423 // packets. +1424 let mut last_packet_number = None; +1425 +1426 // Each loop of this may build one packet. It works logically as follows: +1427 // +1428 // - Check if something *needs* to be sent in this space and *can* be sent. +1429 // - If not, return to the caller who will call us again for the next space. +1430 // - Start a new datagram. +1431 // - Unless coalescing the packet into an existing datagram. +1432 // - Write the packet header and payload. +1433 // - Check if coalescing a next packet into the datagram is possible. +1434 // - If coalescing, finish packet without padding to leave space in the datagram. +1435 // - If not coalescing, complete the datagram: +1436 // - Finish packet with padding. +1437 // - Set the transmit segment size if this is the first datagram. +1438 // - Loop: next iteration will exit the loop if nothing more to send in this +1439 // space. The TransmitBuf will contain a started datagram with space if +1440 // coalescing, or completely filled datagram if not coalescing. +1441 loop { +1442 // Determine if anything can be sent in this packet number space. +1443 let max_packet_size = if transmit.datagram_remaining_mut() > 0 { +1444 // A datagram is started already, we are coalescing another packet into it. +1445 transmit.datagram_remaining_mut() +1446 } else { +1447 // A new datagram needs to be started. +1448 transmit.segment_size() +1449 }; +1450 let can_send = +1451 self.space_can_send(space_id, path_id, max_packet_size, connection_close_pending); +1452 let needs_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; +1453 let space_will_send = { +1454 if scheduling_info.is_abandoned { +1455 // If this path is abandoned then we might still have to send +1456 // PATH_ABANDON itself on it if there was no better space +1457 // available. Otherwise we want to send the PATH_ABANDON as permitted by +1458 // may_send_data however. +1459 scheduling_info.may_self_abandon +1460 && self.spaces[space_id] +1461 .pending +1462 .path_abandon +1463 .contains_key(&path_id) +1464 } else if can_send.close && scheduling_info.may_send_close { +1465 // This is the best path to send a CONNECTION_CLOSE on. +1466 true +1467 } else if needs_loss_probe || can_send.space_specific { +1468 // We always send a loss probe or space-specific frames if the path is +1469 // not abandoned. +1470 true +1471 } else { +1472 // Anything else we only send if we're the best path for SpaceKind::Data +1473 // frames. +1474 !can_send.is_empty() && scheduling_info.may_send_data +1475 } +1476 }; +1477 +1478 if !space_will_send { +1479 // Nothing more to send. Previous iterations of this loop may have built +1480 // packets already. +1481 return match last_packet_number { +1482 Some(pn) => PollPathSpaceStatus::WrotePacket { +1483 last_packet_number: pn, +1484 pad_datagram, +1485 }, +1486 None => { +1487 // Only log for spaces which have crypto. +1488 if self.crypto_state.has_keys(space_id.encryption_level()) +1489 || (space_id == SpaceId::Data +1490 && self.crypto_state.has_keys(EncryptionLevel::ZeroRtt)) +1491 { +1492 trace!(?space_id, %path_id, "nothing to send in space"); +1493 } +1494 PollPathSpaceStatus::NothingToSend { +1495 congestion_blocked: false, +1496 } +1497 } +1498 }; +1499 } +1500 +1501 // We want to send on this space, check congestion control if we can. But only +1502 // if we will need to start a new datagram. If we are coalescing into an already +1503 // started datagram we do not need to check congestion control again. +1504 if transmit.datagram_remaining_mut() == 0 { +1505 let congestion_blocked = +1506 self.path_congestion_check(space_id, path_id, transmit, &can_send, now); +1507 if congestion_blocked != PathBlocked::No { +1508 // Previous iterations of this loop may have built packets already. +1509 return match last_packet_number { +1510 Some(pn) => PollPathSpaceStatus::WrotePacket { +1511 last_packet_number: pn, +1512 pad_datagram, +1513 }, +1514 None => { +1515 return PollPathSpaceStatus::NothingToSend { +1516 congestion_blocked: true, +1517 }; +1518 } +1519 }; +1520 } +1521 +1522 // If the datagram is full (or there never was one started), we need to start a +1523 // new one. +1524 if transmit.num_datagrams() >= transmit.max_datagrams().get() { +1525 // No more datagrams allowed. +1526 // Previous iterations of this loop may have built packets already. +1527 return match last_packet_number { +1528 Some(pn) => PollPathSpaceStatus::WrotePacket { +1529 last_packet_number: pn, +1530 pad_datagram, +1531 }, +1532 None => { +1533 return PollPathSpaceStatus::NothingToSend { +1534 congestion_blocked: false, +1535 }; +1536 } +1537 }; +1538 } +1539 +1540 if needs_loss_probe { +1541 // Ensure we have something to send for a tail-loss probe. +1542 let request_immediate_ack = +1543 space_id == SpaceId::Data && self.peer_supports_ack_frequency(); +1544 self.spaces[space_id].queue_tail_loss_probe( +1545 path_id, +1546 request_immediate_ack, +1547 &self.streams, +1548 ); +1549 +1550 self.spaces[space_id].for_path(path_id).loss_probes -= 1; // needs_loss_probe ensures loss_probes > 0 +1551 +1552 // Clamp the datagram to at most the minimum MTU to ensure that loss +1553 // probes can get through and enable recovery even if the path MTU +1554 // has shrank unexpectedly. +1555 transmit.start_new_datagram_with_size(std::cmp::min( +1556 usize::from(INITIAL_MTU), +1557 transmit.segment_size(), +1558 )); +1559 } else { +1560 transmit.start_new_datagram(); +1561 } +1562 trace!(count = transmit.num_datagrams(), "new datagram started"); +1563 +1564 // We started a new datagram, we decide later if it needs padding. +1565 pad_datagram = PadDatagram::No; +1566 } +1567 +1568 // If coalescing another packet into the existing datagram, there should +1569 // still be enough space for a whole packet. +1570 if transmit.datagram_start_offset() < transmit.len() { +1571 debug_assert!(transmit.datagram_remaining_mut() >= MIN_PACKET_SPACE); +1572 } +1573 +1574 // +1575 // From here on, we've determined that a packet will definitely be sent. +1576 // +1577 +1578 if self.crypto_state.has_keys(EncryptionLevel::Initial) +1579 && space_id == SpaceId::Handshake +1580 && self.side.is_client() +1581 { +1582 // A client stops both sending and processing Initial packets when it +1583 // sends its first Handshake packet. +1584 self.discard_space(now, SpaceKind::Initial); +1585 } +1586 if let Some(ref mut prev) = self.crypto_state.prev_crypto { +1587 prev.update_unacked = false; +1588 } +1589 +1590 let Some(mut builder) = +1591 PacketBuilder::new(now, space_id, path_id, remote_cid, transmit, self) +1592 else { +1593 // Confidentiality limit is exceeded and the connection has been killed. We +1594 // should not send any other packets. This works in a roundabout way: We +1595 // have started a datagram but not written anything into it. So even if we +1596 // get called again for another space we will see an already started +1597 // datagram and try and start another packet here. Then be stopped by the +1598 // same confidentiality limit. +1599 return PollPathSpaceStatus::NothingToSend { +1600 congestion_blocked: false, +1601 }; +1602 }; +1603 last_packet_number = Some(builder.packet_number); +1604 +1605 if space_id == SpaceId::Initial +1606 && (self.side.is_client() || can_send.is_ack_eliciting() || needs_loss_probe) +1607 { +1608 // https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1 +1609 pad_datagram |= PadDatagram::ToMinMtu; +1610 } +1611 if space_id == SpaceId::Data && self.config.pad_to_mtu { +1612 pad_datagram |= PadDatagram::ToSegmentSize; +1613 } +1614 +1615 if scheduling_info.may_send_close && can_send.close { +1616 trace!("sending CONNECTION_CLOSE"); +1617 // Encode ACKs before the ConnectionClose message, to give the receiver +1618 // a better approximate on what data has been processed. This is +1619 // especially important with ack delay, since the peer might not +1620 // have gotten any other ACK for the data earlier on. +1621 let is_multipath_negotiated = self.is_multipath_negotiated(); +1622 for path_id in self.spaces[space_id] +1623 .number_spaces +1624 .iter() +1625 .filter(|(_, pns)| !pns.pending_acks.ranges().is_empty()) +1626 .map(|(&path_id, _)| path_id) +1627 .collect::<Vec<_>>() +1628 { +1629 Self::populate_acks( +1630 now, +1631 self.receiving_ecn, +1632 path_id, +1633 space_id, +1634 &mut self.spaces[space_id], +1635 is_multipath_negotiated, +1636 &mut builder, +1637 &mut self.path_stats.get_mut(path_id).frame_tx, +1638 self.crypto_state.has_keys(space_id.encryption_level()), +1639 ); +1640 } +1641 +1642 // Since there only 64 ACK frames there will always be enough space +1643 // to encode the ConnectionClose frame too. However we still have the +1644 // check here to prevent crashes if something changes. +1645 +1646 // TODO(flub): This needs fixing for multipath, to ensure we can always +1647 // write the CONNECTION_CLOSE even if we have many PATH_ACKs to send: +1648 // https://github.com/n0-computer/noq/issues/367. +1649 debug_assert!( +1650 builder.frame_space_remaining() > frame::ConnectionClose::SIZE_BOUND, +1651 "ACKs should leave space for ConnectionClose" +1652 ); +1653 let stats = &mut self.path_stats.get_mut(path_id).frame_tx; +1654 if frame::ConnectionClose::SIZE_BOUND < builder.frame_space_remaining() { +1655 let max_frame_size = builder.frame_space_remaining(); +1656 let close: Close = match self.state.as_type() { +1657 StateType::Closed => { +1658 let reason: Close = +1659 self.state.as_closed().expect("checked").clone().into(); +1660 if space_id == SpaceId::Data || reason.is_transport_layer() { +1661 reason +1662 } else { +1663 TransportError::APPLICATION_ERROR("").into() +1664 } +1665 } +1666 StateType::Draining => TransportError::NO_ERROR("").into(), +1667 _ => unreachable!( +1668 "tried to make a close packet when the connection wasn't closed" +1669 ), +1670 }; +1671 builder.write_frame(close.encoder(max_frame_size), stats); +1672 } +1673 let last_pn = builder.packet_number; +1674 builder.finish_and_track(now, self, path_id, pad_datagram); +1675 if space_id.kind() == self.highest_space { +1676 // Don't send another close packet. Even with multipath we only send +1677 // CONNECTION_CLOSE on a single path since we expect our paths to work. +1678 self.connection_close_pending = false; +1679 } +1680 // Send a close frame in every possible space for robustness, per +1681 // RFC9000 "Immediate Close during the Handshake". Don't bother trying +1682 // to send anything else. +1683 // TODO(flub): This breaks during the handshake if we can not coalesce +1684 // packets due to space reasons: the next space would either fail a +1685 // debug_assert checking for enough packet space or produce an invalid +1686 // packet. We need to keep track of per-space pending CONNECTION_CLOSE to +1687 // be able to send these across multiple calls to poll_transmit. Then +1688 // check for coalescing space here because initial packets need to be in +1689 // padded datagrams. And also add space checks for CONNECTION_CLOSE in +1690 // space_can_send so it would stop a GSO batch if the datagram is too +1691 // small for another CONNECTION_CLOSE packet. +1692 return PollPathSpaceStatus::WrotePacket { +1693 last_packet_number: last_pn, +1694 pad_datagram, +1695 }; +1696 } +1697 +1698 self.populate_packet(now, space_id, path_id, scheduling_info, &mut builder); +1699 +1700 // ACK-only packets should only be sent when explicitly allowed. If we write them due to +1701 // any other reason, there is a bug which leads to one component announcing write +1702 // readiness while not writing any data. This degrades performance. The condition is +1703 // only checked if the full MTU is available and when potentially large fixed-size +1704 // frames aren't queued, so that lack of space in the datagram isn't the reason for just +1705 // writing ACKs. +1706 debug_assert!( +1707 !(builder.sent_frames().is_ack_only(&self.streams) +1708 && !can_send.acks +1709 && (can_send.other || can_send.space_specific) +1710 && builder.buf.segment_size() +1711 == self.path_data(path_id).current_mtu() as usize +1712 && self.datagrams.outgoing.is_empty()), +1713 "SendableFrames was {can_send:?}, but only ACKs have been written" +1714 ); +1715 if builder.sent_frames().requires_padding { +1716 pad_datagram |= PadDatagram::ToMinMtu; +1717 } +1718 +1719 for (path_id, _pn) in builder.sent_frames().largest_acked.iter() { +1720 self.spaces[space_id] +1721 .for_path(*path_id) +1722 .pending_acks +1723 .acks_sent(); +1724 self.timers.stop( +1725 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +1726 self.qlog.with_time(now), +1727 ); +1728 } +1729 +1730 // Now we need to finish the packet. Before we do so we need to know if we will +1731 // be coalescing the next packet into this one, or will be ending the datagram +1732 // as well. Because if this is the last packet in the datagram more padding +1733 // might be needed because of the packet type, or to fill the GSO segment size. +1734 +1735 // Are we allowed to coalesce AND is there enough space for another *packet* in +1736 // this datagram AND will we definitely send another packet? +1737 if builder.can_coalesce && path_id == PathId::ZERO && { +1738 let max_packet_size = builder +1739 .buf +1740 .datagram_remaining_mut() +1741 .saturating_sub(builder.predict_packet_end()); +1742 max_packet_size > MIN_PACKET_SPACE +1743 && self.has_pending_packet(space_id, max_packet_size, connection_close_pending) +1744 } { +1745 // We can append/coalesce the next packet into the current +1746 // datagram. Finish the current packet without adding extra padding. +1747 trace!("will coalesce with next packet"); +1748 builder.finish_and_track(now, self, path_id, PadDatagram::No); +1749 } else { +1750 // We need a new datagram for the next packet. Finish the current +1751 // packet with padding. +1752 // TODO(flub): if there isn't any more data to be sent, this will still pad +1753 // to the segment size and only discover there is nothing to send before +1754 // starting the next packet. That is wasting up to 32 bytes. +1755 if builder.buf.num_datagrams() > 1 && matches!(pad_datagram, PadDatagram::No) { +1756 // If too many padding bytes would be required to continue the +1757 // GSO batch after this packet, end the GSO batch here. Ensures +1758 // that fixed-size frames with heterogeneous sizes +1759 // (e.g. application datagrams) won't inadvertently waste large +1760 // amounts of bandwidth. The exact threshold is a bit arbitrary +1761 // and might benefit from further tuning, though there's no +1762 // universally optimal value. +1763 const MAX_PADDING: usize = 32; +1764 if builder.buf.datagram_remaining_mut() +1765 > builder.predict_packet_end() + MAX_PADDING +1766 { +1767 trace!( +1768 "GSO truncated by demand for {} padding bytes", +1769 builder.buf.datagram_remaining_mut() - builder.predict_packet_end() +1770 ); +1771 let last_pn = builder.packet_number; +1772 builder.finish_and_track(now, self, path_id, PadDatagram::No); +1773 return PollPathSpaceStatus::Send { +1774 last_packet_number: last_pn, +1775 }; +1776 } +1777 +1778 // Pad the current datagram to GSO segment size so it can be +1779 // included in the GSO batch. +1780 builder.finish_and_track(now, self, path_id, PadDatagram::ToSegmentSize); +1781 } else { +1782 builder.finish_and_track(now, self, path_id, pad_datagram); +1783 } 1784 -1785 fn poll_transmit_mtu_probe( -1786 &mut self, -1787 now: Instant, -1788 buf: &mut Vec<u8>, -1789 path_id: PathId, -1790 ) -> Option<Transmit> { -1791 let (active_cid, probe_size) = self.get_mtu_probe_data(now, path_id)?; -1792 -1793 // We are definitely sending a DPLPMTUD probe. -1794 let mut transmit = TransmitBuf::new(buf, NonZeroUsize::MIN, probe_size as usize); -1795 transmit.start_new_datagram_with_size(probe_size as usize); -1796 -1797 let mut builder = -1798 PacketBuilder::new(now, SpaceId::Data, path_id, active_cid, &mut transmit, self)?; -1799 -1800 // We implement MTU probes as ping packets padded up to the probe size -1801 trace!(?probe_size, "writing MTUD probe"); -1802 builder.write_frame(frame::Ping, &mut self.path_stats.for_path(path_id).frame_tx); -1803 -1804 // If supported by the peer, we want no delays to the probe's ACK -1805 if self.peer_supports_ack_frequency() { -1806 builder.write_frame( -1807 frame::ImmediateAck, -1808 &mut self.path_stats.for_path(path_id).frame_tx, -1809 ); -1810 } -1811 -1812 builder.finish_and_track(now, self, path_id, PadDatagram::ToSize(probe_size)); -1813 -1814 self.path_stats.for_path(path_id).sent_plpmtud_probes += 1; -1815 -1816 Some(self.build_transmit(path_id, transmit)) -1817 } -1818 -1819 /// Returns the CID and probe size if a DPLPMTUD probe is needed. -1820 /// -1821 /// We MTU probe all paths for which all of the following is true: -1822 /// - We have an active destination CID for the path. -1823 /// - The remote address *and* path are validated. -1824 /// - The path is not abandoned. -1825 /// - The MTU Discovery subsystem wants to probe the path. -1826 fn get_mtu_probe_data(&mut self, now: Instant, path_id: PathId) -> Option<(ConnectionId, u16)> { -1827 let active_cid = self.remote_cids.get(&path_id).map(CidQueue::active)?; -1828 let is_eligible = self.path_data(path_id).validated -1829 && !self.path_data(path_id).is_validating_path() -1830 && !self.abandoned_paths.contains(&path_id); -1831 -1832 if !is_eligible { -1833 return None; -1834 } -1835 let next_pn = self.spaces[SpaceId::Data] -1836 .for_path(path_id) -1837 .peek_tx_number(); -1838 let probe_size = self -1839 .path_data_mut(path_id) -1840 .mtud -1841 .poll_transmit(now, next_pn)?; -1842 -1843 Some((active_cid, probe_size)) -1844 } -1845 -1846 /// Returns true if there is a further packet to send on [`PathId::ZERO`]. -1847 /// -1848 /// In other words this is predicting whether the next call to -1849 /// [`Connection::space_can_send`] issued will return some frames to be sent. Including -1850 /// having to predict which packet number space it will be invoked with. This depends on -1851 /// how both [`Connection::poll_transmit_on_path`] and -1852 /// [`Connection::poll_transmit_path_space`] behave. -1853 /// -1854 /// This is needed to determine if packet coalescing can happen. Because the last packet -1855 /// in a datagram may need to be padded and thus we must know if another packet will -1856 /// follow or not. -1857 /// -1858 /// The next packet can be either in the same space, or in one of the following spaces -1859 /// on the same path. Because a 0-RTT packet can be coalesced with a 1-RTT packet and -1860 /// both are in the Data(PathId::ZERO) space. Previous spaces are not checked, because -1861 /// packets are built from Initial to Handshake to Data spaces. -1862 fn has_pending_packet( -1863 &mut self, -1864 current_space_id: SpaceId, -1865 max_packet_size: usize, -1866 connection_close_pending: bool, -1867 ) -> bool { -1868 let mut space_id = current_space_id; -1869 loop { -1870 let can_send = self.space_can_send( -1871 space_id, -1872 PathId::ZERO, -1873 max_packet_size, -1874 connection_close_pending, -1875 ); -1876 if !can_send.is_empty() { -1877 return true; -1878 } -1879 match space_id.next() { -1880 Some(next_space_id) => space_id = next_space_id, -1881 None => break, -1882 } -1883 } -1884 false -1885 } -1886 -1887 /// Checks if creating a new datagram would be blocked by congestion control -1888 fn path_congestion_check( -1889 &mut self, -1890 space_id: SpaceId, -1891 path_id: PathId, -1892 transmit: &TransmitBuf<'_>, -1893 can_send: &SendableFrames, -1894 now: Instant, -1895 ) -> PathBlocked { -1896 // Anti-amplification is only based on `total_sent`, which gets updated after -1897 // the transmit is sent. Therefore we pass the amount of bytes for datagrams -1898 // that are already created, as well as 1 byte for starting another datagram. If -1899 // there is any anti-amplification budget left, we always allow a full MTU to be -1900 // sent (see https://github.com/quinn-rs/quinn/issues/1082). -1901 if self.side().is_server() -1902 && self -1903 .path_data(path_id) -1904 .anti_amplification_blocked(transmit.len() as u64 + 1) -1905 { -1906 trace!(?space_id, %path_id, "blocked by anti-amplification"); -1907 return PathBlocked::AntiAmplification; -1908 } -1909 -1910 // Congestion control check. -1911 // Tail loss probes must not be blocked by congestion, or a deadlock could arise. -1912 let bytes_to_send = transmit.segment_size() as u64; -1913 let need_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; -1914 -1915 if can_send.other && !need_loss_probe && !can_send.close { -1916 let path = self.path_data(path_id); -1917 if path.in_flight.bytes + bytes_to_send >= path.congestion.window() { -1918 trace!( -1919 ?space_id, -1920 %path_id, -1921 in_flight=%path.in_flight.bytes, -1922 congestion_window=%path.congestion.window(), -1923 "blocked by congestion control", -1924 ); -1925 return PathBlocked::Congestion; -1926 } -1927 } -1928 -1929 // Pacing check. -1930 if let Some(delay) = self.path_data_mut(path_id).pacing_delay(bytes_to_send, now) { -1931 let resume_time = now + delay; -1932 self.timers.set( -1933 Timer::PerPath(path_id, PathTimer::Pacing), -1934 resume_time, -1935 self.qlog.with_time(now), -1936 ); -1937 // Loss probes and CONNECTION_CLOSE should be subject to pacing, even though -1938 // they are not congestion controlled. -1939 trace!(?space_id, %path_id, ?delay, "blocked by pacing"); -1940 return PathBlocked::Pacing; -1941 } -1942 -1943 PathBlocked::No -1944 } -1945 -1946 /// Send PATH_CHALLENGE for a previous path if necessary -1947 /// -1948 /// QUIC-TRANSPORT section 9.3.3 -1949 /// <https://www.rfc-editor.org/rfc/rfc9000.html#name-off-path-packet-forwarding> -1950 fn send_prev_path_challenge( -1951 &mut self, -1952 now: Instant, -1953 buf: &mut Vec<u8>, -1954 path_id: PathId, -1955 ) -> Option<Transmit> { -1956 let (prev_cid, prev_path) = self.paths.get_mut(&path_id)?.prev.as_mut()?; -1957 if !prev_path.pending_on_path_challenge { -1958 return None; -1959 }; -1960 prev_path.pending_on_path_challenge = false; -1961 let token = self.rng.random(); -1962 let network_path = prev_path.network_path; -1963 prev_path.record_path_challenge_sent(now, token, network_path); -1964 -1965 debug_assert_eq!( -1966 self.highest_space, -1967 SpaceKind::Data, -1968 "PATH_CHALLENGE queued without 1-RTT keys" -1969 ); -1970 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -1971 buf.start_new_datagram(); -1972 -1973 // Use the previous CID to avoid linking the new path with the previous path. We -1974 // don't bother accounting for possible retirement of that prev_cid because this is -1975 // sent once, immediately after migration, when the CID is known to be valid. Even -1976 // if a post-migration packet caused the CID to be retired, it's fair to pretend -1977 // this is sent first. -1978 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, *prev_cid, buf, self)?; -1979 let challenge = frame::PathChallenge(token); -1980 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -1981 builder.write_frame_with_log_msg(challenge, stats, Some("validating previous path")); -1982 -1983 // An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame -1984 // to at least the smallest allowed maximum datagram size of 1200 bytes, -1985 // unless the anti-amplification limit for the path does not permit -1986 // sending a datagram of this size -1987 builder.pad_to(MIN_INITIAL_SIZE); -1988 -1989 builder.finish(self, now); -1990 self.path_stats -1991 .for_path(path_id) -1992 .udp_tx -1993 .on_sent(1, buf.len()); -1994 -1995 trace!( -1996 dst = ?network_path.remote, -1997 src = ?network_path.local_ip, -1998 len = buf.len(), -1999 "sending prev_path off-path challenge", -2000 ); -2001 Some(Transmit { -2002 destination: network_path.remote, -2003 size: buf.len(), -2004 ecn: None, -2005 segment_size: None, -2006 src_ip: network_path.local_ip, -2007 }) -2008 } -2009 -2010 fn send_off_path_path_response( -2011 &mut self, -2012 now: Instant, -2013 buf: &mut Vec<u8>, -2014 path_id: PathId, -2015 ) -> Option<Transmit> { -2016 let path = self.paths.get_mut(&path_id).map(|state| &mut state.data)?; -2017 let cid_queue = self.remote_cids.get_mut(&path_id)?; -2018 let (token, network_path) = path.path_responses.pop_off_path(path.network_path)?; -2019 -2020 // TODO: make off-path probes unlinkable. -2021 let cid = cid_queue.active(); -2022 -2023 let frame = frame::PathResponse(token); -2024 -2025 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -2026 buf.start_new_datagram(); -2027 -2028 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, buf, self)?; -2029 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -2030 builder.write_frame_with_log_msg(frame, stats, Some("(off-path)")); -2031 -2032 // If we are a client doing NAT traversal, always include a PATH_CHALLENGE with any -2033 // off-path PATH_RESPONSE. No need to schedule any retries for this, if NAT -2034 // traversal is taking place then this remote already is being probed with -2035 // retries, this only speeds up a successful traversal. -2036 if self -2037 .find_validated_path_on_network_path(network_path) -2038 .is_none() -2039 && self.n0_nat_traversal.client_side().is_ok() -2040 { -2041 let token = self.rng.random(); -2042 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -2043 builder.write_frame(frame::PathChallenge(token), stats); -2044 let ip_port = (network_path.remote.ip(), network_path.remote.port()); -2045 self.n0_nat_traversal.mark_probe_sent(ip_port, token); -2046 } -2047 -2048 // Off-path: not tracked in congestion control. The packet is sent to a -2049 // different destination than path_id's network path. -2050 builder.pad_to(MIN_INITIAL_SIZE); -2051 builder.finish(self, now); -2052 -2053 let size = buf.len(); -2054 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); -2055 -2056 trace!( -2057 dst = ?network_path.remote, -2058 src = ?network_path.local_ip, -2059 len = buf.len(), -2060 "sending off-path PATH_RESPONSE", -2061 ); -2062 Some(Transmit { -2063 destination: network_path.remote, -2064 size, -2065 ecn: None, -2066 segment_size: None, -2067 src_ip: network_path.local_ip, -2068 }) -2069 } -2070 -2071 /// Send a nat traversal challenge (off-path) on this path if possible. -2072 fn send_nat_traversal_path_challenge( -2073 &mut self, -2074 now: Instant, -2075 buf: &mut Vec<u8>, -2076 path_id: PathId, -2077 ) -> Option<Transmit> { -2078 let remote = self.n0_nat_traversal.next_probe_addr()?; -2079 -2080 if !self.paths.get(&path_id)?.data.validated { -2081 // Path is not usable for probing -2082 return None; -2083 } -2084 -2085 // TODO: Using the active CID here makes the paths linkable. This is a violation of -2086 // RFC9000 but something we want to accept in the short term. Eventually we aim -2087 // to fix up the supply of CIDs sufficiently so that we can keep paths unlinkable -2088 // again. -2089 let Some(cid) = self -2090 .remote_cids -2091 .get(&path_id) -2092 .map(|cid_queue| cid_queue.active()) -2093 else { -2094 trace!(%path_id, "Not sending NAT traversal probe for path with no CIDs"); -2095 return None; -2096 }; -2097 let token = self.rng.random(); -2098 -2099 let frame = frame::PathChallenge(token); +1785 // If this is the first datagram we set the segment size to the size of the +1786 // first datagram. +1787 if transmit.num_datagrams() == 1 { +1788 transmit.clip_segment_size(); +1789 } +1790 } +1791 } +1792 } +1793 +1794 fn poll_transmit_mtu_probe( +1795 &mut self, +1796 now: Instant, +1797 buf: &mut Vec<u8>, +1798 path_id: PathId, +1799 ) -> Option<Transmit> { +1800 let (active_cid, probe_size) = self.get_mtu_probe_data(now, path_id)?; +1801 +1802 // We are definitely sending a DPLPMTUD probe. +1803 let mut transmit = TransmitBuf::new(buf, NonZeroUsize::MIN, probe_size as usize); +1804 transmit.start_new_datagram_with_size(probe_size as usize); +1805 +1806 let mut builder = +1807 PacketBuilder::new(now, SpaceId::Data, path_id, active_cid, &mut transmit, self)?; +1808 +1809 // We implement MTU probes as ping packets padded up to the probe size +1810 trace!(?probe_size, "writing MTUD probe"); +1811 builder.write_frame(frame::Ping, &mut self.path_stats.get_mut(path_id).frame_tx); +1812 +1813 // If supported by the peer, we want no delays to the probe's ACK +1814 if self.peer_supports_ack_frequency() { +1815 builder.write_frame( +1816 frame::ImmediateAck, +1817 &mut self.path_stats.get_mut(path_id).frame_tx, +1818 ); +1819 } +1820 +1821 builder.finish_and_track(now, self, path_id, PadDatagram::ToSize(probe_size)); +1822 +1823 self.path_stats.get_mut(path_id).sent_plpmtud_probes += 1; +1824 +1825 Some(self.build_transmit(path_id, transmit)) +1826 } +1827 +1828 /// Returns the CID and probe size if a DPLPMTUD probe is needed. +1829 /// +1830 /// We MTU probe all paths for which all of the following is true: +1831 /// - We have an active destination CID for the path. +1832 /// - The remote address *and* path are validated. +1833 /// - The path is not abandoned. +1834 /// - The MTU Discovery subsystem wants to probe the path. +1835 fn get_mtu_probe_data(&mut self, now: Instant, path_id: PathId) -> Option<(ConnectionId, u16)> { +1836 let active_cid = self.remote_cids.get(&path_id).map(CidQueue::active)?; +1837 let is_eligible = self.path_data(path_id).validated +1838 && !self.path_data(path_id).is_validating_path() +1839 && !self.abandoned_paths.contains(&path_id); +1840 +1841 if !is_eligible { +1842 return None; +1843 } +1844 let next_pn = self.spaces[SpaceId::Data] +1845 .for_path(path_id) +1846 .peek_tx_number(); +1847 let probe_size = self +1848 .path_data_mut(path_id) +1849 .mtud +1850 .poll_transmit(now, next_pn)?; +1851 +1852 Some((active_cid, probe_size)) +1853 } +1854 +1855 /// Returns true if there is a further packet to send on [`PathId::ZERO`]. +1856 /// +1857 /// In other words this is predicting whether the next call to +1858 /// [`Connection::space_can_send`] issued will return some frames to be sent. Including +1859 /// having to predict which packet number space it will be invoked with. This depends on +1860 /// how both [`Connection::poll_transmit_on_path`] and +1861 /// [`Connection::poll_transmit_path_space`] behave. +1862 /// +1863 /// This is needed to determine if packet coalescing can happen. Because the last packet +1864 /// in a datagram may need to be padded and thus we must know if another packet will +1865 /// follow or not. +1866 /// +1867 /// The next packet can be either in the same space, or in one of the following spaces +1868 /// on the same path. Because a 0-RTT packet can be coalesced with a 1-RTT packet and +1869 /// both are in the Data(PathId::ZERO) space. Previous spaces are not checked, because +1870 /// packets are built from Initial to Handshake to Data spaces. +1871 fn has_pending_packet( +1872 &mut self, +1873 current_space_id: SpaceId, +1874 max_packet_size: usize, +1875 connection_close_pending: bool, +1876 ) -> bool { +1877 let mut space_id = current_space_id; +1878 loop { +1879 let can_send = self.space_can_send( +1880 space_id, +1881 PathId::ZERO, +1882 max_packet_size, +1883 connection_close_pending, +1884 ); +1885 if !can_send.is_empty() { +1886 return true; +1887 } +1888 match space_id.next() { +1889 Some(next_space_id) => space_id = next_space_id, +1890 None => break, +1891 } +1892 } +1893 false +1894 } +1895 +1896 /// Checks if creating a new datagram would be blocked by congestion control +1897 fn path_congestion_check( +1898 &mut self, +1899 space_id: SpaceId, +1900 path_id: PathId, +1901 transmit: &TransmitBuf<'_>, +1902 can_send: &SendableFrames, +1903 now: Instant, +1904 ) -> PathBlocked { +1905 // Anti-amplification is only based on `total_sent`, which gets updated after +1906 // the transmit is sent. Therefore we pass the amount of bytes for datagrams +1907 // that are already created, as well as 1 byte for starting another datagram. If +1908 // there is any anti-amplification budget left, we always allow a full MTU to be +1909 // sent (see https://github.com/quinn-rs/quinn/issues/1082). +1910 if self.side().is_server() +1911 && self +1912 .path_data(path_id) +1913 .anti_amplification_blocked(transmit.len() as u64 + 1) +1914 { +1915 trace!(?space_id, %path_id, "blocked by anti-amplification"); +1916 return PathBlocked::AntiAmplification; +1917 } +1918 +1919 // Congestion control check. +1920 // Tail loss probes must not be blocked by congestion, or a deadlock could arise. +1921 let bytes_to_send = transmit.segment_size() as u64; +1922 let need_loss_probe = self.spaces[space_id].for_path(path_id).loss_probes > 0; +1923 +1924 if can_send.other && !need_loss_probe && !can_send.close { +1925 let path = self.path_data(path_id); +1926 if path.in_flight.bytes + bytes_to_send >= path.congestion.window() { +1927 trace!( +1928 ?space_id, +1929 %path_id, +1930 in_flight=%path.in_flight.bytes, +1931 congestion_window=%path.congestion.window(), +1932 "blocked by congestion control", +1933 ); +1934 return PathBlocked::Congestion; +1935 } +1936 } +1937 +1938 // Pacing check. +1939 if let Some(delay) = self.path_data_mut(path_id).pacing_delay(bytes_to_send, now) { +1940 let resume_time = now + delay; +1941 self.timers.set( +1942 Timer::PerPath(path_id, PathTimer::Pacing), +1943 resume_time, +1944 self.qlog.with_time(now), +1945 ); +1946 // Loss probes and CONNECTION_CLOSE should be subject to pacing, even though +1947 // they are not congestion controlled. +1948 trace!(?space_id, %path_id, ?delay, "blocked by pacing"); +1949 return PathBlocked::Pacing; +1950 } +1951 +1952 PathBlocked::No +1953 } +1954 +1955 /// Send PATH_CHALLENGE for a previous path if necessary +1956 /// +1957 /// QUIC-TRANSPORT section 9.3.3 +1958 /// <https://www.rfc-editor.org/rfc/rfc9000.html#name-off-path-packet-forwarding> +1959 fn send_prev_path_challenge( +1960 &mut self, +1961 now: Instant, +1962 buf: &mut Vec<u8>, +1963 path_id: PathId, +1964 ) -> Option<Transmit> { +1965 let (prev_cid, prev_path) = self.paths.get_mut(&path_id)?.prev.as_mut()?; +1966 if !prev_path.pending_challenge { +1967 return None; +1968 }; +1969 prev_path.pending_challenge = false; +1970 let token = self.rng.random(); +1971 let network_path = prev_path.network_path; +1972 prev_path.record_path_challenge_sent(now, token, network_path); +1973 +1974 debug_assert_eq!( +1975 self.highest_space, +1976 SpaceKind::Data, +1977 "PATH_CHALLENGE queued without 1-RTT keys" +1978 ); +1979 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +1980 buf.start_new_datagram(); +1981 +1982 // Use the previous CID to avoid linking the new path with the previous path. We +1983 // don't bother accounting for possible retirement of that prev_cid because this is +1984 // sent once, immediately after migration, when the CID is known to be valid. Even +1985 // if a post-migration packet caused the CID to be retired, it's fair to pretend +1986 // this is sent first. +1987 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, *prev_cid, buf, self)?; +1988 let challenge = frame::PathChallenge(token); +1989 let stats = &mut self.path_stats.get_mut(path_id).frame_tx; +1990 builder.write_frame_with_log_msg(challenge, stats, Some("validating previous path")); +1991 +1992 // An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame +1993 // to at least the smallest allowed maximum datagram size of 1200 bytes, +1994 // unless the anti-amplification limit for the path does not permit +1995 // sending a datagram of this size +1996 builder.pad_to(MIN_INITIAL_SIZE); +1997 +1998 builder.finish(self, now); +1999 self.path_stats +2000 .get_mut(path_id) +2001 .udp_tx +2002 .on_sent(1, buf.len()); +2003 +2004 trace!( +2005 dst = ?network_path.remote, +2006 src = ?network_path.local_ip, +2007 len = buf.len(), +2008 "sending prev_path off-path challenge", +2009 ); +2010 Some(Transmit { +2011 destination: network_path.remote, +2012 size: buf.len(), +2013 ecn: None, +2014 segment_size: None, +2015 src_ip: network_path.local_ip, +2016 }) +2017 } +2018 +2019 fn send_off_path_path_response( +2020 &mut self, +2021 now: Instant, +2022 buf: &mut Vec<u8>, +2023 path_id: PathId, +2024 ) -> Option<Transmit> { +2025 let network_path = self +2026 .paths +2027 .get_mut(&path_id) +2028 .map(|state| state.data.network_path)?; +2029 let cid_queue = self.remote_cids.get_mut(&path_id)?; +2030 let pns = self.spaces[SpaceKind::Data].for_path(path_id); +2031 let (token, network_path) = pns.pending_path_responses.pop_off_path(network_path)?; +2032 +2033 // TODO: make off-path probes unlinkable. +2034 let cid = cid_queue.active(); +2035 +2036 // PATH_RESPONSE (off-path) +2037 let frame = frame::PathResponse(token); +2038 +2039 let buf = &mut TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +2040 buf.start_new_datagram(); +2041 +2042 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, buf, self)?; +2043 let stats = &mut self.path_stats.get_mut(path_id).frame_tx; +2044 builder.write_frame_with_log_msg(frame, stats, Some("(off-path)")); +2045 +2046 // PATH_CHALLENGE (off-path) +2047 // +2048 // If we are a client doing NAT traversal, always include a PATH_CHALLENGE with any +2049 // off-path PATH_RESPONSE. No need to schedule any retries for this, if NAT +2050 // traversal is taking place then this remote already is being probed with +2051 // retries, this only speeds up a successful traversal. +2052 if self +2053 .find_validated_path_on_network_path(network_path) +2054 .is_none() +2055 && self.n0_nat_traversal.client_side().is_ok() +2056 { +2057 let token = self.rng.random(); +2058 let stats = &mut self.path_stats.get_mut(path_id).frame_tx; +2059 builder.write_frame(frame::PathChallenge(token), stats); +2060 let ip_port = (network_path.remote.ip(), network_path.remote.port()); +2061 self.n0_nat_traversal.mark_probe_sent(ip_port, token); +2062 } +2063 +2064 // Off-path: not tracked in congestion control. The packet is sent to a +2065 // different destination than path_id's network path. +2066 builder.pad_to(MIN_INITIAL_SIZE); +2067 builder.finish(self, now); +2068 +2069 let size = buf.len(); +2070 self.path_stats.get_mut(path_id).udp_tx.on_sent(1, size); +2071 +2072 trace!( +2073 dst = ?network_path.remote, +2074 src = ?network_path.local_ip, +2075 len = buf.len(), +2076 "sending off-path PATH_RESPONSE", +2077 ); +2078 Some(Transmit { +2079 destination: network_path.remote, +2080 size, +2081 ecn: None, +2082 segment_size: None, +2083 src_ip: network_path.local_ip, +2084 }) +2085 } +2086 +2087 /// Send a nat traversal challenge (off-path) on this path if possible. +2088 fn send_nat_traversal_path_challenge( +2089 &mut self, +2090 now: Instant, +2091 buf: &mut Vec<u8>, +2092 path_id: PathId, +2093 ) -> Option<Transmit> { +2094 let remote = self.n0_nat_traversal.next_probe_addr()?; +2095 +2096 if !self.paths.get(&path_id)?.data.validated { +2097 // Path is not usable for probing +2098 return None; +2099 } 2100 -2101 let mut buf = TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); -2102 buf.start_new_datagram(); -2103 -2104 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, &mut buf, self)?; -2105 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -2106 builder.write_frame_with_log_msg(frame, stats, Some("(nat-traversal)")); -2107 // Off-path: not tracked in congestion control. The packet is sent to a -2108 // different destination than path_id's network path. -2109 builder.finish(self, now); -2110 -2111 // Mark as sent after packet build succeeds. -2112 self.n0_nat_traversal.mark_probe_sent(remote, token); -2113 -2114 let size = buf.len(); -2115 self.path_stats.for_path(path_id).udp_tx.on_sent(1, size); -2116 -2117 trace!(dst = ?remote, len = buf.len(), "sending off-path NAT probe"); -2118 Some(Transmit { -2119 destination: remote.into(), -2120 size, -2121 ecn: None, -2122 segment_size: None, -2123 src_ip: None, -2124 }) -2125 } -2126 -2127 /// Indicate what types of frames are ready to send for the given space. -2128 /// -2129 /// Only for on-path data. -2130 /// -2131 /// *packet_size* is the number of bytes available to build the next packet. -2132 /// *connection_close_pending* indicates whether a CONNECTION_CLOSE frame needs to be -2133 /// sent. -2134 fn space_can_send( -2135 &mut self, -2136 space_id: SpaceId, -2137 path_id: PathId, -2138 packet_size: usize, -2139 connection_close_pending: bool, -2140 ) -> SendableFrames { -2141 let space = &mut self.spaces[space_id]; -2142 let space_has_crypto = self.crypto_state.has_keys(space_id.encryption_level()); +2101 // TODO: Using the active CID here makes the paths linkable. This is a violation of +2102 // RFC9000 but something we want to accept in the short term. Eventually we aim +2103 // to fix up the supply of CIDs sufficiently so that we can keep paths unlinkable +2104 // again. +2105 let Some(cid) = self +2106 .remote_cids +2107 .get(&path_id) +2108 .map(|cid_queue| cid_queue.active()) +2109 else { +2110 trace!(%path_id, "Not sending NAT traversal probe for path with no CIDs"); +2111 return None; +2112 }; +2113 let token = self.rng.random(); +2114 +2115 // PATH_CHALLENGE (NAT probe) +2116 let frame = frame::PathChallenge(token); +2117 +2118 let mut buf = TransmitBuf::new(buf, NonZeroUsize::MIN, MIN_INITIAL_SIZE.into()); +2119 buf.start_new_datagram(); +2120 +2121 let mut builder = PacketBuilder::new(now, SpaceId::Data, path_id, cid, &mut buf, self)?; +2122 let stats = &mut self.path_stats.get_mut(path_id).frame_tx; +2123 builder.write_frame_with_log_msg(frame, stats, Some("(nat-traversal)")); +2124 // Off-path: not tracked in congestion control. The packet is sent to a +2125 // different destination than path_id's network path. +2126 builder.finish(self, now); +2127 +2128 // Mark as sent after packet build succeeds. +2129 self.n0_nat_traversal.mark_probe_sent(remote, token); +2130 +2131 let size = buf.len(); +2132 self.path_stats.get_mut(path_id).udp_tx.on_sent(1, size); +2133 +2134 trace!(dst = ?remote, len = buf.len(), "sending off-path NAT probe"); +2135 Some(Transmit { +2136 destination: remote.into(), +2137 size, +2138 ecn: None, +2139 segment_size: None, +2140 src_ip: None, +2141 }) +2142 } 2143 -2144 if !space_has_crypto -2145 && (space_id != SpaceId::Data -2146 || !self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) -2147 || self.side.is_server()) -2148 { -2149 // Nothing to send in this space -2150 return SendableFrames::empty(); -2151 } -2152 -2153 let mut can_send = space.can_send(path_id, &self.streams); -2154 -2155 // Check for 1RTT space. -2156 if space_id == SpaceId::Data { -2157 let pn = space.for_path(path_id).peek_tx_number(); -2158 // Number of bytes available for frames if this is a 1-RTT packet. We're -2159 // guaranteed to be able to send an individual frame at least this large in the -2160 // next 1-RTT packet. This could be generalized to support every space, but it's -2161 // only needed to handle large fixed-size frames, which only exist in 1-RTT -2162 // (application datagrams). -2163 let frame_space_1rtt = -2164 packet_size.saturating_sub(self.predict_1rtt_overhead(pn, path_id)); -2165 can_send |= self.can_send_1rtt(path_id, frame_space_1rtt); -2166 } -2167 -2168 can_send.close = connection_close_pending && space_has_crypto; +2144 /// Indicate what types of frames are ready to send for the given packet number space. +2145 /// +2146 /// Only for on-path data. +2147 /// +2148 /// *packet_size* is the number of bytes available to build the next packet. +2149 /// *connection_close_pending* indicates whether a CONNECTION_CLOSE frame needs to be +2150 /// sent. +2151 fn space_can_send( +2152 &mut self, +2153 space_id: SpaceId, +2154 path_id: PathId, +2155 packet_size: usize, +2156 connection_close_pending: bool, +2157 ) -> SendableFrames { +2158 let space = &mut self.spaces[space_id]; +2159 let space_has_crypto = self.crypto_state.has_keys(space_id.encryption_level()); +2160 +2161 if !space_has_crypto +2162 && (space_id != SpaceId::Data +2163 || !self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) +2164 || self.side.is_server()) +2165 { +2166 // Nothing to send in this space +2167 return SendableFrames::empty(); +2168 } 2169 -2170 can_send -2171 } -2172 -2173 /// Process `ConnectionEvent`s generated by the associated `Endpoint` -2174 /// -2175 /// Will execute protocol logic upon receipt of a connection event, in turn preparing signals -2176 /// (including application `Event`s, `EndpointEvent`s and outgoing datagrams) that should be -2177 /// extracted through the relevant methods. -2178 pub fn handle_event(&mut self, event: ConnectionEvent) { -2179 use ConnectionEventInner::*; -2180 match event.0 { -2181 Datagram(DatagramConnectionEvent { -2182 now, -2183 network_path, -2184 path_id, -2185 ecn, -2186 first_decode, -2187 remaining, -2188 }) => { -2189 let span = trace_span!("pkt", %path_id); -2190 let _guard = span.enter(); -2191 -2192 if self.early_discard_packet(network_path, path_id) { -2193 // A return value of true indicates we should discard this packet. -2194 return; -2195 } -2196 -2197 let was_anti_amplification_blocked = self -2198 .path(path_id) -2199 .map(|path| path.anti_amplification_blocked(1)) -2200 // We never tried to send on an non-existing (new) path so have not been -2201 // anti-amplification blocked for it previously. -2202 .unwrap_or(false); -2203 -2204 let rx = &mut self.path_stats.for_path(path_id).udp_rx; -2205 rx.datagrams += 1; -2206 rx.bytes += first_decode.len() as u64; -2207 let data_len = first_decode.len(); +2170 let mut can_send = space.can_send(path_id, &self.streams); +2171 +2172 // Check for 1RTT space. +2173 if space_id == SpaceId::Data { +2174 let pn = space.for_path(path_id).peek_tx_number(); +2175 // Number of bytes available for frames if this is a 1-RTT packet. We're +2176 // guaranteed to be able to send an individual frame at least this large in the +2177 // next 1-RTT packet. This could be generalized to support every space, but it's +2178 // only needed to handle large fixed-size frames, which only exist in 1-RTT +2179 // (application datagrams). +2180 let frame_space_1rtt = +2181 packet_size.saturating_sub(self.predict_1rtt_overhead(pn, path_id)); +2182 can_send |= self.can_send_1rtt(path_id, frame_space_1rtt); +2183 } +2184 +2185 can_send.close = connection_close_pending && space_has_crypto; +2186 +2187 can_send +2188 } +2189 +2190 /// Process `ConnectionEvent`s generated by the associated `Endpoint` +2191 /// +2192 /// Will execute protocol logic upon receipt of a connection event, in turn preparing signals +2193 /// (including application `Event`s, `EndpointEvent`s and outgoing datagrams) that should be +2194 /// extracted through the relevant methods. +2195 pub fn handle_event(&mut self, event: ConnectionEvent) { +2196 use ConnectionEventInner::*; +2197 match event.0 { +2198 Datagram(DatagramConnectionEvent { +2199 now, +2200 network_path, +2201 path_id, +2202 ecn, +2203 first_decode, +2204 remaining, +2205 }) => { +2206 let span = trace_span!("pkt", %path_id); +2207 let _guard = span.enter(); 2208 -2209 self.handle_decode(now, network_path, path_id, ecn, first_decode); -2210 // The current `path` might have changed inside `handle_decode` since the packet -2211 // could have triggered a migration. The packet might also belong to an unknown -2212 // path and have been rejected. Make sure the data received is accounted for the -2213 // most recent path by accessing `path` after `handle_decode`. -2214 if let Some(path) = self.path_mut(path_id) { -2215 path.inc_total_recvd(data_len as u64); -2216 } -2217 -2218 if let Some(data) = remaining { -2219 self.path_stats.for_path(path_id).udp_rx.bytes += data.len() as u64; -2220 self.handle_coalesced(now, network_path, path_id, ecn, data); -2221 } -2222 -2223 if let Some(path) = self.paths.get_mut(&path_id) { -2224 self.qlog -2225 .emit_recovery_metrics(path_id, &mut path.data, now); -2226 } -2227 -2228 if was_anti_amplification_blocked { -2229 // A prior attempt to set the loss detection timer may have failed due to -2230 // anti-amplification, so ensure it's set now. Prevents a handshake deadlock if -2231 // the server's first flight is lost. -2232 self.set_loss_detection_timer(now, path_id); +2209 if self.early_discard_packet(network_path, path_id) { +2210 // A return value of true indicates we should discard this packet. +2211 return; +2212 } +2213 +2214 let was_anti_amplification_blocked = self +2215 .path(path_id) +2216 .map(|path| path.anti_amplification_blocked(1)) +2217 // We never tried to send on an non-existing (new) path so have not been +2218 // anti-amplification blocked for it previously. +2219 .unwrap_or(false); +2220 +2221 let rx = &mut self.path_stats.get_mut(path_id).udp_rx; +2222 rx.datagrams += 1; +2223 rx.bytes += first_decode.len() as u64; +2224 let data_len = first_decode.len(); +2225 +2226 self.handle_decode(now, network_path, path_id, ecn, first_decode); +2227 // The current `path` might have changed inside `handle_decode` since the packet +2228 // could have triggered a migration. The packet might also belong to an unknown +2229 // path and have been rejected. Make sure the data received is accounted for the +2230 // most recent path by accessing `path` after `handle_decode`. +2231 if let Some(path) = self.path_mut(path_id) { +2232 path.inc_total_recvd(data_len as u64); 2233 } -2234 } -2235 NewIdentifiers(ids, now, cid_len, cid_lifetime) => { -2236 let path_id = ids.first().map(|issued| issued.path_id).unwrap_or_default(); -2237 debug_assert!(ids.iter().all(|issued| issued.path_id == path_id)); -2238 -2239 // Path may have been abandoned while this reply was in flight, -2240 // retire the CIDs instead of queuing them. -2241 if self.abandoned_paths.contains(&path_id) { -2242 if !self.state.is_drained() { -2243 for issued in &ids { -2244 self.endpoint_events -2245 .push_back(EndpointEventInner::RetireConnectionId( -2246 now, -2247 path_id, -2248 issued.sequence, -2249 false, -2250 )); -2251 } -2252 } -2253 return; -2254 } +2234 +2235 if let Some(data) = remaining { +2236 self.path_stats.get_mut(path_id).udp_rx.bytes += data.len() as u64; +2237 self.handle_coalesced(now, network_path, path_id, ecn, data); +2238 } +2239 +2240 if let Some(path) = self.paths.get_mut(&path_id) { +2241 self.qlog +2242 .emit_recovery_metrics(path_id, &mut path.data, now); +2243 } +2244 +2245 if was_anti_amplification_blocked { +2246 // A prior attempt to set the loss detection timer may have failed due to +2247 // anti-amplification, so ensure it's set now. Prevents a handshake deadlock if +2248 // the server's first flight is lost. +2249 self.set_loss_detection_timer(now, path_id); +2250 } +2251 } +2252 NewIdentifiers(ids, now, cid_len, cid_lifetime) => { +2253 let path_id = ids.first().map(|issued| issued.path_id).unwrap_or_default(); +2254 debug_assert!(ids.iter().all(|issued| issued.path_id == path_id)); 2255 -2256 let cid_state = self -2257 .local_cid_state -2258 .entry(path_id) -2259 .or_insert_with(|| CidState::new(cid_len, cid_lifetime, now, 0)); -2260 cid_state.new_cids(&ids, now); -2261 -2262 ids.into_iter().rev().for_each(|frame| { -2263 self.spaces[SpaceId::Data].pending.new_cids.push(frame); -2264 }); -2265 // Always update Timer::PushNewCid -2266 self.reset_cid_retirement(now); -2267 } -2268 } -2269 } -2270 -2271 /// Returns whether a packet can be discarded early. -2272 /// -2273 /// Packets sent on the wrong network path can be entirely ignored, saving further -2274 /// processing. -2275 /// -2276 /// Returns true if a packet coming in for this `path_id` over given `network_path` -2277 /// should be discarded. -2278 fn early_discard_packet(&mut self, network_path: FourTuple, path_id: PathId) -> bool { -2279 if self.is_handshaking() && path_id != PathId::ZERO { -2280 debug!(%network_path, %path_id, "discarding multipath packet during handshake"); -2281 return true; -2282 } -2283 -2284 // TODO(flub): In RFC9000 the server is allowed to send off-path probing packets -2285 // once the client has been probing such a 4-tuple. These probes are currently -2286 // not yet recognised and discarded here. -2287 // See https://github.com/n0-computer/noq/issues/607. -2288 let remote_may_migrate = self.remote_may_migrate(); -2289 -2290 let local_ip_may_migrate = self.local_ip_may_migrate(); -2291 -2292 // If this packet could initiate a migration and we're a client or a server that -2293 // forbids migration, drop the datagram. This could be relaxed to heuristically -2294 // permit NAT-rebinding-like migration. -2295 if let Some(known_path) = self.path_mut(path_id) { -2296 if network_path.remote != known_path.network_path.remote && !remote_may_migrate { -2297 trace!( -2298 %path_id, -2299 %network_path, -2300 %known_path.network_path, -2301 "discarding packet from unrecognized peer" -2302 ); -2303 return true; -2304 } +2256 // Path may have been abandoned while this reply was in flight, +2257 // retire the CIDs instead of queuing them. +2258 if self.abandoned_paths.contains(&path_id) { +2259 if !self.state.is_drained() { +2260 for issued in &ids { +2261 self.endpoint_events +2262 .push_back(EndpointEventInner::RetireConnectionId( +2263 now, +2264 path_id, +2265 issued.sequence, +2266 false, +2267 )); +2268 } +2269 } +2270 return; +2271 } +2272 +2273 let cid_state = self +2274 .local_cid_state +2275 .entry(path_id) +2276 .or_insert_with(|| CidState::new(cid_len, cid_lifetime, now, 0)); +2277 cid_state.new_cids(&ids, now); +2278 +2279 ids.into_iter().rev().for_each(|frame| { +2280 self.spaces[SpaceId::Data].pending.new_cids.push(frame); +2281 }); +2282 // Always update Timer::PushNewCid +2283 self.reset_cid_retirement(now); +2284 } +2285 } +2286 } +2287 +2288 /// Returns whether a packet can be discarded early. +2289 /// +2290 /// Packets sent on the wrong network path can be entirely ignored, saving further +2291 /// processing. +2292 /// +2293 /// Returns true if a packet coming in for this `path_id` over given `network_path` +2294 /// should be discarded. +2295 fn early_discard_packet(&mut self, network_path: FourTuple, path_id: PathId) -> bool { +2296 if self.is_handshaking() && path_id != PathId::ZERO { +2297 debug!(%network_path, %path_id, "discarding multipath packet during handshake"); +2298 return true; +2299 } +2300 +2301 if !self.paths.contains_key(&path_id) && self.abandoned_paths.contains(&path_id) { +2302 trace!(%path_id, "discarding packet for discarded path"); +2303 return true; +2304 } 2305 -2306 if known_path.network_path.local_ip.is_some() -2307 && network_path.local_ip.is_some() -2308 && known_path.network_path.local_ip != network_path.local_ip -2309 && !local_ip_may_migrate -2310 { -2311 trace!( -2312 %path_id, -2313 %network_path, -2314 %known_path.network_path, -2315 "discarding packet sent to incorrect interface" -2316 ); -2317 return true; -2318 } -2319 } -2320 false -2321 } +2306 let peer_may_probe = self.peer_may_probe(); +2307 let local_ip_may_migrate = self.local_ip_may_migrate(); +2308 +2309 // If this packet could initiate a migration and we're a client or a server that +2310 // forbids migration, drop the datagram. This could be relaxed to heuristically +2311 // permit NAT-rebinding-like migration. +2312 if let Some(known_path) = self.path_mut(path_id) { +2313 if network_path.remote != known_path.network_path.remote && !peer_may_probe { +2314 trace!( +2315 %path_id, +2316 %network_path, +2317 %known_path.network_path, +2318 "discarding packet from unrecognized peer" +2319 ); +2320 return true; +2321 } 2322 -2323 /// Whether a remote is allowed to migrate. -2324 /// -2325 /// QUIC relies on stable endpoints during the handshake. So other than the server's -2326 /// preferred_address transport parameter no side may migrate before the handshake is -2327 /// completed. -2328 /// -2329 /// In RFC9000 only the client may migrate. If QNT is negotiated the server may migrate -2330 /// as well. -2331 /// -2332 /// Additionally for iroh we allow the server to migrate once during the handshake as -2333 /// long as the client has not received an authenticated Handshake packet. This allows -2334 /// us to duplicate client Initial packets to multiple destinations. See -2335 /// [`state::Handshake::allow_server_migration`]. -2336 fn remote_may_migrate(&self) -> bool { -2337 match &self.side { -2338 ConnectionSide::Server { server_config } => { -2339 server_config.migration && self.is_handshake_confirmed() -2340 } -2341 ConnectionSide::Client { .. } => { -2342 if let Some(hs) = self.state.as_handshake() { -2343 hs.allow_server_migration -2344 } else { -2345 self.n0_nat_traversal.is_negotiated() && self.is_handshake_confirmed() -2346 } -2347 } -2348 } -2349 } -2350 -2351 /// Whether our local IP address is allowed to change with new incoming packets. -2352 /// -2353 /// Incoming packets show us the local IP address we received a packet on, which could -2354 /// be different from what we thought due to e.g. NAT rebinding or moving from mobile -2355 /// data to WiFi without being notified of the network change. -2356 /// -2357 /// This is only allowed to happen after the handshake is confirmed and when we are the -2358 /// client. Unless QNT is negotiated in which case the server is also allowed to -2359 /// migrate. -2360 /// -2361 /// Be aware that probing packets, which do not exist in Multipath without QNT, are -2362 /// exempt from this. -2363 fn local_ip_may_migrate(&self) -> bool { -2364 (self.side.is_client() || self.n0_nat_traversal.is_negotiated()) -2365 && self.is_handshake_confirmed() -2366 } -2367 /// Process timer expirations -2368 /// -2369 /// Executes protocol logic, potentially preparing signals (including application `Event`s, -2370 /// `EndpointEvent`s and outgoing datagrams) that should be extracted through the relevant -2371 /// methods. -2372 /// -2373 /// It is most efficient to call this immediately after the system clock reaches the latest -2374 /// `Instant` that was output by `poll_timeout`; however spurious extra calls will simply -2375 /// no-op and therefore are safe. -2376 pub fn handle_timeout(&mut self, now: Instant) { -2377 while let Some((timer, _time)) = self.timers.expire_before(now, &self.qlog) { -2378 let span = match timer { -2379 Timer::Conn(timer) => trace_span!("timeout", scope = "conn", ?timer), -2380 Timer::PerPath(path_id, timer) => { -2381 trace_span!("timer_fired", scope="path", %path_id, ?timer) -2382 } -2383 }; -2384 let _guard = span.enter(); -2385 trace!("timeout"); -2386 match timer { -2387 Timer::Conn(timer) => match timer { -2388 ConnTimer::Close => { -2389 self.state.move_to_drained(None); -2390 // move_to_drained checks that we weren't in drained before. -2391 // Adding events to endpoint_events is only legal if `Drained` was never queued before. -2392 self.endpoint_events.push_back(EndpointEventInner::Drained); -2393 } -2394 ConnTimer::Idle => { -2395 self.kill(ConnectionError::TimedOut); -2396 } -2397 ConnTimer::KeepAlive => { -2398 self.ping(); -2399 } -2400 ConnTimer::KeyDiscard => { -2401 self.crypto_state.discard_temporary_keys(); -2402 } -2403 ConnTimer::PushNewCid => { -2404 while let Some((path_id, when)) = self.next_cid_retirement() { -2405 if when > now { -2406 break; -2407 } -2408 match self.local_cid_state.get_mut(&path_id) { -2409 None => error!(%path_id, "No local CID state for path"), -2410 Some(cid_state) => { -2411 // Update `retire_prior_to` field in NEW_CONNECTION_ID frame -2412 let num_new_cid = cid_state.on_cid_timeout().into(); -2413 if !self.state.is_closed() { -2414 trace!( -2415 "push a new CID to peer RETIRE_PRIOR_TO field {}", -2416 cid_state.retire_prior_to() -2417 ); -2418 self.endpoint_events.push_back( -2419 EndpointEventInner::NeedIdentifiers( -2420 path_id, -2421 now, -2422 num_new_cid, -2423 ), -2424 ); -2425 } -2426 } -2427 } -2428 } -2429 } -2430 ConnTimer::NoAvailablePath => { -2431 // Grace period expired: all paths were abandoned and no new path -2432 // was opened. Close the connection. There are no paths left to -2433 // send CONNECTION_CLOSE on, so this is a silent close. -2434 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 -2435 if self.state.is_closed() || self.state.is_drained() { -2436 // Connection already closing/drained (e.g. application called -2437 // close() before the grace timer fired). Nothing to do. -2438 error!("no viable path timer fired, but connection already closing"); -2439 } else { -2440 trace!("no viable path grace period expired, closing connection"); -2441 let err = TransportError::NO_VIABLE_PATH( -2442 "last path abandoned, no new path opened", -2443 ); -2444 self.close_common(); -2445 self.set_close_timer(now); -2446 self.connection_close_pending = true; -2447 self.state.move_to_closed(err); -2448 } -2449 } -2450 ConnTimer::NatTraversalProbeRetry => { -2451 self.n0_nat_traversal.queue_retries(self.is_ipv6()); -2452 if let Some(delay) = -2453 self.n0_nat_traversal.retry_delay(self.config.initial_rtt) -2454 { -2455 self.timers.set( -2456 Timer::Conn(ConnTimer::NatTraversalProbeRetry), -2457 now + delay, -2458 self.qlog.with_time(now), -2459 ); -2460 trace!("re-queued NAT probes"); -2461 } else { -2462 trace!("no more NAT probes remaining"); -2463 } -2464 } -2465 }, -2466 Timer::PerPath(path_id, timer) => { -2467 match timer { -2468 PathTimer::PathIdle => { -2469 if let Err(err) = -2470 self.close_path_inner(now, path_id, PathAbandonReason::TimedOut) -2471 { -2472 warn!(?err, "failed closing path"); -2473 } -2474 } -2475 -2476 PathTimer::PathKeepAlive => { -2477 self.ping_path(path_id).ok(); -2478 } -2479 PathTimer::LossDetection => { -2480 self.on_loss_detection_timeout(now, path_id); -2481 self.qlog.emit_recovery_metrics( -2482 path_id, -2483 &mut self -2484 .paths -2485 .get_mut(&path_id) -2486 .expect("loss-detection timer fires only on live paths") -2487 .data, -2488 now, -2489 ); -2490 } -2491 PathTimer::PathValidationFailed => { -2492 let Some(path) = self.paths.get_mut(&path_id) else { -2493 continue; -2494 }; -2495 self.timers.stop( -2496 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2497 self.qlog.with_time(now), -2498 ); -2499 debug!("path migration validation failed"); -2500 if let Some((_, prev)) = path.prev.take() { -2501 path.data = prev; -2502 } -2503 path.data.reset_on_path_challenges(); -2504 } -2505 PathTimer::PathChallengeLost => { -2506 let Some(path) = self.paths.get_mut(&path_id) else { -2507 continue; -2508 }; -2509 trace!("path challenge deemed lost"); -2510 path.data.pending_on_path_challenge = true; -2511 } -2512 PathTimer::AbandonFromValidation => { -2513 let Some(path) = self.paths.get_mut(&path_id) else { -2514 continue; -2515 }; -2516 path.data.reset_on_path_challenges(); -2517 self.timers.stop( -2518 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -2519 self.qlog.with_time(now), -2520 ); -2521 debug!("new path validation failed"); -2522 if let Err(err) = self.close_path_inner( -2523 now, -2524 path_id, -2525 PathAbandonReason::ValidationFailed, -2526 ) { -2527 warn!(?err, "failed closing path"); -2528 } -2529 } -2530 PathTimer::Pacing => {} -2531 PathTimer::MaxAckDelay => { -2532 // This timer is only armed in the Data space -2533 self.spaces[SpaceId::Data] -2534 .for_path(path_id) -2535 .pending_acks -2536 .on_max_ack_delay_timeout() -2537 } -2538 PathTimer::PathDrained => { -2539 // The path was abandoned and 3*PTO has expired since. Clean up all -2540 // remaining state and install stateless reset token. -2541 self.timers.stop_per_path(path_id, self.qlog.with_time(now)); -2542 if let Some(local_cid_state) = self.local_cid_state.remove(&path_id) { -2543 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events. All timers should be cleared in drained connections. -2544 let (min_seq, max_seq) = local_cid_state.active_seq(); -2545 for seq in min_seq..=max_seq { -2546 self.endpoint_events.push_back( -2547 EndpointEventInner::RetireConnectionId( -2548 now, path_id, seq, false, -2549 ), -2550 ); -2551 } -2552 } -2553 self.discard_path(path_id, now); -2554 } -2555 } -2556 } -2557 } -2558 } -2559 } -2560 -2561 /// Close a connection immediately -2562 /// -2563 /// This does not ensure delivery of outstanding data. It is the application's responsibility to -2564 /// call this only when all important communications have been completed, e.g. by calling -2565 /// [`SendStream::finish`] on outstanding streams and waiting for the corresponding -2566 /// [`StreamEvent::Finished`] event. -2567 /// -2568 /// If [`Streams::send_streams`] returns 0, all outstanding stream data has been -2569 /// delivered. There may still be data from the peer that has not been received. -2570 /// -2571 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished -2572 pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes) { -2573 self.close_inner( -2574 now, -2575 Close::Application(frame::ApplicationClose { error_code, reason }), -2576 ) -2577 } -2578 -2579 /// Close the connection immediately, initiated by an API call. -2580 /// -2581 /// This will not produce a [`ConnectionLost`] event propagated by the -2582 /// [`Connection::poll`] call, because the API call already propagated the error to the -2583 /// user. -2584 /// -2585 /// Not to be used when entering immediate close due to an internal state change based -2586 /// on an event. See [`State::move_to_closed_local`] for details. -2587 /// -2588 /// This initiates immediate close from -2589 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2>, moving to the closed -2590 /// state. -2591 /// -2592 /// [`ConnectionLost`]: crate::Event::ConnectionLost -2593 /// [`Connection::poll`]: super::Connection::poll -2594 fn close_inner(&mut self, now: Instant, reason: Close) { -2595 let was_closed = self.state.is_closed(); -2596 if !was_closed { -2597 self.close_common(); -2598 self.set_close_timer(now); -2599 self.connection_close_pending = true; -2600 self.state.move_to_closed_local(reason); -2601 } -2602 } -2603 -2604 /// Control datagrams -2605 pub fn datagrams(&mut self) -> Datagrams<'_> { -2606 Datagrams { conn: self } -2607 } -2608 -2609 /// Returns connection statistics -2610 pub fn stats(&mut self) -> ConnectionStats { -2611 let mut stats = self.partial_stats.clone(); -2612 -2613 for path_stats in self.path_stats.iter_stats() { -2614 // Self::path_stats() computes the path rtt, cwnd and current_mtu on access -2615 // because they are not simple counters. When computing the connection stats we -2616 // can skip that effort since those fields are not used in the `impl -2617 // Add<PathStats> for ConnectionStats`. -2618 stats += *path_stats; -2619 } -2620 -2621 stats -2622 } -2623 -2624 /// Returns path statistics -2625 pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { -2626 let path = self.paths.get(&path_id)?; -2627 let stats = self.path_stats.for_path(path_id); -2628 stats.rtt = path.data.rtt.get(); -2629 stats.cwnd = path.data.congestion.window(); -2630 stats.current_mtu = path.data.mtud.current_mtu(); -2631 Some(*stats) -2632 } -2633 -2634 /// Ping the remote endpoint -2635 /// -2636 /// Causes an ACK-eliciting packet to be transmitted on the connection. -2637 pub fn ping(&mut self) { -2638 // TODO(flub): This is very brute-force: it pings *all* the paths. Instead it would -2639 // be nice if we could only send a single packet for this. -2640 for path_data in self.spaces[self.highest_space].number_spaces.values_mut() { -2641 path_data.ping_pending = true; -2642 } +2323 if known_path.network_path.local_ip.is_some() +2324 && network_path.local_ip.is_some() +2325 && known_path.network_path.local_ip != network_path.local_ip +2326 && !local_ip_may_migrate +2327 { +2328 trace!( +2329 %path_id, +2330 %network_path, +2331 %known_path.network_path, +2332 "discarding packet sent to incorrect interface" +2333 ); +2334 return true; +2335 } +2336 } +2337 false +2338 } +2339 +2340 /// Whether the peer may probe new paths. +2341 /// +2342 /// RFC9000 §9 and QNT both have probing packets which may arrive from new paths. This +2343 /// indicates whether these are allowed or not. This is a strict superset from +2344 /// [`Self::peer_may_migrate`]: every network path that may be migrated to, may also +2345 /// be probed. But e.g. servers may not migrate, but can be allowed to probe. +2346 // TODO(flub): In RFC9000 the server is allowed to send off-path probing packets +2347 // once the client has been probing such a 4-tuple. These probes are currently +2348 // not yet recognised and will end up being discarded because of this. +2349 // See https://github.com/n0-computer/noq/issues/607. +2350 fn peer_may_probe(&self) -> bool { +2351 match &self.side { +2352 ConnectionSide::Client { .. } => { +2353 if let Some(hs) = self.state.as_handshake() { +2354 hs.allow_server_migration +2355 } else { +2356 self.n0_nat_traversal.is_negotiated() && self.is_handshake_confirmed() +2357 } +2358 } +2359 ConnectionSide::Server { server_config } => { +2360 self.is_handshake_confirmed() +2361 && (server_config.migration || self.n0_nat_traversal.is_negotiated()) +2362 } +2363 } +2364 } +2365 +2366 /// Whether the peer's remote address may migrate. +2367 /// +2368 /// In RFC9000 only the client may migrate. +2369 /// +2370 /// QUIC relies on stable endpoints during the handshake. So other than the server's +2371 /// preferred_address transport parameter no side may migrate before the handshake is +2372 /// completed. +2373 /// +2374 /// It is noteworthy that for iroh we allow server migrations during the handshake when +2375 /// [`state::Handshake::allow_server_migration`] is enabled, but that is handled earlier +2376 /// in [`Self::handle_packet`] and without probing the current and previous paths. +2377 fn peer_may_migrate(&self) -> bool { +2378 match &self.side { +2379 ConnectionSide::Server { server_config } => { +2380 server_config.migration && self.is_handshake_confirmed() +2381 } +2382 ConnectionSide::Client { .. } => false, +2383 } +2384 } +2385 +2386 /// Whether our local IP address is allowed to change with new incoming packets. +2387 /// +2388 /// Incoming packets show us the local IP address we received a packet on, which could +2389 /// be different from what we thought due to e.g. NAT rebinding or moving from mobile +2390 /// data to WiFi without being notified of the network change. +2391 /// +2392 /// This is only allowed to happen after the handshake is confirmed and when we are the +2393 /// client. Unless QNT is negotiated in which case the server is also allowed to +2394 /// migrate. +2395 /// +2396 /// Be aware that probing packets, which do not exist in Multipath without QNT, are +2397 /// exempt from this. +2398 fn local_ip_may_migrate(&self) -> bool { +2399 (self.side.is_client() || self.n0_nat_traversal.is_negotiated()) +2400 && self.is_handshake_confirmed() +2401 } +2402 /// Process timer expirations +2403 /// +2404 /// Executes protocol logic, potentially preparing signals (including application `Event`s, +2405 /// `EndpointEvent`s and outgoing datagrams) that should be extracted through the relevant +2406 /// methods. +2407 /// +2408 /// It is most efficient to call this immediately after the system clock reaches the latest +2409 /// `Instant` that was output by `poll_timeout`; however spurious extra calls will simply +2410 /// no-op and therefore are safe. +2411 pub fn handle_timeout(&mut self, now: Instant) { +2412 while let Some((timer, _time)) = self.timers.expire_before(now, &self.qlog) { +2413 let span = match timer { +2414 Timer::Conn(timer) => trace_span!("timeout", scope = "conn", ?timer), +2415 Timer::PerPath(path_id, timer) => { +2416 trace_span!("timer_fired", scope="path", %path_id, ?timer) +2417 } +2418 }; +2419 let _guard = span.enter(); +2420 trace!("timeout"); +2421 match timer { +2422 Timer::Conn(timer) => match timer { +2423 ConnTimer::Close => { +2424 let was_draining = self.state.move_to_drained(None); +2425 if !was_draining { +2426 self.endpoint_events.push_back(EndpointEventInner::Draining); +2427 } +2428 // move_to_drained checks that we weren't in drained before. +2429 // Adding events to endpoint_events is only legal if `Drained` was never queued before. +2430 self.endpoint_events.push_back(EndpointEventInner::Drained); +2431 } +2432 ConnTimer::Idle => { +2433 self.kill(ConnectionError::TimedOut); +2434 } +2435 ConnTimer::KeepAlive => { +2436 self.ping(); +2437 } +2438 ConnTimer::KeyDiscard => { +2439 self.crypto_state.discard_temporary_keys(); +2440 } +2441 ConnTimer::PushNewCid => { +2442 while let Some((path_id, when)) = self.next_cid_retirement() { +2443 if when > now { +2444 break; +2445 } +2446 match self.local_cid_state.get_mut(&path_id) { +2447 None => error!(%path_id, "No local CID state for path"), +2448 Some(cid_state) => { +2449 // Update `retire_prior_to` field in NEW_CONNECTION_ID frame +2450 let num_new_cid = cid_state.on_cid_timeout().into(); +2451 if !self.state.is_closed() { +2452 trace!( +2453 "push a new CID to peer RETIRE_PRIOR_TO field {}", +2454 cid_state.retire_prior_to() +2455 ); +2456 self.endpoint_events.push_back( +2457 EndpointEventInner::NeedIdentifiers( +2458 path_id, +2459 now, +2460 num_new_cid, +2461 ), +2462 ); +2463 } +2464 } +2465 } +2466 } +2467 } +2468 ConnTimer::NoAvailablePath => { +2469 // Grace period expired: all paths were abandoned and no new path +2470 // was opened. Close the connection. There are no paths left to +2471 // send CONNECTION_CLOSE on, so this is a silent close. +2472 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4-8 +2473 if self.state.is_closed() || self.state.is_drained() { +2474 // Connection already closing/drained (e.g. application called +2475 // close() before the grace timer fired). Nothing to do. +2476 error!("no viable path timer fired, but connection already closing"); +2477 } else { +2478 trace!("no viable path grace period expired, closing connection"); +2479 let err = TransportError::NO_VIABLE_PATH( +2480 "last path abandoned, no new path opened", +2481 ); +2482 self.close_common(); +2483 self.set_close_timer(now); +2484 self.connection_close_pending = true; +2485 self.state.move_to_closed(err); +2486 } +2487 } +2488 ConnTimer::NatTraversalProbeRetry => { +2489 self.n0_nat_traversal.queue_retries(self.is_ipv6()); +2490 if let Some(delay) = +2491 self.n0_nat_traversal.retry_delay(self.config.initial_rtt) +2492 { +2493 self.timers.set( +2494 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +2495 now + delay, +2496 self.qlog.with_time(now), +2497 ); +2498 trace!("re-queued NAT probes"); +2499 } else { +2500 trace!("no more NAT probes remaining"); +2501 } +2502 } +2503 }, +2504 Timer::PerPath(path_id, timer) => { +2505 match timer { +2506 PathTimer::PathIdle => { +2507 if let Err(err) = +2508 self.close_path_inner(now, path_id, PathAbandonReason::TimedOut) +2509 { +2510 warn!(?err, "failed closing path"); +2511 } +2512 } +2513 +2514 PathTimer::PathKeepAlive => { +2515 self.ping_path(path_id).ok(); +2516 } +2517 PathTimer::LossDetection => { +2518 self.on_loss_detection_timeout(now, path_id); +2519 if let Some(path) = self.paths.get_mut(&path_id) { +2520 self.qlog +2521 .emit_recovery_metrics(path_id, &mut path.data, now); +2522 } else { +2523 error!("LossDetection fired for unknown path"); +2524 } +2525 } +2526 PathTimer::PathValidationFailed => { +2527 let Some(path) = self.paths.get_mut(&path_id) else { +2528 continue; +2529 }; +2530 self.timers.stop( +2531 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +2532 self.qlog.with_time(now), +2533 ); +2534 debug!("path migration validation failed"); +2535 if let Some((_, prev)) = path.prev.take() { +2536 path.data = prev; +2537 } +2538 path.data.reset_on_path_challenges(); +2539 } +2540 PathTimer::PathChallengeLost => { +2541 let Some(path) = self.paths.get_mut(&path_id) else { +2542 continue; +2543 }; +2544 trace!(?path.data.lost_challenge_count, "path challenge deemed lost"); +2545 path.data.pending_challenge = true; +2546 path.data.lost_challenge_count += 1; +2547 } +2548 PathTimer::AbandonFromValidation => { +2549 let Some(path) = self.paths.get_mut(&path_id) else { +2550 continue; +2551 }; +2552 path.data.reset_on_path_challenges(); +2553 self.timers.stop( +2554 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +2555 self.qlog.with_time(now), +2556 ); +2557 debug!("new path validation failed"); +2558 if let Err(err) = self.close_path_inner( +2559 now, +2560 path_id, +2561 PathAbandonReason::ValidationFailed, +2562 ) { +2563 warn!(?err, "failed closing path"); +2564 } +2565 } +2566 PathTimer::Pacing => {} +2567 PathTimer::MaxAckDelay => { +2568 // This timer is only armed in the Data space +2569 self.spaces[SpaceId::Data] +2570 .for_path(path_id) +2571 .pending_acks +2572 .on_max_ack_delay_timeout() +2573 } +2574 PathTimer::PathDrained => { +2575 // The path was abandoned and 3*PTO has expired since. Clean up all +2576 // remaining state and install stateless reset token. +2577 self.timers.stop_per_path(path_id, self.qlog.with_time(now)); +2578 if let Some(local_cid_state) = self.local_cid_state.remove(&path_id) { +2579 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events. All timers should be cleared in drained connections. +2580 let (min_seq, max_seq) = local_cid_state.active_seq(); +2581 for seq in min_seq..=max_seq { +2582 self.endpoint_events.push_back( +2583 EndpointEventInner::RetireConnectionId( +2584 now, path_id, seq, false, +2585 ), +2586 ); +2587 } +2588 } +2589 self.discard_path(path_id, now); +2590 } +2591 } +2592 } +2593 } +2594 } +2595 } +2596 +2597 /// Close a connection immediately +2598 /// +2599 /// This does not ensure delivery of outstanding data. It is the application's responsibility to +2600 /// call this only when all important communications have been completed, e.g. by calling +2601 /// [`SendStream::finish`] on outstanding streams and waiting for the corresponding +2602 /// [`StreamEvent::Finished`] event. +2603 /// +2604 /// If [`Streams::send_streams`] returns 0, all outstanding stream data has been +2605 /// delivered. There may still be data from the peer that has not been received. +2606 /// +2607 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished +2608 pub fn close(&mut self, now: Instant, error_code: VarInt, reason: Bytes) { +2609 self.close_inner( +2610 now, +2611 Close::Application(frame::ApplicationClose { error_code, reason }), +2612 ) +2613 } +2614 +2615 /// Close the connection immediately, initiated by an API call. +2616 /// +2617 /// This will not produce a [`ConnectionLost`] event propagated by the +2618 /// [`Connection::poll`] call, because the API call already propagated the error to the +2619 /// user. +2620 /// +2621 /// Not to be used when entering immediate close due to an internal state change based +2622 /// on an event. See [`State::move_to_closed_local`] for details. +2623 /// +2624 /// This initiates immediate close from +2625 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2>, moving to the closed +2626 /// state. +2627 /// +2628 /// [`ConnectionLost`]: crate::Event::ConnectionLost +2629 /// [`Connection::poll`]: super::Connection::poll +2630 fn close_inner(&mut self, now: Instant, reason: Close) { +2631 let was_closed = self.state.is_closed(); +2632 if !was_closed { +2633 self.close_common(); +2634 self.set_close_timer(now); +2635 self.connection_close_pending = true; +2636 self.state.move_to_closed_local(reason); +2637 } +2638 } +2639 +2640 /// Control datagrams +2641 pub fn datagrams(&mut self) -> Datagrams<'_> { +2642 Datagrams { conn: self } 2643 } 2644 -2645 /// Ping the remote endpoint over a specific path -2646 /// -2647 /// Causes an ACK-eliciting packet to be transmitted on the path. -2648 pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath> { -2649 let path_data = self.spaces[self.highest_space] -2650 .number_spaces -2651 .get_mut(&path) -2652 .ok_or(ClosedPath { _private: () })?; -2653 path_data.ping_pending = true; -2654 Ok(()) -2655 } +2645 /// Returns connection statistics +2646 pub fn stats(&mut self) -> ConnectionStats { +2647 let mut stats = self.partial_stats.clone(); +2648 +2649 for path_stats in self.path_stats.iter_stats() { +2650 // Self::path_stats() computes the path rtt, cwnd and current_mtu on access +2651 // because they are not simple counters. When computing the connection stats we +2652 // can skip that effort since those fields are not used in the `impl +2653 // Add<PathStats> for ConnectionStats`. +2654 stats += *path_stats; +2655 } 2656 -2657 /// Update traffic keys spontaneously -2658 /// -2659 /// This can be useful for testing key updates, as they otherwise only happen infrequently. -2660 pub fn force_key_update(&mut self) { -2661 if !self.state.is_established() { -2662 debug!("ignoring forced key update in illegal state"); -2663 return; -2664 } -2665 if self.crypto_state.prev_crypto.is_some() { -2666 // We already just updated, or are currently updating, the keys. Concurrent key updates -2667 // are illegal. -2668 debug!("ignoring redundant forced key update"); -2669 return; -2670 } -2671 self.crypto_state.update_keys(None, false); -2672 } -2673 -2674 /// Get a session reference -2675 pub fn crypto_session(&self) -> &dyn crypto::Session { -2676 self.crypto_state.session.as_ref() -2677 } -2678 -2679 /// Whether the connection is in the process of being established -2680 /// -2681 /// If this returns `false`, the connection may be either established or closed, signaled by the -2682 /// emission of a [`Connected`](Event::Connected) or [`ConnectionLost`](Event::ConnectionLost) -2683 /// event respectively. Note that locally-initiated closes via [`close()`](Self::close) do not -2684 /// emit a `ConnectionLost` event. -2685 /// -2686 /// For an established connection this essentially means the handshake is **completed**, -2687 /// but not necessarily yet confirmed. -2688 pub fn is_handshaking(&self) -> bool { -2689 self.state.is_handshake() -2690 } -2691 -2692 /// Whether the connection is closed -2693 /// -2694 /// Closed connections cannot transport any further data. A connection becomes closed when -2695 /// either peer application intentionally closes it, or when either transport layer detects an -2696 /// error such as a time-out or certificate validation failure. -2697 /// -2698 /// A [`ConnectionLost`](Event::ConnectionLost) event is emitted with details when the -2699 /// connection is closed by the peer or due to an error. When the local application closes -2700 /// the connection via [`close()`](Self::close), no `ConnectionLost` event is emitted; -2701 /// instead, pending operations fail with [`ConnectionError::LocallyClosed`]. -2702 pub fn is_closed(&self) -> bool { -2703 self.state.is_closed() -2704 } -2705 -2706 /// Whether there is no longer any need to keep the connection around -2707 /// -2708 /// Closed connections become drained after a brief timeout to absorb any remaining in-flight -2709 /// packets from the peer. All drained connections have been closed. -2710 pub fn is_drained(&self) -> bool { -2711 self.state.is_drained() -2712 } -2713 -2714 /// For clients, if the peer accepted the 0-RTT data packets -2715 /// -2716 /// The value is meaningless until after the handshake completes. -2717 pub fn accepted_0rtt(&self) -> bool { -2718 self.crypto_state.accepted_0rtt -2719 } -2720 -2721 /// Whether 0-RTT is/was possible during the handshake -2722 pub fn has_0rtt(&self) -> bool { -2723 self.crypto_state.zero_rtt_enabled -2724 } -2725 -2726 /// Whether there are any pending retransmits -2727 pub fn has_pending_retransmits(&self) -> bool { -2728 !self.spaces[SpaceId::Data].pending.is_empty(&self.streams) -2729 } -2730 -2731 /// Look up whether we're the client or server of this Connection -2732 pub fn side(&self) -> Side { -2733 self.side.side() -2734 } -2735 -2736 /// Get the address observed by the remote over the given path -2737 pub fn path_observed_address(&self, path_id: PathId) -> Result<Option<SocketAddr>, ClosedPath> { -2738 self.path(path_id) -2739 .map(|path_data| { -2740 path_data -2741 .last_observed_addr_report -2742 .as_ref() -2743 .map(|observed| observed.socket_addr()) -2744 }) -2745 .ok_or(ClosedPath { _private: () }) -2746 } -2747 -2748 /// Current best estimate of this connection's latency (round-trip-time) -2749 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { -2750 self.path(path_id).map(|d| d.rtt.get()) -2751 } -2752 -2753 /// Current state of this connection's congestion controller, for debugging purposes -2754 pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller> { -2755 self.path(path_id).map(|d| d.congestion.as_ref()) -2756 } -2757 -2758 /// Modify the number of remotely initiated streams that may be concurrently open -2759 /// -2760 /// No streams may be opened by the peer unless fewer than `count` are already open. Large -2761 /// `count`s increase both minimum and worst-case memory consumption. -2762 pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt) { -2763 self.streams.set_max_concurrent(dir, count); -2764 // If the limit was reduced, then a flow control update previously deemed insignificant may -2765 // now be significant. -2766 let pending = &mut self.spaces[SpaceId::Data].pending; -2767 self.streams.queue_max_stream_id(pending); -2768 } -2769 -2770 /// Modify the number of open paths allowed when multipath is enabled -2771 /// -2772 /// When reducing the number of concurrent paths this will only affect delaying sending -2773 /// new MAX_PATH_ID frames until fewer than this number of paths are possible. To -2774 /// actively reduce paths they must be closed using [`Connection::close_path`], which -2775 /// can also be used to close not-yet-opened paths. -2776 /// -2777 /// If multipath is not negotiated (see the [`TransportConfig`]) this can not enable -2778 /// multipath and will fail. -2779 pub fn set_max_concurrent_paths( -2780 &mut self, -2781 now: Instant, -2782 count: NonZeroU32, -2783 ) -> Result<(), MultipathNotNegotiated> { -2784 if !self.is_multipath_negotiated() { -2785 return Err(MultipathNotNegotiated { _private: () }); -2786 } -2787 self.max_concurrent_paths = count; +2657 stats +2658 } +2659 +2660 /// Returns path statistics +2661 pub fn path_stats(&mut self, path_id: PathId) -> Option<PathStats> { +2662 let path = self.paths.get(&path_id)?; +2663 let mut stats = self.path_stats.get(path_id).unwrap_or_default(); +2664 stats.rtt = path.data.rtt.get(); +2665 stats.cwnd = path.data.congestion.window(); +2666 stats.current_mtu = path.data.mtud.current_mtu(); +2667 Some(stats) +2668 } +2669 +2670 /// Ping the remote endpoint +2671 /// +2672 /// Causes an ACK-eliciting packet to be transmitted on the connection. +2673 pub fn ping(&mut self) { +2674 // TODO(flub): This is very brute-force: it pings *all* the paths. Instead it would +2675 // be nice if we could only send a single packet for this. +2676 for path_data in self.spaces[self.highest_space].number_spaces.values_mut() { +2677 path_data.pending_ping = true; +2678 } +2679 } +2680 +2681 /// Ping the remote endpoint over a specific path +2682 /// +2683 /// Causes an ACK-eliciting packet to be transmitted on the path. +2684 pub fn ping_path(&mut self, path: PathId) -> Result<(), ClosedPath> { +2685 let path_data = self.spaces[self.highest_space] +2686 .number_spaces +2687 .get_mut(&path) +2688 .ok_or(ClosedPath { _private: () })?; +2689 path_data.pending_ping = true; +2690 Ok(()) +2691 } +2692 +2693 /// Update traffic keys spontaneously +2694 /// +2695 /// This can be useful for testing key updates, as they otherwise only happen infrequently. +2696 pub fn force_key_update(&mut self) { +2697 if !self.state.is_established() { +2698 debug!("ignoring forced key update in illegal state"); +2699 return; +2700 } +2701 if self.crypto_state.prev_crypto.is_some() { +2702 // We already just updated, or are currently updating, the keys. Concurrent key updates +2703 // are illegal. +2704 debug!("ignoring redundant forced key update"); +2705 return; +2706 } +2707 self.crypto_state.update_keys(None, false); +2708 } +2709 +2710 /// Get a session reference +2711 pub fn crypto_session(&self) -> &dyn crypto::Session { +2712 self.crypto_state.session.as_ref() +2713 } +2714 +2715 /// Whether the connection is in the process of being established +2716 /// +2717 /// If this returns `false`, the connection may be either established or closed, signaled by the +2718 /// emission of a [`Connected`](Event::Connected) or [`ConnectionLost`](Event::ConnectionLost) +2719 /// event respectively. Note that locally-initiated closes via [`close()`](Self::close) do not +2720 /// emit a `ConnectionLost` event. +2721 /// +2722 /// For an established connection this essentially means the handshake is **completed**, +2723 /// but not necessarily yet confirmed. +2724 pub fn is_handshaking(&self) -> bool { +2725 self.state.is_handshake() +2726 } +2727 +2728 /// Whether the connection is closed +2729 /// +2730 /// Closed connections cannot transport any further data. A connection becomes closed when +2731 /// either peer application intentionally closes it, or when either transport layer detects an +2732 /// error such as a time-out or certificate validation failure. +2733 /// +2734 /// A [`ConnectionLost`](Event::ConnectionLost) event is emitted with details when the +2735 /// connection is closed by the peer or due to an error. When the local application closes +2736 /// the connection via [`close()`](Self::close), no `ConnectionLost` event is emitted; +2737 /// instead, pending operations fail with [`ConnectionError::LocallyClosed`]. +2738 pub fn is_closed(&self) -> bool { +2739 self.state.is_closed() +2740 } +2741 +2742 /// Whether there is no longer any need to keep the connection around +2743 /// +2744 /// Closed connections become drained after a brief timeout to absorb any remaining in-flight +2745 /// packets from the peer. All drained connections have been closed. +2746 pub fn is_drained(&self) -> bool { +2747 self.state.is_drained() +2748 } +2749 +2750 /// For clients, if the peer accepted the 0-RTT data packets +2751 /// +2752 /// The value is meaningless until after the handshake completes. +2753 pub fn accepted_0rtt(&self) -> bool { +2754 self.crypto_state.accepted_0rtt +2755 } +2756 +2757 /// Whether 0-RTT is/was possible during the handshake +2758 pub fn has_0rtt(&self) -> bool { +2759 self.crypto_state.zero_rtt_enabled +2760 } +2761 +2762 /// Whether there are any pending retransmits +2763 pub fn has_pending_retransmits(&self) -> bool { +2764 !self.spaces[SpaceId::Data].pending.is_empty(&self.streams) +2765 } +2766 +2767 /// Look up whether we're the client or server of this Connection +2768 pub fn side(&self) -> Side { +2769 self.side.side() +2770 } +2771 +2772 /// Get the address observed by the remote over the given path +2773 pub fn path_observed_address(&self, path_id: PathId) -> Result<Option<SocketAddr>, ClosedPath> { +2774 self.path(path_id) +2775 .map(|path_data| { +2776 path_data +2777 .last_observed_addr_report +2778 .as_ref() +2779 .map(|observed| observed.socket_addr()) +2780 }) +2781 .ok_or(ClosedPath { _private: () }) +2782 } +2783 +2784 /// Current best estimate of this connection's latency (round-trip-time) +2785 pub fn rtt(&self, path_id: PathId) -> Option<Duration> { +2786 self.path(path_id).map(|d| d.rtt.get()) +2787 } 2788 -2789 let in_use_count = self -2790 .local_max_path_id -2791 .next() -2792 .saturating_sub(self.abandoned_paths.len() as u32) -2793 .as_u32(); -2794 let extra_needed = count.get().saturating_sub(in_use_count); -2795 let new_max_path_id = self.local_max_path_id.saturating_add(extra_needed); -2796 -2797 self.set_max_path_id(now, new_max_path_id); -2798 -2799 Ok(()) -2800 } -2801 -2802 /// If needed, issues a new MAX_PATH_ID frame and new CIDs for any newly allowed paths -2803 fn set_max_path_id(&mut self, now: Instant, max_path_id: PathId) { -2804 if max_path_id <= self.local_max_path_id { -2805 return; -2806 } -2807 -2808 self.local_max_path_id = max_path_id; -2809 self.spaces[SpaceId::Data].pending.max_path_id = true; -2810 -2811 self.issue_first_path_cids(now); -2812 } -2813 -2814 /// Current number of remotely initiated streams that may be concurrently open -2815 /// -2816 /// If the target for this limit is reduced using [`set_max_concurrent_streams`](Self::set_max_concurrent_streams), -2817 /// it will not change immediately, even if fewer streams are open. Instead, it will -2818 /// decrement by one for each time a remotely initiated stream of matching directionality is closed. -2819 pub fn max_concurrent_streams(&self, dir: Dir) -> u64 { -2820 self.streams.max_concurrent(dir) -2821 } -2822 -2823 /// See [`TransportConfig::send_window()`] -2824 pub fn set_send_window(&mut self, send_window: u64) { -2825 self.streams.set_send_window(send_window); -2826 } -2827 -2828 /// See [`TransportConfig::receive_window()`] -2829 pub fn set_receive_window(&mut self, receive_window: VarInt) { -2830 if self.streams.set_receive_window(receive_window) { -2831 self.spaces[SpaceId::Data].pending.max_data = true; -2832 } -2833 } +2789 /// Current state of this connection's congestion controller, for debugging purposes +2790 pub fn congestion_state(&self, path_id: PathId) -> Option<&dyn Controller> { +2791 self.path(path_id).map(|d| d.congestion.as_ref()) +2792 } +2793 +2794 /// Modify the number of remotely initiated streams that may be concurrently open +2795 /// +2796 /// No streams may be opened by the peer unless fewer than `count` are already open. Large +2797 /// `count`s increase both minimum and worst-case memory consumption. +2798 pub fn set_max_concurrent_streams(&mut self, dir: Dir, count: VarInt) { +2799 self.streams.set_max_concurrent(dir, count); +2800 // If the limit was reduced, then a flow control update previously deemed insignificant may +2801 // now be significant. +2802 let pending = &mut self.spaces[SpaceId::Data].pending; +2803 self.streams.queue_max_stream_id(pending); +2804 } +2805 +2806 /// Modify the number of open paths allowed when multipath is enabled +2807 /// +2808 /// When reducing the number of concurrent paths this will only affect delaying sending +2809 /// new MAX_PATH_ID frames until fewer than this number of paths are possible. To +2810 /// actively reduce paths they must be closed using [`Connection::close_path`], which +2811 /// can also be used to close not-yet-opened paths. +2812 /// +2813 /// If multipath is not negotiated (see the [`TransportConfig`]) this can not enable +2814 /// multipath and will fail. +2815 pub fn set_max_concurrent_paths( +2816 &mut self, +2817 now: Instant, +2818 count: NonZeroU32, +2819 ) -> Result<(), MultipathNotNegotiated> { +2820 if !self.is_multipath_negotiated() { +2821 return Err(MultipathNotNegotiated { _private: () }); +2822 } +2823 self.max_concurrent_paths = count; +2824 +2825 let in_use_count = self +2826 .local_max_path_id +2827 .next() +2828 .saturating_sub(self.abandoned_paths.len()) +2829 .as_u32(); +2830 let extra_needed = count.get().saturating_sub(in_use_count); +2831 let new_max_path_id = self.local_max_path_id.saturating_add(extra_needed); +2832 +2833 self.set_max_path_id(now, new_max_path_id); 2834 -2835 /// Whether the Multipath for QUIC extension is enabled. -2836 /// -2837 /// Multipath is only enabled after the handshake is completed and if it was enabled by both -2838 /// peers. -2839 pub fn is_multipath_negotiated(&self) -> bool { -2840 !self.is_handshaking() -2841 && self.config.max_concurrent_multipath_paths.is_some() -2842 && self.peer_params.initial_max_path_id.is_some() -2843 } -2844 -2845 fn on_ack_received( -2846 &mut self, -2847 now: Instant, -2848 space: SpaceId, -2849 ack: frame::Ack, -2850 ) -> Result<(), TransportError> { -2851 // All ACKs are referencing path 0 -2852 let path = PathId::ZERO; -2853 self.inner_on_ack_received(now, space, path, ack) -2854 } -2855 -2856 fn on_path_ack_received( -2857 &mut self, -2858 now: Instant, -2859 space: SpaceId, -2860 path_ack: frame::PathAck, -2861 ) -> Result<(), TransportError> { -2862 let (ack, path) = path_ack.into_ack(); -2863 self.inner_on_ack_received(now, space, path, ack) -2864 } -2865 -2866 /// Handles an ACK frame acknowledging packets sent on *path*. -2867 fn inner_on_ack_received( -2868 &mut self, -2869 now: Instant, -2870 space: SpaceId, -2871 path: PathId, -2872 ack: frame::Ack, -2873 ) -> Result<(), TransportError> { -2874 if !self.spaces[space].number_spaces.contains_key(&path) { -2875 if self.abandoned_paths.contains(&path) { -2876 // See also -2877 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4.3-3 -2878 // > When an endpoint finally deletes all state associated with the path [...] -2879 // > PATH_ACK frames received with an abandoned path ID are silently ignored, -2880 // > as specified in Section 4. -2881 trace!("silently ignoring PATH_ACK on discarded path"); -2882 return Ok(()); -2883 } else { -2884 return Err(TransportError::PROTOCOL_VIOLATION( -2885 "received PATH_ACK with path ID never used", -2886 )); -2887 } -2888 } -2889 if ack.largest >= self.spaces[space].for_path(path).next_packet_number { -2890 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); -2891 } -2892 // `Some(pn)` if this ACK raised `largest_acked_packet_pn`. -2893 let new_largest_pn = { -2894 let space = &mut self.spaces[space].for_path(path); -2895 if space -2896 .largest_acked_packet_pn -2897 .is_none_or(|pn| ack.largest > pn) -2898 { -2899 space.largest_acked_packet_pn = Some(ack.largest); -2900 if let Some(info) = space.sent_packets.get(ack.largest) { -2901 // This should always succeed, but a misbehaving peer might ACK a packet we -2902 // haven't sent. At worst, that will result in us spuriously reducing the -2903 // congestion window. -2904 space.largest_acked_packet_send_time = info.time_sent; -2905 } -2906 Some(ack.largest) -2907 } else { -2908 None -2909 } -2910 }; -2911 -2912 if self.detect_spurious_loss(&ack, space, path) { -2913 self.path_stats.for_path(path).spurious_congestion_events += 1; -2914 self.path_data_mut(path) -2915 .congestion -2916 .on_spurious_congestion_event(); -2917 } -2918 -2919 // Avoid DoS from unreasonably huge ack ranges by filtering out just the new acks. -2920 let mut newly_acked = ArrayRangeSet::new(); -2921 for range in ack.iter() { -2922 self.spaces[space].for_path(path).check_ack(range.clone())?; -2923 for (pn, _) in self.spaces[space] -2924 .for_path(path) -2925 .sent_packets -2926 .iter_range(range) -2927 { -2928 newly_acked.insert_one(pn); -2929 } -2930 } -2931 -2932 if newly_acked.is_empty() { -2933 return Ok(()); -2934 } -2935 -2936 let mut ack_eliciting_acked = false; -2937 for packet in newly_acked.elts() { -2938 if let Some(info) = self.spaces[space].for_path(path).take(packet) { -2939 for (acked_path_id, acked_pn) in info.largest_acked.iter() { -2940 // Assume ACKs for all packets below the largest acknowledged in -2941 // `packet` have been received. This can cause the peer to spuriously -2942 // retransmit if some of our earlier ACKs were lost, but allows for -2943 // simpler state tracking. See discussion at -2944 // https://www.rfc-editor.org/rfc/rfc9000.html#name-limiting-ranges-by-tracking -2945 if let Some(pns) = self.spaces[space].path_space_mut(*acked_path_id) { -2946 pns.pending_acks.subtract_below(*acked_pn); -2947 } -2948 } -2949 ack_eliciting_acked |= info.ack_eliciting; -2950 -2951 // Notify MTU discovery that a packet was acked, because it might be an MTU probe -2952 let path_data = self.path_data_mut(path); -2953 let mtu_updated = path_data.mtud.on_acked(space.kind(), packet, info.size); -2954 if mtu_updated { -2955 path_data -2956 .congestion -2957 .on_mtu_update(path_data.mtud.current_mtu()); -2958 } -2959 -2960 // Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame -2961 self.ack_frequency.on_acked(path, packet); -2962 -2963 self.on_packet_acked(now, path, packet, info); -2964 } -2965 } -2966 -2967 let largest_ackd = self.spaces[space].for_path(path).largest_acked_packet_pn; -2968 let path_data = self.path_data_mut(path); -2969 let app_limited = path_data.app_limited; -2970 let in_flight = path_data.in_flight.bytes; +2835 Ok(()) +2836 } +2837 +2838 /// If needed, issues a new MAX_PATH_ID frame and new CIDs for any newly allowed paths +2839 fn set_max_path_id(&mut self, now: Instant, max_path_id: PathId) { +2840 if max_path_id <= self.local_max_path_id { +2841 return; +2842 } +2843 +2844 self.local_max_path_id = max_path_id; +2845 self.spaces[SpaceId::Data].pending.max_path_id = true; +2846 +2847 self.issue_first_path_cids(now); +2848 } +2849 +2850 /// Current number of remotely initiated streams that may be concurrently open +2851 /// +2852 /// If the target for this limit is reduced using [`set_max_concurrent_streams`](Self::set_max_concurrent_streams), +2853 /// it will not change immediately, even if fewer streams are open. Instead, it will +2854 /// decrement by one for each time a remotely initiated stream of matching directionality is closed. +2855 pub fn max_concurrent_streams(&self, dir: Dir) -> u64 { +2856 self.streams.max_concurrent(dir) +2857 } +2858 +2859 /// See [`TransportConfig::send_window()`] +2860 pub fn set_send_window(&mut self, send_window: u64) { +2861 self.streams.set_send_window(send_window); +2862 } +2863 +2864 /// See [`TransportConfig::receive_window()`] +2865 pub fn set_receive_window(&mut self, receive_window: VarInt) { +2866 if self.streams.set_receive_window(receive_window) { +2867 self.spaces[SpaceId::Data].pending.max_data = true; +2868 } +2869 } +2870 +2871 /// Whether the Multipath for QUIC extension is enabled. +2872 /// +2873 /// Multipath is only enabled after the handshake is completed and if it was enabled by both +2874 /// peers. +2875 pub fn is_multipath_negotiated(&self) -> bool { +2876 !self.is_handshaking() +2877 && self.config.max_concurrent_multipath_paths.is_some() +2878 && self.peer_params.initial_max_path_id.is_some() +2879 } +2880 +2881 fn on_ack_received( +2882 &mut self, +2883 now: Instant, +2884 space: SpaceId, +2885 ack: frame::Ack, +2886 ) -> Result<(), TransportError> { +2887 // All ACKs are referencing path 0 +2888 let path = PathId::ZERO; +2889 self.inner_on_ack_received(now, space, path, ack) +2890 } +2891 +2892 fn on_path_ack_received( +2893 &mut self, +2894 now: Instant, +2895 space: SpaceId, +2896 path_ack: frame::PathAck, +2897 ) -> Result<(), TransportError> { +2898 let (ack, path) = path_ack.into_ack(); +2899 self.inner_on_ack_received(now, space, path, ack) +2900 } +2901 +2902 /// Handles an ACK frame acknowledging packets sent on *path*. +2903 fn inner_on_ack_received( +2904 &mut self, +2905 now: Instant, +2906 space: SpaceId, +2907 path: PathId, +2908 ack: frame::Ack, +2909 ) -> Result<(), TransportError> { +2910 if !self.spaces[space].number_spaces.contains_key(&path) { +2911 if self.abandoned_paths.contains(&path) { +2912 // See also +2913 // https://www.ietf.org/archive/id/draft-ietf-quic-multipath-21.html#section-3.4.3-3 +2914 // > When an endpoint finally deletes all state associated with the path [...] +2915 // > PATH_ACK frames received with an abandoned path ID are silently ignored, +2916 // > as specified in Section 4. +2917 trace!("silently ignoring PATH_ACK on discarded path"); +2918 return Ok(()); +2919 } else { +2920 return Err(TransportError::PROTOCOL_VIOLATION( +2921 "received PATH_ACK with path ID never used", +2922 )); +2923 } +2924 } +2925 if ack.largest >= self.spaces[space].for_path(path).next_packet_number { +2926 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); +2927 } +2928 // `Some(pn)` if this ACK raised `largest_acked_packet_pn`. +2929 let new_largest_pn = { +2930 let space = &mut self.spaces[space].for_path(path); +2931 if space +2932 .largest_acked_packet_pn +2933 .is_none_or(|pn| ack.largest > pn) +2934 { +2935 space.largest_acked_packet_pn = Some(ack.largest); +2936 if let Some(info) = space.sent_packets.get(ack.largest) { +2937 // This should always succeed, but a misbehaving peer might ACK a packet we +2938 // haven't sent. At worst, that will result in us spuriously reducing the +2939 // congestion window. +2940 space.largest_acked_packet_send_time = info.time_sent; +2941 } +2942 Some(ack.largest) +2943 } else { +2944 None +2945 } +2946 }; +2947 +2948 if self.detect_spurious_loss(&ack, space, path) { +2949 self.path_stats.get_mut(path).spurious_congestion_events += 1; +2950 self.path_data_mut(path) +2951 .congestion +2952 .on_spurious_congestion_event(); +2953 } +2954 +2955 // Avoid DoS from unreasonably huge ack ranges by filtering out just the new acks. +2956 let mut newly_acked: ArrayRangeSet = ArrayRangeSet::new(); +2957 for range in ack.iter() { +2958 self.spaces[space].for_path(path).check_ack(range.clone())?; +2959 for (pn, _) in self.spaces[space] +2960 .for_path(path) +2961 .sent_packets +2962 .iter_range(range) +2963 { +2964 newly_acked.insert_one(pn); +2965 } +2966 } +2967 +2968 if newly_acked.is_empty() { +2969 return Ok(()); +2970 } 2971 -2972 path_data -2973 .congestion -2974 .on_end_acks(now, in_flight, app_limited, largest_ackd); -2975 -2976 if new_largest_pn.is_some() && ack_eliciting_acked { -2977 let ack_delay = if space != SpaceId::Data { -2978 Duration::from_micros(0) -2979 } else { -2980 cmp::min( -2981 self.ack_frequency.peer_max_ack_delay, -2982 Duration::from_micros(ack.delay << self.peer_params.ack_delay_exponent.0), -2983 ) -2984 }; -2985 let rtt = now.saturating_duration_since( -2986 self.spaces[space] -2987 .for_path(path) -2988 .largest_acked_packet_send_time, -2989 ); -2990 -2991 let next_pn = self.spaces[space].for_path(path).next_packet_number; -2992 let path_data = self.path_data_mut(path); -2993 // TODO(@divma): should be a method of path, should be contained in a single place -2994 path_data.rtt.update(ack_delay, rtt); -2995 if path_data.first_packet_after_rtt_sample.is_none() { -2996 path_data.first_packet_after_rtt_sample = Some((space.kind(), next_pn)); -2997 } -2998 } -2999 -3000 // Must be called before crypto/pto_count are clobbered -3001 self.detect_lost_packets(now, space, path, true); +2972 let mut ack_eliciting_acked = false; +2973 for packet in newly_acked.elts() { +2974 if let Some(info) = self.spaces[space].for_path(path).take(packet) { +2975 for (acked_path_id, acked_pn) in info.largest_acked.iter() { +2976 // Assume ACKs for all packets below the largest acknowledged in +2977 // `packet` have been received. This can cause the peer to spuriously +2978 // retransmit if some of our earlier ACKs were lost, but allows for +2979 // simpler state tracking. See discussion at +2980 // https://www.rfc-editor.org/rfc/rfc9000.html#name-limiting-ranges-by-tracking +2981 if let Some(pns) = self.spaces[space].path_space_mut(*acked_path_id) { +2982 pns.pending_acks.subtract_below(*acked_pn); +2983 } +2984 } +2985 ack_eliciting_acked |= info.ack_eliciting; +2986 +2987 // Notify MTU discovery that a packet was acked, because it might be an MTU probe +2988 let path_data = self.path_data_mut(path); +2989 let mtu_updated = path_data.mtud.on_acked(space.kind(), packet, info.size); +2990 if mtu_updated { +2991 path_data +2992 .congestion +2993 .on_mtu_update(path_data.mtud.current_mtu()); +2994 } +2995 +2996 // Notify ack frequency that a packet was acked, because it might contain an ACK_FREQUENCY frame +2997 self.ack_frequency.on_acked(path, packet); +2998 +2999 self.on_packet_acked(now, path, packet, info); +3000 } +3001 } 3002 -3003 // If the peer did not complete the handshake address validation the ACK could be -3004 // spoofed, e.g. in the Initial space. Setting the pto_count back to 0 removes the -3005 // exponential backoff from the PTO timer and would result in too many tail-loss -3006 // probes being sent. -3007 if self.peer_completed_handshake_address_validation() { -3008 self.path_data_mut(path).pto_count = 0; -3009 } -3010 -3011 // Explicit congestion notification -3012 // TODO(@divma): this code is a good example of logic that should be contained in a single -3013 // place but it's split between the path data and the packet number space data, we should -3014 // find a way to make this work without two lookups -3015 if self.path_data(path).sending_ecn { -3016 if let Some(ecn) = ack.ecn { -3017 // We only examine ECN counters from ACKs that we are certain we received in transmit -3018 // order, allowing us to compute an increase in ECN counts to compare against the number -3019 // of newly acked packets that remains well-defined in the presence of arbitrary packet -3020 // reordering. -3021 if let Some(largest_sent_pn) = new_largest_pn { -3022 let sent = self.spaces[space] -3023 .for_path(path) -3024 .largest_acked_packet_send_time; -3025 self.process_ecn( -3026 now, -3027 space, -3028 path, -3029 newly_acked.len() as u64, -3030 ecn, -3031 sent, -3032 largest_sent_pn, -3033 ); -3034 } -3035 } else { -3036 // We always start out sending ECN, so any ack that doesn't acknowledge it disables it. -3037 debug!("ECN not acknowledged by peer"); -3038 self.path_data_mut(path).sending_ecn = false; -3039 } -3040 } -3041 -3042 self.set_loss_detection_timer(now, path); -3043 Ok(()) -3044 } -3045 -3046 fn detect_spurious_loss(&mut self, ack: &frame::Ack, space: SpaceId, path: PathId) -> bool { -3047 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; -3048 -3049 if lost_packets.is_empty() { -3050 return false; -3051 } -3052 -3053 for range in ack.iter() { -3054 let spurious_losses: Vec<u64> = lost_packets -3055 .iter_range(range.clone()) -3056 .map(|(pn, _info)| pn) -3057 .collect(); -3058 -3059 for pn in spurious_losses { -3060 lost_packets.remove(pn); -3061 } -3062 } -3063 -3064 // If this ACK frame acknowledged all deemed lost packets, -3065 // then we have raised a spurious congestion event in the past. -3066 // We cannot conclude when there are remaining packets, -3067 // but future ACK frames might indicate a spurious loss detection. -3068 lost_packets.is_empty() -3069 } -3070 -3071 /// Drain lost packets that we reasonably think will never arrive -3072 /// -3073 /// The current criterion is copied from `msquic`: -3074 /// discard packets that were sent earlier than 2 probe timeouts ago. -3075 fn drain_lost_packets(&mut self, now: Instant, space: SpaceId, path: PathId) { -3076 let two_pto = 2 * self.path_data(path).rtt.pto_base(); +3003 let largest_ackd = self.spaces[space].for_path(path).largest_acked_packet_pn; +3004 let path_data = self.path_data_mut(path); +3005 let app_limited = path_data.app_limited; +3006 let in_flight = path_data.in_flight.bytes; +3007 +3008 path_data +3009 .congestion +3010 .on_end_acks(now, in_flight, app_limited, largest_ackd); +3011 +3012 if new_largest_pn.is_some() && ack_eliciting_acked { +3013 let ack_delay = if space != SpaceId::Data { +3014 Duration::from_micros(0) +3015 } else { +3016 cmp::min( +3017 self.ack_frequency.peer_max_ack_delay, +3018 Duration::from_micros(ack.delay << self.peer_params.ack_delay_exponent.0), +3019 ) +3020 }; +3021 let rtt = now.saturating_duration_since( +3022 self.spaces[space] +3023 .for_path(path) +3024 .largest_acked_packet_send_time, +3025 ); +3026 +3027 let next_pn = self.spaces[space].for_path(path).next_packet_number; +3028 let path_data = self.path_data_mut(path); +3029 // TODO(@divma): should be a method of path, should be contained in a single place +3030 path_data.rtt.update(ack_delay, rtt); +3031 if path_data.first_packet_after_rtt_sample.is_none() { +3032 path_data.first_packet_after_rtt_sample = Some((space.kind(), next_pn)); +3033 } +3034 } +3035 +3036 // Must be called before crypto/pto_count are clobbered +3037 self.detect_lost_packets(now, space, path, true); +3038 +3039 // If the peer did not complete the handshake address validation the ACK could be +3040 // spoofed, e.g. in the Initial space. Setting the pto_count back to 0 removes the +3041 // exponential backoff from the PTO timer and would result in too many tail-loss +3042 // probes being sent. +3043 if self.peer_completed_handshake_address_validation() { +3044 self.path_data_mut(path).pto_count = 0; +3045 } +3046 +3047 // Explicit congestion notification +3048 // TODO(@divma): this code is a good example of logic that should be contained in a single +3049 // place but it's split between the path data and the packet number space data, we should +3050 // find a way to make this work without two lookups +3051 if self.path_data(path).sending_ecn { +3052 if let Some(ecn) = ack.ecn { +3053 // We only examine ECN counters from ACKs that we are certain we received in transmit +3054 // order, allowing us to compute an increase in ECN counts to compare against the number +3055 // of newly acked packets that remains well-defined in the presence of arbitrary packet +3056 // reordering. +3057 if let Some(largest_sent_pn) = new_largest_pn { +3058 let sent = self.spaces[space] +3059 .for_path(path) +3060 .largest_acked_packet_send_time; +3061 self.process_ecn( +3062 now, +3063 space, +3064 path, +3065 newly_acked.range_count() as u64, +3066 ecn, +3067 sent, +3068 largest_sent_pn, +3069 ); +3070 } +3071 } else { +3072 // We always start out sending ECN, so any ack that doesn't acknowledge it disables it. +3073 debug!("ECN not acknowledged by peer"); +3074 self.path_data_mut(path).sending_ecn = false; +3075 } +3076 } 3077 -3078 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; -3079 lost_packets.retain(|_pn, info| now.saturating_duration_since(info.time_sent) <= two_pto); +3078 self.set_loss_detection_timer(now, path); +3079 Ok(()) 3080 } 3081 -3082 /// Process a new ECN block from an in-order ACK -3083 fn process_ecn( -3084 &mut self, -3085 now: Instant, -3086 space: SpaceId, -3087 path: PathId, -3088 newly_acked_pn: u64, -3089 ecn: frame::EcnCounts, -3090 largest_sent_time: Instant, -3091 largest_sent_pn: u64, -3092 ) { -3093 match self.spaces[space] -3094 .for_path(path) -3095 .detect_ecn(newly_acked_pn, ecn) -3096 { -3097 Err(e) => { -3098 debug!("halting ECN due to verification failure: {}", e); +3082 fn detect_spurious_loss(&mut self, ack: &frame::Ack, space: SpaceId, path: PathId) -> bool { +3083 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; +3084 +3085 if lost_packets.is_empty() { +3086 return false; +3087 } +3088 +3089 for range in ack.iter() { +3090 let spurious_losses: Vec<u64> = lost_packets +3091 .iter_range(range.clone()) +3092 .map(|(pn, _info)| pn) +3093 .collect(); +3094 +3095 for pn in spurious_losses { +3096 lost_packets.remove(pn); +3097 } +3098 } 3099 -3100 self.path_data_mut(path).sending_ecn = false; -3101 // Wipe out the existing value because it might be garbage and could interfere with -3102 // future attempts to use ECN on new paths. -3103 self.spaces[space].for_path(path).ecn_feedback = frame::EcnCounts::ZERO; -3104 } -3105 Ok(false) => {} -3106 Ok(true) => { -3107 self.path_stats.for_path(path).congestion_events += 1; -3108 self.path_data_mut(path).congestion.on_congestion_event( -3109 now, -3110 largest_sent_time, -3111 false, -3112 true, -3113 0, -3114 largest_sent_pn, -3115 ); -3116 } -3117 } -3118 } -3119 -3120 // Not timing-aware, so it's safe to call this for inferred acks, such as arise from -3121 // high-latency handshakes -3122 fn on_packet_acked(&mut self, now: Instant, path_id: PathId, pn: u64, info: SentPacket) { -3123 let path = self.path_data_mut(path_id); -3124 let app_limited = path.app_limited; -3125 path.remove_in_flight(&info); -3126 if info.ack_eliciting && info.path_generation == path.generation() { -3127 // Only pass ACKs to the congestion controller if it belongs to this exact -3128 // generation of the path. Otherwise we might be feeding ACKs from the previous -3129 // 4-tuple into our congestion controller. -3130 let rtt = path.rtt; -3131 path.congestion -3132 .on_ack(now, info.time_sent, info.size.into(), pn, app_limited, &rtt); -3133 } -3134 -3135 // Update state for confirmed delivery of frames -3136 if let Some(retransmits) = info.retransmits.get() { -3137 for (id, _) in retransmits.reset_stream.iter() { -3138 self.streams.reset_acked(*id); -3139 } -3140 } -3141 -3142 for frame in info.stream_frames { -3143 self.streams.received_ack_of(frame); -3144 } -3145 } -3146 -3147 fn set_key_discard_timer(&mut self, now: Instant, space: SpaceKind) { -3148 let start = if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) { -3149 now -3150 } else { -3151 self.crypto_state -3152 .prev_crypto -3153 .as_ref() -3154 .expect("no previous keys") -3155 .end_packet -3156 .as_ref() -3157 .expect("update not acknowledged yet") -3158 .1 -3159 }; -3160 -3161 // QUIC-MULTIPATH § 2.5 Key Phase Update Process: use largest PTO of all paths. -3162 self.timers.set( -3163 Timer::Conn(ConnTimer::KeyDiscard), -3164 start + self.max_pto_for_space(space) * 3, -3165 self.qlog.with_time(now), -3166 ); -3167 } -3168 -3169 /// Handle a [`PathTimer::LossDetection`] timeout. -3170 /// -3171 /// This timer expires for two reasons: -3172 /// - An ACK-eliciting packet we sent should be considered lost. -3173 /// - The PTO may have expired and a tail-loss probe needs to be scheduled. -3174 /// -3175 /// The former needs us to schedule re-transmission of the lost data. -3176 /// -3177 /// The latter means we have not received an ACK for an ack-eliciting packet we sent -3178 /// within the PTO time-window. We need to schedule a tail-loss probe, an ack-eliciting -3179 /// packet, to try and elicit new acknowledgements. These new acknowledgements will -3180 /// indicate whether the previously sent packets were lost or not. -3181 fn on_loss_detection_timeout(&mut self, now: Instant, path_id: PathId) { -3182 if let Some((_, pn_space)) = self.loss_time_and_space(path_id) { -3183 // Time threshold loss Detection -3184 self.detect_lost_packets(now, pn_space, path_id, false); -3185 self.set_loss_detection_timer(now, path_id); -3186 return; -3187 } -3188 -3189 let Some((_, space)) = self.pto_time_and_space(now, path_id) else { -3190 error!(%path_id, "PTO expired while unset"); -3191 return; -3192 }; -3193 trace!( -3194 in_flight = self.path_data(path_id).in_flight.bytes, -3195 count = self.path_data(path_id).pto_count, -3196 ?space, -3197 %path_id, -3198 "PTO fired" -3199 ); -3200 -3201 let count = match self.path_data(path_id).in_flight.ack_eliciting { -3202 // A PTO when we're not expecting any ACKs must be due to handshake -3203 // anti-amplification deadlock prevention. -3204 0 => { -3205 debug_assert!(!self.peer_completed_handshake_address_validation()); -3206 1 -3207 } -3208 // Conventional loss probe -3209 _ => 2, -3210 }; -3211 let pns = self.spaces[space].for_path(path_id); -3212 pns.loss_probes = pns.loss_probes.saturating_add(count); -3213 let path_data = self.path_data_mut(path_id); -3214 path_data.pto_count = path_data.pto_count.saturating_add(1); -3215 self.set_loss_detection_timer(now, path_id); -3216 } -3217 -3218 /// Detect any lost packets -3219 /// -3220 /// There are two cases in which we detects lost packets: -3221 /// -3222 /// - We received an ACK packet. -3223 /// - The [`PathTimer::LossDetection`] timer expired. So there is an un-acknowledged packet -3224 /// that was followed by an acknowledged packet. The loss timer for this -3225 /// un-acknowledged packet expired and we need to detect that packet as lost. -3226 /// -3227 /// Packets are lost if they are both (See RFC9002 §6.1): -3228 /// -3229 /// - Unacknowledged, in flight and sent prior to an acknowledged packet. -3230 /// - Old enough by either: -3231 /// - Having a packet number [`TransportConfig::packet_threshold`] lower then the last -3232 /// acknowledged packet. -3233 /// - Being sent [`TransportConfig::time_threshold`] * RTT in the past. -3234 fn detect_lost_packets( -3235 &mut self, -3236 now: Instant, -3237 pn_space: SpaceId, -3238 path_id: PathId, -3239 due_to_ack: bool, -3240 ) { -3241 let mut lost_packets = Vec::<u64>::new(); -3242 let mut lost_mtu_probe = None; -3243 let mut in_persistent_congestion = false; -3244 let mut size_of_lost_packets = 0u64; -3245 self.spaces[pn_space].for_path(path_id).loss_time = None; -3246 -3247 // Find all the lost packets, populating all variables initialised above. -3248 -3249 let path = self.path_data(path_id); -3250 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -3251 let loss_delay = path -3252 .rtt -3253 .conservative() -3254 .mul_f32(self.config.time_threshold) -3255 .max(TIMER_GRANULARITY); -3256 let first_packet_after_rtt_sample = path.first_packet_after_rtt_sample; -3257 -3258 let largest_acked_packet_pn = self.spaces[pn_space] -3259 .for_path(path_id) -3260 .largest_acked_packet_pn -3261 .expect("detect_lost_packets only to be called if path received at least one ACK"); -3262 let packet_threshold = self.config.packet_threshold as u64; -3263 -3264 // InPersistentCongestion: Determine if all packets in the time period before the newest -3265 // lost packet, including the edges, are marked lost. PTO computation must always -3266 // include max ACK delay, i.e. operate as if in Data space (see RFC9001 §7.6.1). -3267 let congestion_period = self -3268 .pto(SpaceKind::Data, path_id) -3269 .saturating_mul(self.config.persistent_congestion_threshold); -3270 let mut persistent_congestion_start: Option<Instant> = None; -3271 let mut prev_packet = None; -3272 let space = self.spaces[pn_space].for_path(path_id); -3273 -3274 for (packet, info) in space.sent_packets.iter_range(0..largest_acked_packet_pn) { -3275 if prev_packet != Some(packet.wrapping_sub(1)) { -3276 // An intervening packet was acknowledged -3277 persistent_congestion_start = None; -3278 } -3279 -3280 // Packets sent before now - loss_delay are deemed lost. -3281 // However, we avoid subtraction as it can panic and there's no -3282 // saturating equivalent of this subtraction operation with a Duration. -3283 let packet_too_old = now.saturating_duration_since(info.time_sent) >= loss_delay; -3284 if packet_too_old || largest_acked_packet_pn >= packet + packet_threshold { -3285 // The packet should be declared lost. -3286 if Some(packet) == in_flight_mtu_probe { -3287 // Lost MTU probes are not included in `lost_packets`, because they -3288 // should not trigger a congestion control response -3289 lost_mtu_probe = in_flight_mtu_probe; -3290 } else { -3291 lost_packets.push(packet); -3292 size_of_lost_packets += info.size as u64; -3293 if info.ack_eliciting && due_to_ack { -3294 match persistent_congestion_start { -3295 // Two ACK-eliciting packets lost more than -3296 // congestion_period apart, with no ACKed packets in between -3297 Some(start) if info.time_sent - start > congestion_period => { -3298 in_persistent_congestion = true; -3299 } -3300 // Persistent congestion must start after the first RTT sample -3301 None if first_packet_after_rtt_sample -3302 .is_some_and(|x| x < (pn_space.kind(), packet)) => -3303 { -3304 persistent_congestion_start = Some(info.time_sent); -3305 } -3306 _ => {} -3307 } -3308 } -3309 } -3310 } else { -3311 // The packet should not yet be declared lost. -3312 if space.loss_time.is_none() { -3313 // Since we iterate in order the lowest packet number's loss time will -3314 // always be the earliest. -3315 space.loss_time = Some(info.time_sent + loss_delay); -3316 } -3317 persistent_congestion_start = None; -3318 } -3319 -3320 prev_packet = Some(packet); -3321 } -3322 -3323 self.handle_lost_packets( -3324 pn_space, -3325 path_id, -3326 now, -3327 lost_packets, -3328 lost_mtu_probe, -3329 loss_delay, -3330 in_persistent_congestion, -3331 size_of_lost_packets, -3332 ); -3333 } -3334 -3335 /// Drops the path state, declaring any remaining in-flight packets as lost -3336 fn discard_path(&mut self, path_id: PathId, now: Instant) { -3337 trace!(%path_id, "dropping path state"); -3338 let path = self.path_data(path_id); -3339 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); -3340 -3341 let mut size_of_lost_packets = 0u64; // add to path_stats.lost_bytes; -3342 let lost_pns: Vec<_> = self.spaces[SpaceId::Data] -3343 .for_path(path_id) -3344 .sent_packets -3345 .iter() -3346 .filter(|(pn, _info)| Some(*pn) != in_flight_mtu_probe) -3347 .map(|(pn, info)| { -3348 size_of_lost_packets += info.size as u64; -3349 pn -3350 }) -3351 .collect(); -3352 -3353 if !lost_pns.is_empty() { -3354 trace!( -3355 %path_id, -3356 count = lost_pns.len(), -3357 lost_bytes = size_of_lost_packets, -3358 "packets lost on path abandon" -3359 ); -3360 self.handle_lost_packets( -3361 SpaceId::Data, -3362 path_id, -3363 now, -3364 lost_pns, -3365 in_flight_mtu_probe, -3366 Duration::ZERO, -3367 false, -3368 size_of_lost_packets, -3369 ); -3370 } -3371 // Before removing the path, we fetch the final path stats via `Self::path_stats`. -3372 // This updates some values for the last time. -3373 let path_stats = self.path_stats.discard(&path_id); -3374 self.partial_stats += path_stats; -3375 self.paths.remove(&path_id); -3376 self.spaces[SpaceId::Data].number_spaces.remove(&path_id); -3377 -3378 self.events.push_back( -3379 PathEvent::Discarded { -3380 id: path_id, -3381 path_stats: Box::new(path_stats), -3382 } -3383 .into(), -3384 ); -3385 } -3386 -3387 fn handle_lost_packets( -3388 &mut self, -3389 pn_space: SpaceId, -3390 path_id: PathId, -3391 now: Instant, -3392 lost_packets: Vec<u64>, -3393 lost_mtu_probe: Option<u64>, -3394 loss_delay: Duration, -3395 in_persistent_congestion: bool, -3396 size_of_lost_packets: u64, -3397 ) { -3398 debug_assert!(lost_packets.is_sorted(), "lost_packets must be sorted"); -3399 -3400 self.drain_lost_packets(now, pn_space, path_id); -3401 -3402 // OnPacketsLost -3403 if let Some(largest_lost) = lost_packets.last().cloned() { -3404 let old_bytes_in_flight = self.path_data_mut(path_id).in_flight.bytes; -3405 let largest_lost_sent = self.spaces[pn_space] -3406 .for_path(path_id) -3407 .sent_packets -3408 .get(largest_lost) -3409 .unwrap() -3410 .time_sent; -3411 let path_stats = self.path_stats.for_path(path_id); -3412 path_stats.lost_packets += lost_packets.len() as u64; -3413 path_stats.lost_bytes += size_of_lost_packets; -3414 trace!( -3415 %path_id, -3416 count = lost_packets.len(), -3417 lost_bytes = size_of_lost_packets, -3418 "packets lost", -3419 ); -3420 -3421 for &packet in &lost_packets { -3422 let Some(info) = self.spaces[pn_space].for_path(path_id).take(packet) else { -3423 continue; -3424 }; -3425 self.qlog -3426 .emit_packet_lost(packet, &info, loss_delay, pn_space.kind(), now); -3427 self.paths -3428 .get_mut(&path_id) -3429 .unwrap() -3430 .remove_in_flight(&info); -3431 -3432 for frame in info.stream_frames { -3433 self.streams.retransmit(frame); -3434 } -3435 self.spaces[pn_space].pending |= info.retransmits; -3436 let path = self.path_data_mut(path_id); -3437 path.mtud.on_non_probe_lost(packet, info.size); -3438 path.congestion.on_packet_lost(info.size, packet, now); -3439 -3440 self.spaces[pn_space].for_path(path_id).lost_packets.insert( -3441 packet, -3442 LostPacket { -3443 time_sent: info.time_sent, -3444 }, -3445 ); -3446 } -3447 -3448 let path = self.path_data_mut(path_id); -3449 if path.mtud.black_hole_detected(now) { -3450 path.congestion.on_mtu_update(path.mtud.current_mtu()); -3451 if let Some(max_datagram_size) = self.datagrams().max_size() -3452 && self.datagrams.drop_oversized(max_datagram_size) -3453 && self.datagrams.send_blocked -3454 { -3455 self.datagrams.send_blocked = false; -3456 self.events.push_back(Event::DatagramsUnblocked); -3457 } -3458 self.path_stats.for_path(path_id).black_holes_detected += 1; -3459 } -3460 -3461 // Don't apply congestion penalty for lost ack-only packets -3462 let lost_ack_eliciting = -3463 old_bytes_in_flight != self.path_data_mut(path_id).in_flight.bytes; -3464 -3465 if lost_ack_eliciting { -3466 self.path_stats.for_path(path_id).congestion_events += 1; -3467 self.path_data_mut(path_id).congestion.on_congestion_event( -3468 now, -3469 largest_lost_sent, -3470 in_persistent_congestion, -3471 false, -3472 size_of_lost_packets, -3473 largest_lost, -3474 ); -3475 } -3476 } +3100 // If this ACK frame acknowledged all deemed lost packets, +3101 // then we have raised a spurious congestion event in the past. +3102 // We cannot conclude when there are remaining packets, +3103 // but future ACK frames might indicate a spurious loss detection. +3104 lost_packets.is_empty() +3105 } +3106 +3107 /// Drain lost packets that we reasonably think will never arrive +3108 /// +3109 /// The current criterion is copied from `msquic`: +3110 /// discard packets that were sent earlier than 2 probe timeouts ago. +3111 fn drain_lost_packets(&mut self, now: Instant, space: SpaceId, path: PathId) { +3112 let two_pto = 2 * self.path_data(path).rtt.pto_base(); +3113 +3114 let lost_packets = &mut self.spaces[space].for_path(path).lost_packets; +3115 lost_packets.retain(|_pn, info| now.saturating_duration_since(info.time_sent) <= two_pto); +3116 } +3117 +3118 /// Process a new ECN block from an in-order ACK +3119 fn process_ecn( +3120 &mut self, +3121 now: Instant, +3122 space: SpaceId, +3123 path: PathId, +3124 newly_acked_pn: u64, +3125 ecn: frame::EcnCounts, +3126 largest_sent_time: Instant, +3127 largest_sent_pn: u64, +3128 ) { +3129 match self.spaces[space] +3130 .for_path(path) +3131 .detect_ecn(newly_acked_pn, ecn) +3132 { +3133 Err(e) => { +3134 debug!("halting ECN due to verification failure: {}", e); +3135 +3136 self.path_data_mut(path).sending_ecn = false; +3137 // Wipe out the existing value because it might be garbage and could interfere with +3138 // future attempts to use ECN on new paths. +3139 self.spaces[space].for_path(path).ecn_feedback = frame::EcnCounts::ZERO; +3140 } +3141 Ok(false) => {} +3142 Ok(true) => { +3143 self.path_stats.get_mut(path).congestion_events += 1; +3144 self.path_data_mut(path).congestion.on_congestion_event( +3145 now, +3146 largest_sent_time, +3147 false, +3148 true, +3149 0, +3150 largest_sent_pn, +3151 ); +3152 } +3153 } +3154 } +3155 +3156 // Not timing-aware, so it's safe to call this for inferred acks, such as arise from +3157 // high-latency handshakes +3158 fn on_packet_acked(&mut self, now: Instant, path_id: PathId, pn: u64, info: SentPacket) { +3159 let path = self.path_data_mut(path_id); +3160 let app_limited = path.app_limited; +3161 path.remove_in_flight(&info); +3162 if info.ack_eliciting && info.path_generation == path.generation() { +3163 // Only pass ACKs to the congestion controller if it belongs to this exact +3164 // generation of the path. Otherwise we might be feeding ACKs from the previous +3165 // 4-tuple into our congestion controller. +3166 let rtt = path.rtt; +3167 path.congestion +3168 .on_ack(now, info.time_sent, info.size.into(), pn, app_limited, &rtt); +3169 } +3170 +3171 // Update state for confirmed delivery of frames +3172 if let Some(retransmits) = info.retransmits.get() { +3173 for (id, _) in retransmits.reset_stream.iter() { +3174 self.streams.reset_acked(*id); +3175 } +3176 } +3177 +3178 for frame in info.stream_frames { +3179 self.streams.received_ack_of(frame); +3180 } +3181 } +3182 +3183 fn set_key_discard_timer(&mut self, now: Instant, space: SpaceKind) { +3184 let start = if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) { +3185 now +3186 } else { +3187 self.crypto_state +3188 .prev_crypto +3189 .as_ref() +3190 .expect("no previous keys") +3191 .end_packet +3192 .as_ref() +3193 .expect("update not acknowledged yet") +3194 .1 +3195 }; +3196 +3197 // QUIC-MULTIPATH § 2.5 Key Phase Update Process: use largest PTO of all paths. +3198 self.timers.set( +3199 Timer::Conn(ConnTimer::KeyDiscard), +3200 start + self.max_pto_for_space(space) * 3, +3201 self.qlog.with_time(now), +3202 ); +3203 } +3204 +3205 /// Handle a [`PathTimer::LossDetection`] timeout. +3206 /// +3207 /// This timer expires for two reasons: +3208 /// - An ACK-eliciting packet we sent should be considered lost. +3209 /// - The PTO may have expired and a tail-loss probe needs to be scheduled. +3210 /// +3211 /// The former needs us to schedule re-transmission of the lost data. +3212 /// +3213 /// The latter means we have not received an ACK for an ack-eliciting packet we sent +3214 /// within the PTO time-window. We need to schedule a tail-loss probe, an ack-eliciting +3215 /// packet, to try and elicit new acknowledgements. These new acknowledgements will +3216 /// indicate whether the previously sent packets were lost or not. +3217 fn on_loss_detection_timeout(&mut self, now: Instant, path_id: PathId) { +3218 if let Some((_, pn_space)) = self.loss_time_and_space(path_id) { +3219 // Time threshold loss Detection +3220 self.detect_lost_packets(now, pn_space, path_id, false); +3221 self.set_loss_detection_timer(now, path_id); +3222 return; +3223 } +3224 +3225 let Some((_, space)) = self.pto_time_and_space(now, path_id) else { +3226 error!(%path_id, "PTO expired while unset"); +3227 return; +3228 }; +3229 trace!( +3230 in_flight = self.path_data(path_id).in_flight.bytes, +3231 count = self.path_data(path_id).pto_count, +3232 ?space, +3233 %path_id, +3234 "PTO fired" +3235 ); +3236 +3237 let count = match self.path_data(path_id).in_flight.ack_eliciting { +3238 // A PTO when we're not expecting any ACKs must be due to handshake +3239 // anti-amplification deadlock prevention. +3240 0 => { +3241 debug_assert!(!self.peer_completed_handshake_address_validation()); +3242 1 +3243 } +3244 // Conventional loss probe +3245 _ => 2, +3246 }; +3247 let pns = self.spaces[space].for_path(path_id); +3248 pns.loss_probes = pns.loss_probes.saturating_add(count); +3249 let path_data = self.path_data_mut(path_id); +3250 path_data.pto_count = path_data.pto_count.saturating_add(1); +3251 self.set_loss_detection_timer(now, path_id); +3252 } +3253 +3254 /// Detect any lost packets +3255 /// +3256 /// There are two cases in which we detects lost packets: +3257 /// +3258 /// - We received an ACK packet. +3259 /// - The [`PathTimer::LossDetection`] timer expired. So there is an un-acknowledged packet +3260 /// that was followed by an acknowledged packet. The loss timer for this +3261 /// un-acknowledged packet expired and we need to detect that packet as lost. +3262 /// +3263 /// Packets are lost if they are both (See RFC9002 §6.1): +3264 /// +3265 /// - Unacknowledged, in flight and sent prior to an acknowledged packet. +3266 /// - Old enough by either: +3267 /// - Having a packet number [`TransportConfig::packet_threshold`] lower then the last +3268 /// acknowledged packet. +3269 /// - Being sent [`TransportConfig::time_threshold`] * RTT in the past. +3270 fn detect_lost_packets( +3271 &mut self, +3272 now: Instant, +3273 pn_space: SpaceId, +3274 path_id: PathId, +3275 due_to_ack: bool, +3276 ) { +3277 let mut lost_packets = Vec::<u64>::new(); +3278 let mut lost_mtu_probe = None; +3279 let mut in_persistent_congestion = false; +3280 let mut size_of_lost_packets = 0u64; +3281 self.spaces[pn_space].for_path(path_id).loss_time = None; +3282 +3283 // Find all the lost packets, populating all variables initialised above. +3284 +3285 let path = self.path_data(path_id); +3286 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); +3287 let loss_delay = path +3288 .rtt +3289 .conservative() +3290 .mul_f32(self.config.time_threshold) +3291 .max(TIMER_GRANULARITY); +3292 let first_packet_after_rtt_sample = path.first_packet_after_rtt_sample; +3293 +3294 let largest_acked_packet_pn = self.spaces[pn_space] +3295 .for_path(path_id) +3296 .largest_acked_packet_pn +3297 .expect("detect_lost_packets only to be called if path received at least one ACK"); +3298 let packet_threshold = self.config.packet_threshold as u64; +3299 +3300 // InPersistentCongestion: Determine if all packets in the time period before the newest +3301 // lost packet, including the edges, are marked lost. PTO computation must always +3302 // include max ACK delay, i.e. operate as if in Data space (see RFC9001 §7.6.1). +3303 let congestion_period = self +3304 .pto(SpaceKind::Data, path_id) +3305 .saturating_mul(self.config.persistent_congestion_threshold); +3306 let mut persistent_congestion_start: Option<Instant> = None; +3307 let mut prev_packet = None; +3308 let space = self.spaces[pn_space].for_path(path_id); +3309 +3310 for (packet, info) in space.sent_packets.iter_range(0..largest_acked_packet_pn) { +3311 if prev_packet != Some(packet.wrapping_sub(1)) { +3312 // An intervening packet was acknowledged +3313 persistent_congestion_start = None; +3314 } +3315 +3316 // Packets sent before now - loss_delay are deemed lost. +3317 // However, we avoid subtraction as it can panic and there's no +3318 // saturating equivalent of this subtraction operation with a Duration. +3319 let packet_too_old = now.saturating_duration_since(info.time_sent) >= loss_delay; +3320 if packet_too_old || largest_acked_packet_pn >= packet + packet_threshold { +3321 // The packet should be declared lost. +3322 if Some(packet) == in_flight_mtu_probe { +3323 // Lost MTU probes are not included in `lost_packets`, because they +3324 // should not trigger a congestion control response +3325 lost_mtu_probe = in_flight_mtu_probe; +3326 } else { +3327 lost_packets.push(packet); +3328 size_of_lost_packets += info.size as u64; +3329 if info.ack_eliciting && due_to_ack { +3330 match persistent_congestion_start { +3331 // Two ACK-eliciting packets lost more than +3332 // congestion_period apart, with no ACKed packets in between +3333 Some(start) if info.time_sent - start > congestion_period => { +3334 in_persistent_congestion = true; +3335 } +3336 // Persistent congestion must start after the first RTT sample +3337 None if first_packet_after_rtt_sample +3338 .is_some_and(|x| x < (pn_space.kind(), packet)) => +3339 { +3340 persistent_congestion_start = Some(info.time_sent); +3341 } +3342 _ => {} +3343 } +3344 } +3345 } +3346 } else { +3347 // The packet should not yet be declared lost. +3348 if space.loss_time.is_none() { +3349 // Since we iterate in order the lowest packet number's loss time will +3350 // always be the earliest. +3351 space.loss_time = Some(info.time_sent + loss_delay); +3352 } +3353 persistent_congestion_start = None; +3354 } +3355 +3356 prev_packet = Some(packet); +3357 } +3358 +3359 self.handle_lost_packets( +3360 pn_space, +3361 path_id, +3362 now, +3363 lost_packets, +3364 lost_mtu_probe, +3365 loss_delay, +3366 in_persistent_congestion, +3367 size_of_lost_packets, +3368 ); +3369 } +3370 +3371 /// Drops the path state, declaring any remaining in-flight packets as lost +3372 fn discard_path(&mut self, path_id: PathId, now: Instant) { +3373 trace!(%path_id, "dropping path state"); +3374 let path = self.path_data(path_id); +3375 let in_flight_mtu_probe = path.mtud.in_flight_mtu_probe(); +3376 +3377 let mut size_of_lost_packets = 0u64; // add to path_stats.lost_bytes; +3378 let lost_pns: Vec<_> = self.spaces[SpaceId::Data] +3379 .for_path(path_id) +3380 .sent_packets +3381 .iter() +3382 .filter(|(pn, _info)| Some(*pn) != in_flight_mtu_probe) +3383 .map(|(pn, info)| { +3384 size_of_lost_packets += info.size as u64; +3385 pn +3386 }) +3387 .collect(); +3388 +3389 if !lost_pns.is_empty() { +3390 trace!( +3391 %path_id, +3392 count = lost_pns.len(), +3393 lost_bytes = size_of_lost_packets, +3394 "packets lost on path abandon" +3395 ); +3396 self.handle_lost_packets( +3397 SpaceId::Data, +3398 path_id, +3399 now, +3400 lost_pns, +3401 in_flight_mtu_probe, +3402 Duration::ZERO, +3403 false, +3404 size_of_lost_packets, +3405 ); +3406 } +3407 // Before removing the path, we fetch the final path stats via `Self::path_stats`. +3408 // This ensures snapshot values (like rtt) are properly updated. +3409 let path_stats = self.path_stats(path_id).unwrap_or_default(); +3410 self.path_stats.discard(&path_id); +3411 self.partial_stats += path_stats; +3412 self.paths.remove(&path_id); +3413 self.spaces[SpaceId::Data].number_spaces.remove(&path_id); +3414 +3415 self.events.push_back( +3416 PathEvent::Discarded { +3417 id: path_id, +3418 path_stats: Box::new(path_stats), +3419 } +3420 .into(), +3421 ); +3422 } +3423 +3424 fn handle_lost_packets( +3425 &mut self, +3426 pn_space: SpaceId, +3427 path_id: PathId, +3428 now: Instant, +3429 lost_packets: Vec<u64>, +3430 lost_mtu_probe: Option<u64>, +3431 loss_delay: Duration, +3432 in_persistent_congestion: bool, +3433 size_of_lost_packets: u64, +3434 ) { +3435 debug_assert!(lost_packets.is_sorted(), "lost_packets must be sorted"); +3436 +3437 self.drain_lost_packets(now, pn_space, path_id); +3438 +3439 // OnPacketsLost +3440 if let Some(largest_lost) = lost_packets.last().cloned() { +3441 let old_bytes_in_flight = self.path_data_mut(path_id).in_flight.bytes; +3442 let largest_lost_sent = self.spaces[pn_space] +3443 .for_path(path_id) +3444 .sent_packets +3445 .get(largest_lost) +3446 .unwrap() +3447 .time_sent; +3448 let path_stats = self.path_stats.get_mut(path_id); +3449 path_stats.lost_packets += lost_packets.len() as u64; +3450 path_stats.lost_bytes += size_of_lost_packets; +3451 trace!( +3452 %path_id, +3453 count = lost_packets.len(), +3454 lost_bytes = size_of_lost_packets, +3455 "packets lost", +3456 ); +3457 +3458 for &packet in &lost_packets { +3459 let Some(info) = self.spaces[pn_space].for_path(path_id).take(packet) else { +3460 continue; +3461 }; +3462 self.qlog +3463 .emit_packet_lost(packet, &info, loss_delay, pn_space.kind(), now); +3464 self.paths +3465 .get_mut(&path_id) +3466 .unwrap() +3467 .remove_in_flight(&info); +3468 +3469 for frame in info.stream_frames { +3470 self.streams.retransmit(frame); +3471 } +3472 self.spaces[pn_space].pending |= info.retransmits; +3473 let path = self.path_data_mut(path_id); +3474 path.pending |= info.path_retransmits; +3475 path.mtud.on_non_probe_lost(packet, info.size); +3476 path.congestion.on_packet_lost(info.size, packet, now); 3477 -3478 // Handle a lost MTU probe -3479 if let Some(packet) = lost_mtu_probe { -3480 let info = self.spaces[SpaceId::Data] -3481 .for_path(path_id) -3482 .take(packet) -3483 .unwrap(); // safe: lost_mtu_probe is omitted from lost_packets, and -3484 // therefore must not have been removed yet -3485 self.paths -3486 .get_mut(&path_id) -3487 .unwrap() -3488 .remove_in_flight(&info); -3489 self.path_data_mut(path_id).mtud.on_probe_lost(); -3490 self.path_stats.for_path(path_id).lost_plpmtud_probes += 1; -3491 } -3492 } -3493 -3494 /// Returns the earliest time packets should be declared lost for all spaces on a path. -3495 /// -3496 /// If a path has an acknowledged packet with any prior un-acknowledged packets, the -3497 /// earliest un-acknowledged packet can be declared lost after a timeout has elapsed. -3498 /// The time returned is when this packet should be declared lost. -3499 fn loss_time_and_space(&self, path_id: PathId) -> Option<(Instant, SpaceId)> { -3500 SpaceId::iter() -3501 .filter_map(|id| { -3502 self.spaces[id] -3503 .number_spaces -3504 .get(&path_id) -3505 .and_then(|pns| pns.loss_time) -3506 .map(|time| (time, id)) -3507 }) -3508 .min_by_key(|&(time, _)| time) -3509 } -3510 -3511 /// Returns the earliest next PTO should fire for all spaces on a path. -3512 /// -3513 /// This needs to be fully deterministic because it is also used to determine the PTO -3514 /// that fired, not just to set the next timer. So if it fired in the past it needs to -3515 /// return the time from the past at which it fired. -3516 /// -3517 /// This is the next time a tail-loss probe should be sent. -3518 fn pto_time_and_space(&mut self, now: Instant, path_id: PathId) -> Option<(Instant, SpaceId)> { -3519 let path = self.path(path_id)?; -3520 let pto_count = path.pto_count; -3521 -3522 // Cap the maximum interval between two tail-loss probes. -3523 let max_interval = if path.rtt.get() > SLOW_RTT_THRESHOLD { -3524 // For slow links we want to increase the interval beyond 2s. -3525 (path.rtt.get() * 3) / 2 -3526 } else if let Some(idle) = path.idle_timeout.or(self.idle_timeout) -3527 && idle <= MIN_IDLE_FOR_FAST_PTO -3528 { -3529 // If the idle timeout is relatively low, cap at 1s so we get plenty of retries -3530 // before the idle timeout fires. -3531 MAX_PTO_FAST_INTERVAL -3532 } else { -3533 // Otherwise cap to 2s. -3534 MAX_PTO_INTERVAL -3535 }; -3536 -3537 if path_id == PathId::ZERO -3538 && path.in_flight.ack_eliciting == 0 -3539 && !self.peer_completed_handshake_address_validation() -3540 { -3541 // Address Validation during Connection Establishment: -3542 // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1. To prevent a -3543 // deadlock if an Initial or Handshake packet from the server is lost and the -3544 // server can not send more due to its anti-amplification limit the client must -3545 // send another packet on PTO. -3546 let space = match self.highest_space { -3547 SpaceKind::Handshake => SpaceId::Handshake, -3548 _ => SpaceId::Initial, -3549 }; -3550 -3551 let backoff = 2u32.pow(path.pto_count.min(MAX_BACKOFF_EXPONENT)); -3552 let duration = path.rtt.pto_base() * backoff; -3553 let duration = duration.min(max_interval); -3554 return Some((now + duration, space)); -3555 } -3556 -3557 let mut result = None; -3558 for space in SpaceId::iter() { -3559 let Some(pns) = self.spaces[space].number_spaces.get(&path_id) else { -3560 continue; -3561 }; -3562 -3563 if space == SpaceId::Data && !self.is_handshake_confirmed() { -3564 // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-7: -3565 // An endpoint MUST NOT set its PTO timer for the Application Data packet -3566 // number space until the handshake is confirmed. -3567 continue; -3568 } -3569 -3570 if !pns.has_in_flight() { -3571 continue; -3572 } -3573 -3574 // Compute the PTO duration for this space, we want to cap the maximum interval -3575 // between two tail-loss probes so to not do a simple exponential backoff but -3576 // rather iterate through the probes to compute the capped increment for an -3577 // exponential backoff at each step. -3578 let duration = { -3579 let max_ack_delay = if space == SpaceId::Data { -3580 self.ack_frequency.max_ack_delay_for_pto() -3581 } else { -3582 Duration::ZERO -3583 }; -3584 let pto_base = path.rtt.pto_base() + max_ack_delay; -3585 let mut duration = pto_base; -3586 for i in 1..=pto_count { -3587 let exponential_duration = pto_base * 2u32.pow(i.min(MAX_BACKOFF_EXPONENT)); -3588 let max_duration = duration + max_interval; -3589 duration = exponential_duration.min(max_duration); -3590 } -3591 duration -3592 }; -3593 -3594 let Some(last_ack_eliciting) = pns.time_of_last_ack_eliciting_packet else { -3595 continue; -3596 }; -3597 // Base the deadline on when the last probe was sent, so the PTO -3598 // doesn't fire before the response has had time to arrive. -3599 let pto = last_ack_eliciting + duration; -3600 if result.is_none_or(|(earliest_pto, _)| pto < earliest_pto) { -3601 if path.anti_amplification_blocked(1) { -3602 // Nothing would be able to be sent. -3603 continue; -3604 } -3605 if path.in_flight.ack_eliciting == 0 { -3606 // Nothing ack-eliciting, no PTO to arm/fire. -3607 continue; -3608 } -3609 result = Some((pto, space)); +3478 self.spaces[pn_space].for_path(path_id).lost_packets.insert( +3479 packet, +3480 LostPacket { +3481 time_sent: info.time_sent, +3482 }, +3483 ); +3484 } +3485 +3486 let path = self.path_data_mut(path_id); +3487 if path.mtud.black_hole_detected(now) { +3488 path.congestion.on_mtu_update(path.mtud.current_mtu()); +3489 if let Some(max_datagram_size) = self.datagrams().max_size() +3490 && self.datagrams.drop_oversized(max_datagram_size) +3491 && self.datagrams.send_blocked +3492 { +3493 self.datagrams.send_blocked = false; +3494 self.events.push_back(Event::DatagramsUnblocked); +3495 } +3496 self.path_stats.get_mut(path_id).black_holes_detected += 1; +3497 } +3498 +3499 // Don't apply congestion penalty for lost ack-only packets +3500 let lost_ack_eliciting = +3501 old_bytes_in_flight != self.path_data_mut(path_id).in_flight.bytes; +3502 +3503 if lost_ack_eliciting { +3504 self.path_stats.get_mut(path_id).congestion_events += 1; +3505 self.path_data_mut(path_id).congestion.on_congestion_event( +3506 now, +3507 largest_lost_sent, +3508 in_persistent_congestion, +3509 false, +3510 size_of_lost_packets, +3511 largest_lost, +3512 ); +3513 } +3514 } +3515 +3516 // Handle a lost MTU probe +3517 if let Some(packet) = lost_mtu_probe { +3518 let info = self.spaces[SpaceId::Data] +3519 .for_path(path_id) +3520 .take(packet) +3521 .unwrap(); // safe: lost_mtu_probe is omitted from lost_packets, and +3522 // therefore must not have been removed yet +3523 self.paths +3524 .get_mut(&path_id) +3525 .unwrap() +3526 .remove_in_flight(&info); +3527 self.path_data_mut(path_id).mtud.on_probe_lost(); +3528 self.path_stats.get_mut(path_id).lost_plpmtud_probes += 1; +3529 } +3530 } +3531 +3532 /// Returns the earliest time packets should be declared lost for all spaces on a path. +3533 /// +3534 /// If a path has an acknowledged packet with any prior un-acknowledged packets, the +3535 /// earliest un-acknowledged packet can be declared lost after a timeout has elapsed. +3536 /// The time returned is when this packet should be declared lost. +3537 fn loss_time_and_space(&self, path_id: PathId) -> Option<(Instant, SpaceId)> { +3538 SpaceId::iter() +3539 .filter_map(|id| { +3540 self.spaces[id] +3541 .number_spaces +3542 .get(&path_id) +3543 .and_then(|pns| pns.loss_time) +3544 .map(|time| (time, id)) +3545 }) +3546 .min_by_key(|&(time, _)| time) +3547 } +3548 +3549 /// Returns the earliest next PTO should fire for all spaces on a path. +3550 /// +3551 /// This needs to be fully deterministic because it is also used to determine the PTO +3552 /// that fired, not just to set the next timer. So if it fired in the past it needs to +3553 /// return the time from the past at which it fired. +3554 /// +3555 /// This is the next time a tail-loss probe should be sent. +3556 fn pto_time_and_space(&mut self, now: Instant, path_id: PathId) -> Option<(Instant, SpaceId)> { +3557 let path = self.path(path_id)?; +3558 let pto_count = path.pto_count; +3559 +3560 // Cap the maximum interval between two tail-loss probes. +3561 let max_interval = if path.rtt.get() > SLOW_RTT_THRESHOLD { +3562 // For slow links we want to increase the interval beyond 2s. +3563 (path.rtt.get() * 3) / 2 +3564 } else if let Some(idle) = path.idle_timeout.or(self.idle_timeout) +3565 && idle <= MIN_IDLE_FOR_FAST_PTO +3566 { +3567 // If the idle timeout is relatively low, cap at 1s so we get plenty of retries +3568 // before the idle timeout fires. +3569 MAX_PTO_FAST_INTERVAL +3570 } else { +3571 // Otherwise cap to 2s. +3572 MAX_PTO_INTERVAL +3573 }; +3574 +3575 if path_id == PathId::ZERO +3576 && path.in_flight.ack_eliciting == 0 +3577 && !self.peer_completed_handshake_address_validation() +3578 { +3579 // Address Validation during Connection Establishment: +3580 // https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1. To prevent a +3581 // deadlock if an Initial or Handshake packet from the server is lost and the +3582 // server can not send more due to its anti-amplification limit the client must +3583 // send another packet on PTO. +3584 let space = match self.highest_space { +3585 SpaceKind::Handshake => SpaceId::Handshake, +3586 _ => SpaceId::Initial, +3587 }; +3588 +3589 let backoff = 2u32.pow(path.pto_count.min(MAX_BACKOFF_EXPONENT)); +3590 let duration = path.rtt.pto_base() * backoff; +3591 let duration = duration.min(max_interval); +3592 return Some((now + duration, space)); +3593 } +3594 +3595 let mut result = None; +3596 for space in SpaceId::iter() { +3597 let Some(pns) = self.spaces[space].number_spaces.get(&path_id) else { +3598 continue; +3599 }; +3600 +3601 if space == SpaceId::Data && !self.is_handshake_confirmed() { +3602 // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-7: +3603 // An endpoint MUST NOT set its PTO timer for the Application Data packet +3604 // number space until the handshake is confirmed. +3605 continue; +3606 } +3607 +3608 if !pns.has_in_flight() { +3609 continue; 3610 } -3611 } -3612 result -3613 } -3614 -3615 /// Whether the peer validated our address in the connection handshake. -3616 fn peer_completed_handshake_address_validation(&self) -> bool { -3617 if self.side.is_server() || self.state.is_closed() { -3618 return true; -3619 } -3620 // The server is guaranteed to have validated our address if any of our handshake or -3621 // 1-RTT packets are acknowledged or we've seen HANDSHAKE_DONE and discarded -3622 // handshake keys. -3623 self.spaces[SpaceId::Handshake] -3624 .path_space(PathId::ZERO) -3625 .and_then(|pns| pns.largest_acked_packet_pn) -3626 .is_some() -3627 || self.spaces[SpaceId::Data] -3628 .path_space(PathId::ZERO) -3629 .and_then(|pns| pns.largest_acked_packet_pn) -3630 .is_some() -3631 || (self.crypto_state.has_keys(EncryptionLevel::OneRtt) -3632 && !self.crypto_state.has_keys(EncryptionLevel::Handshake)) -3633 } -3634 -3635 /// Resets the the [`PathTimer::LossDetection`] timer to the next instant it may be needed -3636 /// -3637 /// The timer must fire if either: -3638 /// - An ack-eliciting packet we sent needs to be declared lost. -3639 /// - A tail-loss probe needs to be sent. -3640 /// -3641 /// See [`Connection::on_loss_detection_timeout`] for details. -3642 fn set_loss_detection_timer(&mut self, now: Instant, path_id: PathId) { -3643 if self.state.is_closed() { -3644 // No loss detection takes place on closed connections, and `close_common` already -3645 // stopped time timer. Ensure we don't restart it inadvertently, e.g. in response to a -3646 // reordered packet being handled by state-insensitive code. -3647 return; -3648 } -3649 -3650 if let Some((loss_time, _)) = self.loss_time_and_space(path_id) { -3651 // Time threshold loss detection. -3652 self.timers.set( -3653 Timer::PerPath(path_id, PathTimer::LossDetection), -3654 loss_time, -3655 self.qlog.with_time(now), -3656 ); -3657 return; -3658 } -3659 -3660 // Determine which PN space to arm PTO for. -3661 // We can only send tail-loss probes on paths that aren't abandoned yet. -3662 if !self.abandoned_paths.contains(&path_id) -3663 && let Some((timeout, _)) = self.pto_time_and_space(now, path_id) -3664 { -3665 self.timers.set( -3666 Timer::PerPath(path_id, PathTimer::LossDetection), -3667 timeout, -3668 self.qlog.with_time(now), -3669 ); -3670 } else { -3671 self.timers.stop( -3672 Timer::PerPath(path_id, PathTimer::LossDetection), -3673 self.qlog.with_time(now), -3674 ); -3675 } -3676 } -3677 -3678 /// The maximum probe timeout across all paths -3679 /// -3680 /// See [`Connection::pto`] -3681 fn max_pto_for_space(&self, space: SpaceKind) -> Duration { -3682 self.paths -3683 .keys() -3684 .map(|path_id| self.pto(space, *path_id)) -3685 .max() -3686 .unwrap_or_else(|| { -3687 // No paths remain (e.g. last path was abandoned and the NoAvailablePath grace timer -3688 // fired before any new path was opened). Fall back to a PTO derived from the -3689 // configured initial RTT, matching RFC 9002 §6.2.2 initial values. -3690 let rtt = self.config.initial_rtt; -3691 let max_ack_delay = match space { -3692 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, -3693 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), -3694 }; -3695 rtt + cmp::max(4 * (rtt / 2), TIMER_GRANULARITY) + max_ack_delay -3696 }) -3697 } -3698 -3699 /// Probe Timeout -3700 /// -3701 /// The PTO is logically the time in which you'd expect to receive an acknowledgement -3702 /// for a packet. So approximately RTT + max_ack_delay. -3703 fn pto(&self, space: SpaceKind, path_id: PathId) -> Duration { -3704 let max_ack_delay = match space { -3705 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, -3706 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), -3707 }; -3708 self.path_data(path_id).rtt.pto_base() + max_ack_delay -3709 } -3710 -3711 fn on_packet_authenticated( -3712 &mut self, -3713 now: Instant, -3714 space_id: SpaceKind, -3715 path_id: PathId, -3716 ecn: Option<EcnCodepoint>, -3717 packet_number: Option<u64>, -3718 spin: bool, -3719 is_1rtt: bool, -3720 remote: &FourTuple, -3721 ) { -3722 // During the handshake we already have discarded packets that do not match the path -3723 // remote. So any off-path packet here is either a probing packet or a -3724 // migration. Handling probing packets here means that the path's idle timeout will -3725 // be reset and will delay detecting the path as idle. However tail-loss probes -3726 // would still not get acknowledged if the path was broken so eventually the path -3727 // would still become idle. -3728 let is_on_path = self -3729 .path_data(path_id) -3730 .network_path -3731 .is_probably_same_path(remote); -3732 -3733 self.total_authed_packets += 1; -3734 self.reset_keep_alive(path_id, now); -3735 self.reset_idle_timeout(now, space_id, path_id); -3736 self.path_data_mut(path_id).permit_idle_reset = true; -3737 -3738 // Do not process ECN for off-path packets. If this is a migration we'll get ECN -3739 // back once we've migrated. -3740 if is_on_path { -3741 self.receiving_ecn |= ecn.is_some(); -3742 if let Some(x) = ecn { -3743 let space = &mut self.spaces[space_id]; -3744 space.for_path(path_id).ecn_counters += x; -3745 -3746 if x.is_ce() { -3747 space -3748 .for_path(path_id) -3749 .pending_acks -3750 .set_immediate_ack_required(); -3751 } -3752 } -3753 } -3754 -3755 let Some(packet_number) = packet_number else { -3756 return; -3757 }; -3758 match &self.side { -3759 ConnectionSide::Client { .. } => { -3760 // If we received a handshake packet that authenticated, then we're talking to -3761 // the real server. From now on we should no longer allow the server to migrate -3762 // its address. -3763 if space_id == SpaceKind::Handshake -3764 && let Some(hs) = self.state.as_handshake_mut() -3765 { -3766 hs.allow_server_migration = false; -3767 } -3768 } -3769 ConnectionSide::Server { .. } => { -3770 if self.crypto_state.has_keys(EncryptionLevel::Initial) -3771 && space_id == SpaceKind::Handshake -3772 { -3773 // A server stops sending and processing Initial packets when it receives its first Handshake packet. -3774 self.discard_space(now, SpaceKind::Initial); -3775 } -3776 if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) && is_1rtt { -3777 // Discard 0-RTT keys soon after receiving a 1-RTT packet -3778 self.set_key_discard_timer(now, space_id) -3779 } -3780 } -3781 } -3782 let space = self.spaces[space_id].for_path(path_id); +3611 +3612 // Compute the PTO duration for this space, we want to cap the maximum interval +3613 // between two tail-loss probes so to not do a simple exponential backoff but +3614 // rather iterate through the probes to compute the capped increment for an +3615 // exponential backoff at each step. +3616 let duration = { +3617 let max_ack_delay = if space == SpaceId::Data { +3618 self.ack_frequency.max_ack_delay_for_pto() +3619 } else { +3620 Duration::ZERO +3621 }; +3622 let pto_base = path.rtt.pto_base() + max_ack_delay; +3623 let mut duration = pto_base; +3624 for i in 1..=pto_count { +3625 let exponential_duration = pto_base * 2u32.pow(i.min(MAX_BACKOFF_EXPONENT)); +3626 let max_duration = duration + max_interval; +3627 duration = exponential_duration.min(max_duration); +3628 } +3629 duration +3630 }; +3631 +3632 let Some(last_ack_eliciting) = pns.time_of_last_ack_eliciting_packet else { +3633 continue; +3634 }; +3635 // Base the deadline on when the last probe was sent, so the PTO +3636 // doesn't fire before the response has had time to arrive. +3637 let pto = last_ack_eliciting + duration; +3638 if result.is_none_or(|(earliest_pto, _)| pto < earliest_pto) { +3639 if path.anti_amplification_blocked(1) { +3640 // Nothing would be able to be sent. +3641 continue; +3642 } +3643 if path.in_flight.ack_eliciting == 0 { +3644 // Nothing ack-eliciting, no PTO to arm/fire. +3645 continue; +3646 } +3647 result = Some((pto, space)); +3648 } +3649 } +3650 result +3651 } +3652 +3653 /// Whether the peer validated our address in the connection handshake. +3654 fn peer_completed_handshake_address_validation(&self) -> bool { +3655 if self.side.is_server() || self.state.is_closed() { +3656 return true; +3657 } +3658 // The server is guaranteed to have validated our address if any of our handshake or +3659 // 1-RTT packets are acknowledged or we've seen HANDSHAKE_DONE and discarded +3660 // handshake keys. +3661 self.spaces[SpaceId::Handshake] +3662 .path_space(PathId::ZERO) +3663 .and_then(|pns| pns.largest_acked_packet_pn) +3664 .is_some() +3665 || self.spaces[SpaceId::Data] +3666 .path_space(PathId::ZERO) +3667 .and_then(|pns| pns.largest_acked_packet_pn) +3668 .is_some() +3669 || (self.crypto_state.has_keys(EncryptionLevel::OneRtt) +3670 && !self.crypto_state.has_keys(EncryptionLevel::Handshake)) +3671 } +3672 +3673 /// Resets the the [`PathTimer::LossDetection`] timer to the next instant it may be needed +3674 /// +3675 /// The timer must fire if either: +3676 /// - An ack-eliciting packet we sent needs to be declared lost. +3677 /// - A tail-loss probe needs to be sent. +3678 /// +3679 /// See [`Connection::on_loss_detection_timeout`] for details. +3680 fn set_loss_detection_timer(&mut self, now: Instant, path_id: PathId) { +3681 if self.state.is_closed() { +3682 // No loss detection takes place on closed connections, and `close_common` already +3683 // stopped time timer. Ensure we don't restart it inadvertently, e.g. in response to a +3684 // reordered packet being handled by state-insensitive code. +3685 return; +3686 } +3687 +3688 if let Some((loss_time, _)) = self.loss_time_and_space(path_id) { +3689 // Time threshold loss detection. +3690 self.timers.set( +3691 Timer::PerPath(path_id, PathTimer::LossDetection), +3692 loss_time, +3693 self.qlog.with_time(now), +3694 ); +3695 return; +3696 } +3697 +3698 // Determine which PN space to arm PTO for. +3699 // We can only send tail-loss probes on paths that aren't abandoned yet. +3700 if !self.abandoned_paths.contains(&path_id) +3701 && let Some((timeout, _)) = self.pto_time_and_space(now, path_id) +3702 { +3703 self.timers.set( +3704 Timer::PerPath(path_id, PathTimer::LossDetection), +3705 timeout, +3706 self.qlog.with_time(now), +3707 ); +3708 } else { +3709 self.timers.stop( +3710 Timer::PerPath(path_id, PathTimer::LossDetection), +3711 self.qlog.with_time(now), +3712 ); +3713 } +3714 } +3715 +3716 /// The maximum probe timeout across all paths +3717 /// +3718 /// See [`Connection::pto`] +3719 fn max_pto_for_space(&self, space: SpaceKind) -> Duration { +3720 self.paths +3721 .keys() +3722 .map(|path_id| self.pto(space, *path_id)) +3723 .max() +3724 .unwrap_or_else(|| { +3725 // No paths remain (e.g. last path was abandoned and the NoAvailablePath grace timer +3726 // fired before any new path was opened). Fall back to a PTO derived from the +3727 // configured initial RTT, matching RFC 9002 §6.2.2 initial values. +3728 let rtt = self.config.initial_rtt; +3729 let max_ack_delay = match space { +3730 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, +3731 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), +3732 }; +3733 rtt + cmp::max(4 * (rtt / 2), TIMER_GRANULARITY) + max_ack_delay +3734 }) +3735 } +3736 +3737 /// Probe Timeout +3738 /// +3739 /// The PTO is logically the time in which you'd expect to receive an acknowledgement +3740 /// for a packet. So approximately RTT + max_ack_delay. +3741 fn pto(&self, space: SpaceKind, path_id: PathId) -> Duration { +3742 let max_ack_delay = match space { +3743 SpaceKind::Initial | SpaceKind::Handshake => Duration::ZERO, +3744 SpaceKind::Data => self.ack_frequency.max_ack_delay_for_pto(), +3745 }; +3746 self.path_data(path_id).rtt.pto_base() + max_ack_delay +3747 } +3748 +3749 fn on_packet_authenticated( +3750 &mut self, +3751 now: Instant, +3752 space_id: SpaceKind, +3753 path_id: PathId, +3754 ecn: Option<EcnCodepoint>, +3755 packet_number: Option<u64>, +3756 spin: bool, +3757 is_1rtt: bool, +3758 remote: &FourTuple, +3759 ) { +3760 // During the handshake we already have discarded packets that do not match the path +3761 // remote. So any off-path packet here is either a probing packet or a +3762 // migration. Handling probing packets here means that the path's idle timeout will +3763 // be reset and will delay detecting the path as idle. However tail-loss probes +3764 // would still not get acknowledged if the path was broken so eventually the path +3765 // would still become idle. +3766 let is_on_path = self +3767 .path_data(path_id) +3768 .network_path +3769 .is_probably_same_path(remote); +3770 +3771 self.total_authed_packets += 1; +3772 self.reset_keep_alive(path_id, now); +3773 self.reset_idle_timeout(now, space_id, path_id); +3774 self.path_data_mut(path_id).permit_idle_reset = true; +3775 +3776 // Do not process ECN for off-path packets. If this is a migration we'll get ECN +3777 // back once we've migrated. +3778 if is_on_path { +3779 self.receiving_ecn |= ecn.is_some(); +3780 if let Some(x) = ecn { +3781 let space = &mut self.spaces[space_id]; +3782 space.for_path(path_id).ecn_counters += x; 3783 -3784 space.pending_acks.insert_one(packet_number, now); -3785 if packet_number >= space.largest_received_packet_number.unwrap_or_default() { -3786 space.largest_received_packet_number = Some(packet_number); -3787 -3788 // Update outgoing spin bit for on-path packets, inverting iff we're the client -3789 if is_on_path { -3790 self.spin = self.side.is_client() ^ spin; -3791 } -3792 } -3793 } -3794 -3795 /// Resets the idle timeout timers -3796 /// -3797 /// Without multipath there is only the connection-wide idle timeout. When multipath is -3798 /// enabled there is an additional per-path idle timeout. -3799 fn reset_idle_timeout(&mut self, now: Instant, space: SpaceKind, path_id: PathId) { -3800 // First reset the global idle timeout. -3801 if let Some(timeout) = self.idle_timeout { -3802 if self.state.is_closed() { -3803 self.timers -3804 .stop(Timer::Conn(ConnTimer::Idle), self.qlog.with_time(now)); -3805 } else { -3806 let dt = cmp::max(timeout, 3 * self.max_pto_for_space(space)); -3807 self.timers.set( -3808 Timer::Conn(ConnTimer::Idle), -3809 now + dt, -3810 self.qlog.with_time(now), -3811 ); -3812 } -3813 } -3814 -3815 // Now handle the per-path state -3816 if let Some(timeout) = self.path_data(path_id).idle_timeout { -3817 if self.state.is_closed() { -3818 self.timers.stop( -3819 Timer::PerPath(path_id, PathTimer::PathIdle), -3820 self.qlog.with_time(now), -3821 ); -3822 } else { -3823 let dt = cmp::max(timeout, 3 * self.pto(space, path_id)); -3824 self.timers.set( -3825 Timer::PerPath(path_id, PathTimer::PathIdle), -3826 now + dt, -3827 self.qlog.with_time(now), -3828 ); +3784 if x.is_ce() { +3785 space +3786 .for_path(path_id) +3787 .pending_acks +3788 .set_immediate_ack_required(); +3789 } +3790 } +3791 } +3792 +3793 let Some(packet_number) = packet_number else { +3794 return; +3795 }; +3796 match &self.side { +3797 ConnectionSide::Client { .. } => { +3798 // If we received a handshake packet that authenticated, then we're talking to +3799 // the real server. From now on we should no longer allow the server to migrate +3800 // its address. +3801 if space_id == SpaceKind::Handshake +3802 && let Some(hs) = self.state.as_handshake_mut() +3803 { +3804 hs.allow_server_migration = false; +3805 } +3806 } +3807 ConnectionSide::Server { .. } => { +3808 if self.crypto_state.has_keys(EncryptionLevel::Initial) +3809 && space_id == SpaceKind::Handshake +3810 { +3811 // A server stops sending and processing Initial packets when it receives its first Handshake packet. +3812 self.discard_space(now, SpaceKind::Initial); +3813 } +3814 if self.crypto_state.has_keys(EncryptionLevel::ZeroRtt) && is_1rtt { +3815 // Discard 0-RTT keys soon after receiving a 1-RTT packet +3816 self.set_key_discard_timer(now, space_id) +3817 } +3818 } +3819 } +3820 let space = self.spaces[space_id].for_path(path_id); +3821 +3822 space.pending_acks.insert_one(packet_number, now); +3823 if packet_number >= space.largest_received_packet_number.unwrap_or_default() { +3824 space.largest_received_packet_number = Some(packet_number); +3825 +3826 // Update outgoing spin bit for on-path packets, inverting iff we're the client +3827 if is_on_path { +3828 self.spin = self.side.is_client() ^ spin; 3829 } 3830 } 3831 } 3832 -3833 /// Resets both the [`ConnTimer::KeepAlive`] and [`PathTimer::PathKeepAlive`] timers -3834 fn reset_keep_alive(&mut self, path_id: PathId, now: Instant) { -3835 if !self.state.is_established() { -3836 return; -3837 } -3838 -3839 if let Some(interval) = self.config.keep_alive_interval { -3840 self.timers.set( -3841 Timer::Conn(ConnTimer::KeepAlive), -3842 now + interval, -3843 self.qlog.with_time(now), -3844 ); -3845 } -3846 -3847 if let Some(interval) = self.path_data(path_id).keep_alive { -3848 self.timers.set( -3849 Timer::PerPath(path_id, PathTimer::PathKeepAlive), -3850 now + interval, -3851 self.qlog.with_time(now), -3852 ); -3853 } -3854 } -3855 -3856 /// Sets the timer for when a previously issued CID should be retired next -3857 fn reset_cid_retirement(&mut self, now: Instant) { -3858 if let Some((_path, t)) = self.next_cid_retirement() { -3859 self.timers.set( -3860 Timer::Conn(ConnTimer::PushNewCid), -3861 t, -3862 self.qlog.with_time(now), -3863 ); -3864 } -3865 } -3866 -3867 /// The next time when a previously issued CID should be retired -3868 fn next_cid_retirement(&self) -> Option<(PathId, Instant)> { -3869 self.local_cid_state -3870 .iter() -3871 .filter_map(|(path_id, cid_state)| cid_state.next_timeout().map(|t| (*path_id, t))) -3872 .min_by_key(|(_path_id, timeout)| *timeout) -3873 } -3874 -3875 /// Handle the already-decrypted first packet from the client -3876 /// -3877 /// Decrypting the first packet in the `Endpoint` allows stateless packet handling to be more -3878 /// efficient. -3879 pub(crate) fn handle_first_packet( -3880 &mut self, -3881 now: Instant, -3882 network_path: FourTuple, -3883 ecn: Option<EcnCodepoint>, -3884 packet_number: u64, -3885 packet: InitialPacket, -3886 remaining: Option<BytesMut>, -3887 ) -> Result<(), ConnectionError> { -3888 let span = trace_span!("first recv"); -3889 let _guard = span.enter(); -3890 debug_assert!(self.side.is_server()); -3891 let len = packet.header_data.len() + packet.payload.len(); -3892 let path_id = PathId::ZERO; -3893 self.path_data_mut(path_id).total_recvd = len as u64; -3894 -3895 if let Some(hs) = self.state.as_handshake_mut() { -3896 hs.expected_token = packet.header.token.clone(); -3897 } else { -3898 unreachable!("first packet must be delivered in Handshake state"); -3899 } -3900 -3901 // The first packet is always on PathId::ZERO -3902 self.on_packet_authenticated( -3903 now, -3904 SpaceKind::Initial, -3905 path_id, -3906 ecn, -3907 Some(packet_number), -3908 false, -3909 false, -3910 &network_path, -3911 ); +3833 /// Resets the idle timeout timers +3834 /// +3835 /// Without multipath there is only the connection-wide idle timeout. When multipath is +3836 /// enabled there is an additional per-path idle timeout. +3837 fn reset_idle_timeout(&mut self, now: Instant, space: SpaceKind, path_id: PathId) { +3838 // First reset the global idle timeout. +3839 if let Some(timeout) = self.idle_timeout { +3840 if self.state.is_closed() { +3841 self.timers +3842 .stop(Timer::Conn(ConnTimer::Idle), self.qlog.with_time(now)); +3843 } else { +3844 let dt = cmp::max(timeout, 3 * self.max_pto_for_space(space)); +3845 self.timers.set( +3846 Timer::Conn(ConnTimer::Idle), +3847 now + dt, +3848 self.qlog.with_time(now), +3849 ); +3850 } +3851 } +3852 +3853 // Now handle the per-path state +3854 if let Some(timeout) = self.path_data(path_id).idle_timeout { +3855 if self.state.is_closed() { +3856 self.timers.stop( +3857 Timer::PerPath(path_id, PathTimer::PathIdle), +3858 self.qlog.with_time(now), +3859 ); +3860 } else { +3861 let dt = cmp::max(timeout, 3 * self.pto(space, path_id)); +3862 self.timers.set( +3863 Timer::PerPath(path_id, PathTimer::PathIdle), +3864 now + dt, +3865 self.qlog.with_time(now), +3866 ); +3867 } +3868 } +3869 } +3870 +3871 /// Resets both the [`ConnTimer::KeepAlive`] and [`PathTimer::PathKeepAlive`] timers +3872 fn reset_keep_alive(&mut self, path_id: PathId, now: Instant) { +3873 if !self.state.is_established() { +3874 return; +3875 } +3876 +3877 if let Some(interval) = self.config.keep_alive_interval { +3878 self.timers.set( +3879 Timer::Conn(ConnTimer::KeepAlive), +3880 now + interval, +3881 self.qlog.with_time(now), +3882 ); +3883 } +3884 +3885 if let Some(interval) = self.path_data(path_id).keep_alive { +3886 self.timers.set( +3887 Timer::PerPath(path_id, PathTimer::PathKeepAlive), +3888 now + interval, +3889 self.qlog.with_time(now), +3890 ); +3891 } +3892 } +3893 +3894 /// Sets the timer for when a previously issued CID should be retired next +3895 fn reset_cid_retirement(&mut self, now: Instant) { +3896 if let Some((_path, t)) = self.next_cid_retirement() { +3897 self.timers.set( +3898 Timer::Conn(ConnTimer::PushNewCid), +3899 t, +3900 self.qlog.with_time(now), +3901 ); +3902 } +3903 } +3904 +3905 /// The next time when a previously issued CID should be retired +3906 fn next_cid_retirement(&self) -> Option<(PathId, Instant)> { +3907 self.local_cid_state +3908 .iter() +3909 .filter_map(|(path_id, cid_state)| cid_state.next_timeout().map(|t| (*path_id, t))) +3910 .min_by_key(|(_path_id, timeout)| *timeout) +3911 } 3912 -3913 let packet: Packet = packet.into(); -3914 -3915 let mut qlog = QlogRecvPacket::new(len); -3916 qlog.header(&packet.header, Some(packet_number), path_id); -3917 -3918 self.process_decrypted_packet( -3919 now, -3920 network_path, -3921 path_id, -3922 Some(packet_number), -3923 packet, -3924 &mut qlog, -3925 )?; -3926 self.qlog.emit_packet_received(qlog, now); -3927 if let Some(data) = remaining { -3928 self.handle_coalesced(now, network_path, path_id, ecn, data); -3929 } -3930 -3931 self.qlog.emit_recovery_metrics( -3932 path_id, -3933 &mut self -3934 .paths -3935 .get_mut(&path_id) -3936 .expect("path_id was supplied by the caller for an active path") -3937 .data, -3938 now, -3939 ); -3940 -3941 Ok(()) -3942 } -3943 -3944 fn init_0rtt(&mut self, now: Instant) { -3945 let Some((header, packet)) = self.crypto_state.session.early_crypto() else { -3946 return; -3947 }; -3948 if self.side.is_client() { -3949 match self.crypto_state.session.transport_parameters() { -3950 Ok(params) => { -3951 let params = params -3952 .expect("crypto layer didn't supply transport parameters with ticket"); -3953 // Certain values must not be cached -3954 let params = TransportParameters { -3955 initial_src_cid: None, -3956 original_dst_cid: None, -3957 preferred_address: None, -3958 retry_src_cid: None, -3959 stateless_reset_token: None, -3960 min_ack_delay: None, -3961 ack_delay_exponent: TransportParameters::default().ack_delay_exponent, -3962 max_ack_delay: TransportParameters::default().max_ack_delay, -3963 initial_max_path_id: None, -3964 ..params -3965 }; -3966 self.set_peer_params(params); -3967 self.qlog.emit_peer_transport_params_restored(self, now); -3968 } -3969 Err(e) => { -3970 error!("session ticket has malformed transport parameters: {}", e); -3971 return; -3972 } -3973 } -3974 } -3975 trace!("0-RTT enabled"); -3976 self.crypto_state.enable_zero_rtt(header, packet); -3977 } +3913 /// Handle the already-decrypted first packet from the client +3914 /// +3915 /// Decrypting the first packet in the `Endpoint` allows stateless packet handling to be more +3916 /// efficient. +3917 pub(crate) fn handle_first_packet( +3918 &mut self, +3919 now: Instant, +3920 network_path: FourTuple, +3921 ecn: Option<EcnCodepoint>, +3922 packet_number: u64, +3923 packet: InitialPacket, +3924 remaining: Option<BytesMut>, +3925 ) -> Result<(), ConnectionError> { +3926 let span = trace_span!("first recv"); +3927 let _guard = span.enter(); +3928 debug_assert!(self.side.is_server()); +3929 let len = packet.header_data.len() + packet.payload.len(); +3930 let path_id = PathId::ZERO; +3931 self.path_data_mut(path_id).total_recvd = len as u64; +3932 +3933 if let Some(hs) = self.state.as_handshake_mut() { +3934 hs.expected_token = packet.header.token.clone(); +3935 } else { +3936 unreachable!("first packet must be delivered in Handshake state"); +3937 } +3938 +3939 // The first packet is always on PathId::ZERO +3940 self.on_packet_authenticated( +3941 now, +3942 SpaceKind::Initial, +3943 path_id, +3944 ecn, +3945 Some(packet_number), +3946 false, +3947 false, +3948 &network_path, +3949 ); +3950 +3951 let packet: Packet = packet.into(); +3952 +3953 let mut qlog = QlogRecvPacket::new(len); +3954 qlog.header(&packet.header, Some(packet_number), path_id); +3955 +3956 self.process_decrypted_packet( +3957 now, +3958 network_path, +3959 path_id, +3960 Some(packet_number), +3961 packet, +3962 &mut qlog, +3963 )?; +3964 self.qlog.emit_packet_received(qlog, now); +3965 if let Some(data) = remaining { +3966 self.handle_coalesced(now, network_path, path_id, ecn, data); +3967 } +3968 +3969 self.qlog.emit_recovery_metrics( +3970 path_id, +3971 &mut self +3972 .paths +3973 .get_mut(&path_id) +3974 .expect("path_id was supplied by the caller for an active path") +3975 .data, +3976 now, +3977 ); 3978 -3979 fn read_crypto( -3980 &mut self, -3981 space: SpaceId, -3982 crypto: &frame::Crypto, -3983 payload_len: usize, -3984 ) -> Result<(), TransportError> { -3985 let expected = if !self.state.is_handshake() { -3986 SpaceId::Data -3987 } else if self.highest_space == SpaceKind::Initial { -3988 SpaceId::Initial -3989 } else { -3990 // On the server, self.highest_space can be Data after receiving the client's first -3991 // flight, but we expect Handshake CRYPTO until the handshake is complete. -3992 SpaceId::Handshake -3993 }; -3994 // We can't decrypt Handshake packets when highest_space is Initial, CRYPTO frames in 0-RTT -3995 // packets are illegal, and we don't process 1-RTT packets until the handshake is -3996 // complete. Therefore, we will never see CRYPTO data from a later-than-expected space. -3997 debug_assert!(space <= expected, "received out-of-order CRYPTO data"); -3998 -3999 let end = crypto.offset + crypto.data.len() as u64; -4000 if space < expected -4001 && end -4002 > self.crypto_state.spaces[space.kind()] -4003 .crypto_stream -4004 .bytes_read() -4005 { -4006 warn!( -4007 "received new {:?} CRYPTO data when expecting {:?}", -4008 space, expected -4009 ); -4010 return Err(TransportError::PROTOCOL_VIOLATION( -4011 "new data at unexpected encryption level", -4012 )); -4013 } -4014 -4015 let crypto_space = &mut self.crypto_state.spaces[space.kind()]; -4016 let max = end.saturating_sub(crypto_space.crypto_stream.bytes_read()); -4017 if max > self.config.crypto_buffer_size as u64 { -4018 return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); -4019 } -4020 -4021 crypto_space -4022 .crypto_stream -4023 .insert(crypto.offset, crypto.data.clone(), payload_len); -4024 while let Some(chunk) = crypto_space.crypto_stream.read(usize::MAX, true) { -4025 trace!("consumed {} CRYPTO bytes", chunk.bytes.len()); -4026 if self.crypto_state.session.read_handshake(&chunk.bytes)? { -4027 self.events.push_back(Event::HandshakeDataReady); -4028 } -4029 } -4030 -4031 Ok(()) -4032 } -4033 -4034 fn write_crypto(&mut self) { -4035 loop { -4036 let space = self.highest_space; -4037 let mut outgoing = Vec::new(); -4038 if let Some(crypto) = self.crypto_state.session.write_handshake(&mut outgoing) { -4039 match space { -4040 SpaceKind::Initial => { -4041 self.upgrade_crypto(SpaceKind::Handshake, crypto); -4042 } -4043 SpaceKind::Handshake => { -4044 self.upgrade_crypto(SpaceKind::Data, crypto); -4045 } -4046 SpaceKind::Data => unreachable!("got updated secrets during 1-RTT"), -4047 } -4048 } -4049 if outgoing.is_empty() { -4050 if space == self.highest_space { -4051 break; -4052 } else { -4053 // Keys updated, check for more data to send -4054 continue; -4055 } -4056 } -4057 let offset = self.crypto_state.spaces[space].crypto_offset; -4058 let outgoing = Bytes::from(outgoing); -4059 if let Some(hs) = self.state.as_handshake_mut() -4060 && space == SpaceKind::Initial -4061 && offset == 0 -4062 && self.side.is_client() -4063 { -4064 hs.client_hello = Some(outgoing.clone()); -4065 } -4066 self.crypto_state.spaces[space].crypto_offset += outgoing.len() as u64; -4067 trace!("wrote {} {:?} CRYPTO bytes", outgoing.len(), space); -4068 self.spaces[space].pending.crypto.push_back(frame::Crypto { -4069 offset, -4070 data: outgoing, -4071 }); -4072 } -4073 } -4074 -4075 /// Switch to stronger cryptography during handshake -4076 fn upgrade_crypto(&mut self, space: SpaceKind, crypto: Keys) { -4077 debug_assert!( -4078 !self.crypto_state.has_keys(space.encryption_level()), -4079 "already reached packet space {space:?}" -4080 ); -4081 trace!("{:?} keys ready", space); -4082 if space == SpaceKind::Data { -4083 // Precompute the first key update -4084 self.crypto_state.next_crypto = Some( -4085 self.crypto_state -4086 .session -4087 .next_1rtt_keys() -4088 .expect("handshake should be complete"), -4089 ); -4090 } -4091 -4092 self.crypto_state.spaces[space].keys = Some(crypto); -4093 debug_assert!(space > self.highest_space); -4094 self.highest_space = space; -4095 if space == SpaceKind::Data && self.side.is_client() { -4096 // Discard 0-RTT keys because 1-RTT keys are available. -4097 self.crypto_state.discard_zero_rtt(); -4098 } -4099 } -4100 -4101 fn discard_space(&mut self, now: Instant, space: SpaceKind) { -4102 debug_assert!(space != SpaceKind::Data); -4103 trace!("discarding {:?} keys", space); -4104 if space == SpaceKind::Initial { -4105 // No longer needed -4106 if let ConnectionSide::Client { token, .. } = &mut self.side { -4107 *token = Bytes::new(); -4108 } -4109 } -4110 self.crypto_state.spaces[space].keys = None; -4111 let space = &mut self.spaces[space]; -4112 let pns = space.for_path(PathId::ZERO); -4113 pns.time_of_last_ack_eliciting_packet = None; -4114 pns.loss_time = None; -4115 pns.loss_probes = 0; -4116 let sent_packets = mem::take(&mut pns.sent_packets); -4117 let path = self -4118 .paths -4119 .get_mut(&PathId::ZERO) -4120 .expect("PathId::ZERO is alive while Initial/Handshake spaces exist"); -4121 for (_, packet) in sent_packets.into_iter() { -4122 path.data.remove_in_flight(&packet); -4123 } -4124 -4125 self.set_loss_detection_timer(now, PathId::ZERO) -4126 } -4127 -4128 fn handle_coalesced( -4129 &mut self, -4130 now: Instant, -4131 network_path: FourTuple, -4132 path_id: PathId, -4133 ecn: Option<EcnCodepoint>, -4134 data: BytesMut, -4135 ) { -4136 self.path_data_mut(path_id) -4137 .inc_total_recvd(data.len() as u64); -4138 let mut remaining = Some(data); -4139 let cid_len = self -4140 .local_cid_state -4141 .values() -4142 .map(|cid_state| cid_state.cid_len()) -4143 .next() -4144 .expect("one cid_state must exist"); -4145 while let Some(data) = remaining { -4146 match PartialDecode::new( -4147 data, -4148 &FixedLengthConnectionIdParser::new(cid_len), -4149 &[self.version], -4150 self.endpoint_config.grease_quic_bit, -4151 ) { -4152 Ok((partial_decode, rest)) => { -4153 remaining = rest; -4154 self.handle_decode(now, network_path, path_id, ecn, partial_decode); -4155 } -4156 Err(e) => { -4157 trace!("malformed header: {}", e); -4158 return; -4159 } -4160 } +3979 Ok(()) +3980 } +3981 +3982 fn init_0rtt(&mut self, now: Instant) { +3983 let Some((header, packet)) = self.crypto_state.session.early_crypto() else { +3984 return; +3985 }; +3986 if self.side.is_client() { +3987 match self.crypto_state.session.transport_parameters() { +3988 Ok(params) => { +3989 let params = params +3990 .expect("crypto layer didn't supply transport parameters with ticket"); +3991 // Certain values must not be cached +3992 let params = TransportParameters { +3993 initial_src_cid: None, +3994 original_dst_cid: None, +3995 preferred_address: None, +3996 retry_src_cid: None, +3997 stateless_reset_token: None, +3998 min_ack_delay: None, +3999 ack_delay_exponent: TransportParameters::default().ack_delay_exponent, +4000 max_ack_delay: TransportParameters::default().max_ack_delay, +4001 initial_max_path_id: None, +4002 ..params +4003 }; +4004 self.set_peer_params(params); +4005 self.qlog.emit_peer_transport_params_restored(self, now); +4006 } +4007 Err(e) => { +4008 error!("session ticket has malformed transport parameters: {}", e); +4009 return; +4010 } +4011 } +4012 } +4013 trace!("0-RTT enabled"); +4014 self.crypto_state.enable_zero_rtt(header, packet); +4015 } +4016 +4017 fn read_crypto( +4018 &mut self, +4019 space: SpaceId, +4020 crypto: &frame::Crypto, +4021 payload_len: usize, +4022 ) -> Result<(), TransportError> { +4023 let expected = if !self.state.is_handshake() { +4024 SpaceId::Data +4025 } else if self.highest_space == SpaceKind::Initial { +4026 SpaceId::Initial +4027 } else { +4028 // On the server, self.highest_space can be Data after receiving the client's first +4029 // flight, but we expect Handshake CRYPTO until the handshake is complete. +4030 SpaceId::Handshake +4031 }; +4032 // We can't decrypt Handshake packets when highest_space is Initial, CRYPTO frames in 0-RTT +4033 // packets are illegal, and we don't process 1-RTT packets until the handshake is +4034 // complete. Therefore, we will never see CRYPTO data from a later-than-expected space. +4035 debug_assert!(space <= expected, "received out-of-order CRYPTO data"); +4036 +4037 let end = crypto.offset + crypto.data.len() as u64; +4038 if space < expected +4039 && end +4040 > self.crypto_state.spaces[space.kind()] +4041 .crypto_stream +4042 .bytes_read() +4043 { +4044 warn!( +4045 "received new {:?} CRYPTO data when expecting {:?}", +4046 space, expected +4047 ); +4048 return Err(TransportError::PROTOCOL_VIOLATION( +4049 "new data at unexpected encryption level", +4050 )); +4051 } +4052 +4053 let crypto_space = &mut self.crypto_state.spaces[space.kind()]; +4054 let max = end.saturating_sub(crypto_space.crypto_stream.bytes_read()); +4055 if max > self.config.crypto_buffer_size as u64 { +4056 return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); +4057 } +4058 +4059 crypto_space +4060 .crypto_stream +4061 .insert(crypto.offset, crypto.data.clone(), payload_len); +4062 while let Some(chunk) = crypto_space.crypto_stream.read(usize::MAX, true) { +4063 trace!("consumed {} CRYPTO bytes", chunk.bytes.len()); +4064 if self.crypto_state.session.read_handshake(&chunk.bytes)? { +4065 self.events.push_back(Event::HandshakeDataReady); +4066 } +4067 } +4068 +4069 Ok(()) +4070 } +4071 +4072 fn write_crypto(&mut self) { +4073 loop { +4074 let space = self.highest_space; +4075 let mut outgoing = Vec::new(); +4076 if let Some(crypto) = self.crypto_state.session.write_handshake(&mut outgoing) { +4077 match space { +4078 SpaceKind::Initial => { +4079 self.upgrade_crypto(SpaceKind::Handshake, crypto); +4080 } +4081 SpaceKind::Handshake => { +4082 self.upgrade_crypto(SpaceKind::Data, crypto); +4083 } +4084 SpaceKind::Data => unreachable!("got updated secrets during 1-RTT"), +4085 } +4086 } +4087 if outgoing.is_empty() { +4088 if space == self.highest_space { +4089 break; +4090 } else { +4091 // Keys updated, check for more data to send +4092 continue; +4093 } +4094 } +4095 let offset = self.crypto_state.spaces[space].crypto_offset; +4096 let outgoing = Bytes::from(outgoing); +4097 if let Some(hs) = self.state.as_handshake_mut() +4098 && space == SpaceKind::Initial +4099 && offset == 0 +4100 && self.side.is_client() +4101 { +4102 hs.client_hello = Some(outgoing.clone()); +4103 } +4104 self.crypto_state.spaces[space].crypto_offset += outgoing.len() as u64; +4105 trace!("wrote {} {:?} CRYPTO bytes", outgoing.len(), space); +4106 self.spaces[space].pending.crypto.push_back(frame::Crypto { +4107 offset, +4108 data: outgoing, +4109 }); +4110 } +4111 } +4112 +4113 /// Switch to stronger cryptography during handshake +4114 fn upgrade_crypto(&mut self, space: SpaceKind, crypto: Keys) { +4115 debug_assert!( +4116 !self.crypto_state.has_keys(space.encryption_level()), +4117 "already reached packet space {space:?}" +4118 ); +4119 trace!("{:?} keys ready", space); +4120 if space == SpaceKind::Data { +4121 // Precompute the first key update +4122 self.crypto_state.next_crypto = Some( +4123 self.crypto_state +4124 .session +4125 .next_1rtt_keys() +4126 .expect("handshake should be complete"), +4127 ); +4128 } +4129 +4130 self.crypto_state.spaces[space].keys = Some(crypto); +4131 debug_assert!(space > self.highest_space); +4132 self.highest_space = space; +4133 if space == SpaceKind::Data && self.side.is_client() { +4134 // Discard 0-RTT keys because 1-RTT keys are available. +4135 self.crypto_state.discard_zero_rtt(); +4136 } +4137 } +4138 +4139 fn discard_space(&mut self, now: Instant, space: SpaceKind) { +4140 debug_assert!(space != SpaceKind::Data); +4141 trace!("discarding {:?} keys", space); +4142 if space == SpaceKind::Initial { +4143 // No longer needed +4144 if let ConnectionSide::Client { token, .. } = &mut self.side { +4145 *token = Bytes::new(); +4146 } +4147 } +4148 self.crypto_state.spaces[space].keys = None; +4149 let space = &mut self.spaces[space]; +4150 let pns = space.for_path(PathId::ZERO); +4151 pns.time_of_last_ack_eliciting_packet = None; +4152 pns.loss_time = None; +4153 pns.loss_probes = 0; +4154 let sent_packets = mem::take(&mut pns.sent_packets); +4155 let path = self +4156 .paths +4157 .get_mut(&PathId::ZERO) +4158 .expect("PathId::ZERO is alive while Initial/Handshake spaces exist"); +4159 for (_, packet) in sent_packets.into_iter() { +4160 path.data.remove_in_flight(&packet); 4161 } -4162 } -4163 -4164 /// Decrypts the packet and processes the payload. -4165 /// -4166 /// Processes the entire packet, starting with removing header protection, then handling -4167 /// a stateless reset if needed, and decrypting and processing the frames in the payload -4168 /// if not a stateless reset. -4169 fn handle_decode( -4170 &mut self, -4171 now: Instant, -4172 network_path: FourTuple, -4173 path_id: PathId, -4174 ecn: Option<EcnCodepoint>, -4175 partial_decode: PartialDecode, -4176 ) { -4177 let qlog = QlogRecvPacket::new(partial_decode.len()); -4178 if let Some(decoded) = self -4179 .crypto_state -4180 .unprotect_header(partial_decode, self.peer_params.stateless_reset_token) -4181 { -4182 self.handle_packet( -4183 now, -4184 network_path, -4185 path_id, -4186 ecn, -4187 decoded.packet, -4188 decoded.stateless_reset, -4189 qlog, -4190 ); -4191 } -4192 } -4193 -4194 /// Handles a packet with header protection removed. -4195 /// -4196 /// The packet body is still encrypted at this point. -4197 /// -4198 /// If the datagram was a stateless reset we may have failed to remove header protection -4199 /// and thus `packet` may be `None`. -4200 fn handle_packet( -4201 &mut self, -4202 now: Instant, -4203 network_path: FourTuple, -4204 path_id: PathId, -4205 ecn: Option<EcnCodepoint>, -4206 packet: Option<Packet>, -4207 stateless_reset: bool, -4208 mut qlog: QlogRecvPacket, -4209 ) { -4210 self.path_stats.for_path(path_id).udp_rx.ios += 1; -4211 -4212 if let Some(ref packet) = packet { -4213 trace!( -4214 "got {:?} packet ({} bytes) from {} using id {}", -4215 packet.header.space(), -4216 packet.payload.len() + packet.header_data.len(), -4217 network_path, -4218 packet.header.dst_cid(), -4219 ); -4220 } -4221 -4222 let was_closed = self.state.is_closed(); -4223 let was_drained = self.state.is_drained(); -4224 -4225 // Now decrypt the packet payload in-place. -4226 let decrypted = match packet { -4227 None => Err(None), -4228 Some(mut packet) => self -4229 .decrypt_packet(now, path_id, &mut packet) -4230 .map(move |number| (packet, number)), -4231 }; -4232 let result = match decrypted { -4233 _ if stateless_reset => { -4234 debug!("got stateless reset"); -4235 Err(ConnectionError::Reset) -4236 } -4237 Err(Some(e)) => { -4238 warn!("illegal packet: {}", e); -4239 Err(e.into()) -4240 } -4241 Err(None) => { -4242 debug!("failed to authenticate packet"); -4243 self.authentication_failures += 1; -4244 let integrity_limit = self -4245 .crypto_state -4246 .integrity_limit(self.highest_space) -4247 .unwrap(); -4248 if self.authentication_failures > integrity_limit { -4249 Err(TransportError::AEAD_LIMIT_REACHED("integrity limit violated").into()) -4250 } else { -4251 return; -4252 } -4253 } -4254 Ok((packet, pn)) => { -4255 // We received an authenticated packet and decrypted it. -4256 qlog.header(&packet.header, pn, path_id); -4257 let span = match pn { -4258 Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), -4259 None => trace_span!("recv", space = ?packet.header.space()), -4260 }; -4261 let _guard = span.enter(); -4262 -4263 // Now the packet is authenticated we do the migration during the handshake, -4264 // see Handshake::allow_server_migration for details. Be careful here to -4265 // not yet rely on the path existing however, new paths are accepted and -4266 // created later. -4267 // Note that we can't do any other migrations yet, for those we need to know -4268 // whether this was a probing packet or not. See the end of -4269 // Self::process_packet for that. -4270 if self.is_handshaking() -4271 && self -4272 .path(path_id) -4273 .map(|path_data| { -4274 !path_data.network_path.is_probably_same_path(&network_path) -4275 }) -4276 .unwrap_or(false) -4277 { -4278 if let Some(hs) = self.state.as_handshake() -4279 && hs.allow_server_migration -4280 { -4281 trace!( -4282 %network_path, -4283 prev = %self.path_data(path_id).network_path, -4284 "server migrated to new remote", -4285 ); -4286 self.path_data_mut(path_id).network_path = network_path; -4287 self.qlog.emit_tuple_assigned(path_id, network_path, now); -4288 } else { -4289 debug!( -4290 recv_path = %network_path, -4291 expected_path = %self.path_data_mut(path_id).network_path, -4292 "discarding packet with unexpected remote during handshake", -4293 ); -4294 return; -4295 } -4296 } -4297 -4298 let dedup = self.spaces[packet.header.space()] -4299 .path_space_mut(path_id) -4300 .map(|pns| &mut pns.dedup); -4301 if pn.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { -4302 debug!("discarding possible duplicate packet"); -4303 self.qlog.emit_packet_received(qlog, now); -4304 return; -4305 } else if self.state.is_handshake() && packet.header.is_short() { -4306 // TODO: SHOULD buffer these to improve reordering tolerance. -4307 trace!("dropping short packet during handshake"); -4308 self.qlog.emit_packet_received(qlog, now); -4309 return; -4310 } else { -4311 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header -4312 && let Some(hs) = self.state.as_handshake() -4313 && self.side.is_server() -4314 && token != &hs.expected_token -4315 { -4316 // Clients must send the same retry token in every Initial. Initial -4317 // packets can be spoofed, so we discard rather than killing the -4318 // connection. -4319 warn!("discarding Initial with invalid retry token"); -4320 self.qlog.emit_packet_received(qlog, now); -4321 return; -4322 } -4323 -4324 if !self.state.is_closed() { -4325 let spin = match packet.header { -4326 Header::Short { spin, .. } => spin, -4327 _ => false, -4328 }; -4329 -4330 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { -4331 // Only the client is allowed to open paths -4332 self.ensure_path(path_id, network_path, now, pn); -4333 } -4334 if self.paths.contains_key(&path_id) { -4335 self.on_packet_authenticated( -4336 now, -4337 packet.header.space(), -4338 path_id, -4339 ecn, -4340 pn, -4341 spin, -4342 packet.header.is_1rtt(), -4343 &network_path, -4344 ); -4345 } -4346 } -4347 -4348 let res = self.process_decrypted_packet( -4349 now, -4350 network_path, -4351 path_id, -4352 pn, -4353 packet, -4354 &mut qlog, -4355 ); -4356 -4357 self.qlog.emit_packet_received(qlog, now); -4358 res -4359 } -4360 } -4361 }; -4362 -4363 // State transitions for error cases -4364 if let Err(conn_err) = result { -4365 match conn_err { -4366 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), -4367 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), -4368 ConnectionError::Reset -4369 | ConnectionError::TransportError(TransportError { -4370 code: TransportErrorCode::AEAD_LIMIT_REACHED, -4371 .. -4372 }) => { -4373 self.state.move_to_drained(Some(conn_err)); -4374 } -4375 ConnectionError::TimedOut => { -4376 unreachable!("timeouts aren't generated by packet processing"); -4377 } -4378 ConnectionError::TransportError(err) => { -4379 debug!("closing connection due to transport error: {}", err); -4380 self.state.move_to_closed(err); -4381 } -4382 ConnectionError::VersionMismatch => { -4383 self.state.move_to_draining(Some(conn_err)); -4384 } -4385 ConnectionError::LocallyClosed => { -4386 unreachable!("LocallyClosed isn't generated by packet processing"); -4387 } -4388 ConnectionError::CidsExhausted => { -4389 unreachable!("CidsExhausted isn't generated by packet processing"); -4390 } -4391 }; -4392 } -4393 -4394 if !was_closed && self.state.is_closed() { -4395 self.close_common(); -4396 if !self.state.is_drained() { -4397 self.set_close_timer(now); -4398 } -4399 } -4400 if !was_drained && self.state.is_drained() { -4401 self.endpoint_events.push_back(EndpointEventInner::Drained); -4402 // Close timer may have been started previously, e.g. if we sent a close and got a -4403 // stateless reset in response -4404 self.timers -4405 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); -4406 } -4407 -4408 // Transmit CONNECTION_CLOSE if necessary. -4409 // -4410 // If we received a valid packet and we are in the closed state we should respond -4411 // with a CONNECTION_CLOSE frame. -4412 // TODO: This SHOULD be rate-limited according to §10.2.1 of QUIC-TRANSPORT, but -4413 // that does not yet happen. This is triggered by each received packet. -4414 if matches!(self.state.as_type(), StateType::Closed) { -4415 // From https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1-7 -4416 // -4417 // While in the closing state we must either: -4418 // - discard packets coming from an un-validated remote OR -4419 // - ensure we do not send more than 3 times the received data -4420 // -4421 // Doing the 2nd would mean we would be able to send CONNECTION_CLOSE to a peer -4422 // who was (involuntary) migrated just at the time we initiated immediate -4423 // close. It is a lot more work though. So while we would like to do this for -4424 // now we only do 1. -4425 // -4426 // Another shortcoming of the current implementation is that when we have a -4427 // previous PathData which is validated and the remote matches that path, we -4428 // should schedule CONNECTION_CLOSE on that path. However currently we can not -4429 // schedule such a packet. We should also fix this some day. This makes us -4430 // vulnerable to an attacker faking a migration at the right time and then we'd -4431 // be unable to send the CONNECTION_CLOSE to the real remote. -4432 if self -4433 .paths -4434 .get(&path_id) -4435 .map(|p| p.data.validated && p.data.network_path == network_path) -4436 .unwrap_or(false) -4437 { -4438 self.connection_close_pending = true; -4439 } -4440 } -4441 } -4442 -4443 fn process_decrypted_packet( -4444 &mut self, -4445 now: Instant, -4446 network_path: FourTuple, -4447 path_id: PathId, -4448 number: Option<u64>, -4449 packet: Packet, -4450 qlog: &mut QlogRecvPacket, -4451 ) -> Result<(), ConnectionError> { -4452 if !self.paths.contains_key(&path_id) { -4453 // There is a chance this is a server side, first (for this path) packet, which would -4454 // be a protocol violation. It's more likely, however, that this is a packet of a -4455 // pruned path -4456 trace!(%path_id, ?number, "discarding packet for unknown path"); -4457 return Ok(()); -4458 } -4459 let state = match self.state.as_type() { -4460 StateType::Established => { -4461 match packet.header.space() { -4462 SpaceKind::Data => self.process_payload( -4463 now, -4464 network_path, -4465 path_id, -4466 number.unwrap(), -4467 packet, -4468 qlog, -4469 )?, -4470 _ if packet.header.has_frames() => { -4471 self.process_early_payload(now, path_id, packet, qlog)? -4472 } -4473 _ => { -4474 trace!("discarding unexpected pre-handshake packet"); -4475 } -4476 } -4477 return Ok(()); -4478 } -4479 StateType::Closed => { -4480 for result in frame::Iter::new(packet.payload.freeze())? { -4481 let frame = match result { -4482 Ok(frame) => frame, -4483 Err(err) => { -4484 debug!("frame decoding error: {err:?}"); -4485 continue; -4486 } -4487 }; -4488 qlog.frame(&frame); -4489 -4490 if let Frame::Padding = frame { -4491 continue; -4492 }; -4493 -4494 self.path_stats -4495 .for_path(path_id) -4496 .frame_rx -4497 .record(frame.ty()); -4498 -4499 if let Frame::Close(_error) = frame { -4500 self.state.move_to_draining(None); -4501 break; -4502 } -4503 } -4504 return Ok(()); -4505 } -4506 StateType::Draining | StateType::Drained => return Ok(()), -4507 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), -4508 }; -4509 -4510 match packet.header { -4511 Header::Retry { -4512 src_cid: remote_cid, -4513 .. -4514 } => { -4515 debug_assert_eq!(path_id, PathId::ZERO); -4516 if self.side.is_server() { -4517 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); -4518 } -4519 -4520 let is_valid_retry = self -4521 .remote_cids -4522 .get(&path_id) -4523 .map(|cids| cids.active()) -4524 .map(|orig_dst_cid| { -4525 self.crypto_state.session.is_valid_retry( -4526 orig_dst_cid, -4527 &packet.header_data, -4528 &packet.payload, -4529 ) -4530 }) -4531 .unwrap_or_default(); -4532 if self.total_authed_packets > 1 -4533 || packet.payload.len() <= 16 // token + 16 byte tag -4534 || !is_valid_retry -4535 { -4536 trace!("discarding invalid Retry"); -4537 // - After the client has received and processed an Initial or Retry -4538 // packet from the server, it MUST discard any subsequent Retry -4539 // packets that it receives. -4540 // - A client MUST discard a Retry packet with a zero-length Retry Token -4541 // field. -4542 // - Clients MUST discard Retry packets that have a Retry Integrity Tag -4543 // that cannot be validated -4544 return Ok(()); -4545 } -4546 -4547 trace!("retrying with CID {}", remote_cid); -4548 let client_hello = state.client_hello.take().unwrap(); -4549 self.retry_src_cid = Some(remote_cid); -4550 self.remote_cids -4551 .get_mut(&path_id) -4552 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") -4553 .update_initial_cid(remote_cid); -4554 self.remote_handshake_cid = remote_cid; -4555 -4556 let space = &mut self.spaces[SpaceId::Initial]; -4557 if let Some(info) = space.for_path(PathId::ZERO).take(0) { -4558 self.on_packet_acked(now, PathId::ZERO, 0, info); -4559 }; -4560 -4561 self.discard_space(now, SpaceKind::Initial); // Make sure we clean up after -4562 // any retransmitted Initials -4563 let crypto_space = &mut self.crypto_state.spaces[SpaceKind::Initial]; -4564 crypto_space.keys = Some( -4565 self.crypto_state -4566 .session -4567 .initial_keys(remote_cid, self.side.side()), -4568 ); -4569 crypto_space.crypto_offset = client_hello.len() as u64; -4570 -4571 let next_pn = self.spaces[SpaceId::Initial] -4572 .for_path(path_id) -4573 .next_packet_number; -4574 self.spaces[SpaceId::Initial] = { -4575 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); -4576 space.for_path(path_id).next_packet_number = next_pn; -4577 space.pending.crypto.push_back(frame::Crypto { -4578 offset: 0, -4579 data: client_hello, -4580 }); -4581 space -4582 }; -4583 -4584 // Retransmit all 0-RTT data -4585 let zero_rtt = mem::take( -4586 &mut self.spaces[SpaceId::Data] -4587 .for_path(PathId::ZERO) -4588 .sent_packets, -4589 ); -4590 for (_, info) in zero_rtt.into_iter() { -4591 self.paths -4592 .get_mut(&PathId::ZERO) -4593 .unwrap() -4594 .remove_in_flight(&info); -4595 self.spaces[SpaceId::Data].pending |= info.retransmits; -4596 } -4597 self.streams.retransmit_all_for_0rtt(); -4598 -4599 let token_len = packet.payload.len() - 16; -4600 let ConnectionSide::Client { ref mut token, .. } = self.side else { -4601 unreachable!("we already short-circuited if we're server"); -4602 }; -4603 *token = packet.payload.freeze().split_to(token_len); -4604 -4605 self.state = State::handshake(state::Handshake { -4606 expected_token: Bytes::new(), -4607 remote_cid_set: false, -4608 client_hello: None, -4609 allow_server_migration: true, -4610 }); -4611 Ok(()) -4612 } -4613 Header::Long { -4614 ty: LongType::Handshake, -4615 src_cid: remote_cid, -4616 dst_cid: local_cid, -4617 .. -4618 } => { -4619 debug_assert_eq!(path_id, PathId::ZERO); -4620 if remote_cid != self.remote_handshake_cid { -4621 debug!( -4622 "discarding packet with mismatched remote CID: {} != {}", -4623 self.remote_handshake_cid, remote_cid -4624 ); -4625 return Ok(()); -4626 } -4627 self.on_path_validated(path_id); -4628 -4629 self.process_early_payload(now, path_id, packet, qlog)?; -4630 if self.state.is_closed() { -4631 return Ok(()); -4632 } -4633 -4634 if self.crypto_state.session.is_handshaking() { -4635 trace!("handshake ongoing"); -4636 return Ok(()); -4637 } +4162 +4163 self.set_loss_detection_timer(now, PathId::ZERO) +4164 } +4165 +4166 fn handle_coalesced( +4167 &mut self, +4168 now: Instant, +4169 network_path: FourTuple, +4170 path_id: PathId, +4171 ecn: Option<EcnCodepoint>, +4172 data: BytesMut, +4173 ) { +4174 self.path_data_mut(path_id) +4175 .inc_total_recvd(data.len() as u64); +4176 let mut remaining = Some(data); +4177 let cid_len = self +4178 .local_cid_state +4179 .values() +4180 .map(|cid_state| cid_state.cid_len()) +4181 .next() +4182 .expect("one cid_state must exist"); +4183 while let Some(data) = remaining { +4184 match PartialDecode::new( +4185 data, +4186 &FixedLengthConnectionIdParser::new(cid_len), +4187 &[self.version], +4188 self.endpoint_config.grease_quic_bit, +4189 ) { +4190 Ok((partial_decode, rest)) => { +4191 remaining = rest; +4192 self.handle_decode(now, network_path, path_id, ecn, partial_decode); +4193 } +4194 Err(e) => { +4195 trace!("malformed header: {}", e); +4196 return; +4197 } +4198 } +4199 } +4200 } +4201 +4202 /// Decrypts the packet and processes the payload. +4203 /// +4204 /// Processes the entire packet, starting with removing header protection, then handling +4205 /// a stateless reset if needed, and decrypting and processing the frames in the payload +4206 /// if not a stateless reset. +4207 fn handle_decode( +4208 &mut self, +4209 now: Instant, +4210 network_path: FourTuple, +4211 path_id: PathId, +4212 ecn: Option<EcnCodepoint>, +4213 partial_decode: PartialDecode, +4214 ) { +4215 let qlog = QlogRecvPacket::new(partial_decode.len()); +4216 if let Some(decoded) = self +4217 .crypto_state +4218 .unprotect_header(partial_decode, self.peer_params.stateless_reset_token) +4219 { +4220 self.handle_packet( +4221 now, +4222 network_path, +4223 path_id, +4224 ecn, +4225 decoded.packet, +4226 decoded.stateless_reset, +4227 qlog, +4228 ); +4229 } +4230 } +4231 +4232 /// Handles a packet with header protection removed. +4233 /// +4234 /// The packet body is still encrypted at this point. +4235 /// +4236 /// If the datagram was a stateless reset we may have failed to remove header protection +4237 /// and thus `packet` may be `None`. +4238 fn handle_packet( +4239 &mut self, +4240 now: Instant, +4241 network_path: FourTuple, +4242 path_id: PathId, +4243 ecn: Option<EcnCodepoint>, +4244 packet: Option<Packet>, +4245 stateless_reset: bool, +4246 mut qlog: QlogRecvPacket, +4247 ) { +4248 if let Some(ref packet) = packet { +4249 trace!( +4250 "got {:?} packet ({} bytes) from {} using id {}", +4251 packet.header.space(), +4252 packet.payload.len() + packet.header_data.len(), +4253 network_path, +4254 packet.header.dst_cid(), +4255 ); +4256 } +4257 +4258 let was_closed = self.state.is_closed(); +4259 let was_drained = self.state.is_drained(); +4260 +4261 // Now decrypt the packet payload in-place. +4262 let decrypted = match packet { +4263 None => Err(None), +4264 Some(mut packet) => self +4265 .decrypt_packet(now, path_id, &mut packet) +4266 .map(move |number| (packet, number)), +4267 }; +4268 let result = match decrypted { +4269 _ if stateless_reset => { +4270 debug!("got stateless reset"); +4271 Err(ConnectionError::Reset) +4272 } +4273 Err(Some(e)) => { +4274 warn!("illegal packet: {}", e); +4275 Err(e.into()) +4276 } +4277 Err(None) => { +4278 debug!("failed to authenticate packet"); +4279 self.authentication_failures += 1; +4280 let integrity_limit = self +4281 .crypto_state +4282 .integrity_limit(self.highest_space) +4283 .unwrap(); +4284 if self.authentication_failures > integrity_limit { +4285 Err(TransportError::AEAD_LIMIT_REACHED("integrity limit violated").into()) +4286 } else { +4287 return; +4288 } +4289 } +4290 Ok((packet, pn)) => { +4291 // We received an authenticated packet and decrypted it. +4292 qlog.header(&packet.header, pn, path_id); +4293 let span = match pn { +4294 Some(pn) => trace_span!("recv", space = ?packet.header.space(), pn), +4295 None => trace_span!("recv", space = ?packet.header.space()), +4296 }; +4297 let _guard = span.enter(); +4298 +4299 // Now the packet is authenticated we do the migration during the handshake, +4300 // see Handshake::allow_server_migration for details. Be careful here to +4301 // not yet rely on the path existing however, new paths are accepted and +4302 // created later. +4303 // Note that we can't do any other migrations yet, for those we need to know +4304 // whether this was a probing packet or not. See the end of +4305 // Self::process_packet for that. +4306 if self.is_handshaking() +4307 && self +4308 .path(path_id) +4309 .map(|path_data| { +4310 !path_data.network_path.is_probably_same_path(&network_path) +4311 }) +4312 .unwrap_or(false) +4313 { +4314 if let Some(hs) = self.state.as_handshake() +4315 && hs.allow_server_migration +4316 { +4317 trace!( +4318 %network_path, +4319 prev = %self.path_data(path_id).network_path, +4320 "server migrated to new remote", +4321 ); +4322 self.path_data_mut(path_id).network_path = network_path; +4323 self.qlog.emit_tuple_assigned(path_id, network_path, now); +4324 } else { +4325 debug!( +4326 recv_path = %network_path, +4327 expected_path = %self.path_data_mut(path_id).network_path, +4328 "discarding packet with unexpected remote during handshake", +4329 ); +4330 return; +4331 } +4332 } +4333 +4334 let dedup = self.spaces[packet.header.space()] +4335 .path_space_mut(path_id) +4336 .map(|pns| &mut pns.dedup); +4337 if pn.zip(dedup).is_some_and(|(n, d)| d.insert(n)) { +4338 debug!("discarding possible duplicate packet"); +4339 self.qlog.emit_packet_received(qlog, now); +4340 return; +4341 } else if self.state.is_handshake() && packet.header.is_short() { +4342 // TODO: SHOULD buffer these to improve reordering tolerance. +4343 trace!("dropping short packet during handshake"); +4344 self.qlog.emit_packet_received(qlog, now); +4345 return; +4346 } else { +4347 if let Header::Initial(InitialHeader { ref token, .. }) = packet.header +4348 && let Some(hs) = self.state.as_handshake() +4349 && self.side.is_server() +4350 && token != &hs.expected_token +4351 { +4352 // Clients must send the same retry token in every Initial. Initial +4353 // packets can be spoofed, so we discard rather than killing the +4354 // connection. +4355 warn!("discarding Initial with invalid retry token"); +4356 self.qlog.emit_packet_received(qlog, now); +4357 return; +4358 } +4359 +4360 if !self.state.is_closed() { +4361 let spin = match packet.header { +4362 Header::Short { spin, .. } => spin, +4363 _ => false, +4364 }; +4365 +4366 if self.side().is_server() && !self.abandoned_paths.contains(&path_id) { +4367 // Only the client is allowed to open paths +4368 self.ensure_path(path_id, network_path, now, pn); +4369 } +4370 if self.paths.contains_key(&path_id) { +4371 self.on_packet_authenticated( +4372 now, +4373 packet.header.space(), +4374 path_id, +4375 ecn, +4376 pn, +4377 spin, +4378 packet.header.is_1rtt(), +4379 &network_path, +4380 ); +4381 } +4382 } +4383 +4384 let res = self.process_decrypted_packet( +4385 now, +4386 network_path, +4387 path_id, +4388 pn, +4389 packet, +4390 &mut qlog, +4391 ); +4392 +4393 self.qlog.emit_packet_received(qlog, now); +4394 res +4395 } +4396 } +4397 }; +4398 +4399 // State transitions for error cases +4400 if let Err(conn_err) = result { +4401 match conn_err { +4402 ConnectionError::ApplicationClosed(reason) => self.state.move_to_closed(reason), +4403 ConnectionError::ConnectionClosed(reason) => self.state.move_to_closed(reason), +4404 ConnectionError::Reset +4405 | ConnectionError::TransportError(TransportError { +4406 code: TransportErrorCode::AEAD_LIMIT_REACHED, +4407 .. +4408 }) => { +4409 let was_draining = self.state.move_to_drained(Some(conn_err)); +4410 if !was_draining { +4411 self.endpoint_events.push_back(EndpointEventInner::Draining); +4412 } +4413 } +4414 ConnectionError::TimedOut => { +4415 unreachable!("timeouts aren't generated by packet processing"); +4416 } +4417 ConnectionError::TransportError(err) => { +4418 debug!("closing connection due to transport error: {}", err); +4419 self.state.move_to_closed(err); +4420 } +4421 ConnectionError::VersionMismatch => { +4422 self.state.move_to_draining(Some(conn_err)); +4423 self.endpoint_events.push_back(EndpointEventInner::Draining); +4424 } +4425 ConnectionError::LocallyClosed => { +4426 unreachable!("LocallyClosed isn't generated by packet processing"); +4427 } +4428 ConnectionError::CidsExhausted => { +4429 unreachable!("CidsExhausted isn't generated by packet processing"); +4430 } +4431 }; +4432 } +4433 +4434 if !was_closed && self.state.is_closed() { +4435 self.close_common(); +4436 if !self.state.is_drained() { +4437 self.set_close_timer(now); +4438 } +4439 } +4440 if !was_drained && self.state.is_drained() { +4441 self.endpoint_events.push_back(EndpointEventInner::Drained); +4442 // Close timer may have been started previously, e.g. if we sent a close and got a +4443 // stateless reset in response +4444 self.timers +4445 .stop(Timer::Conn(ConnTimer::Close), self.qlog.with_time(now)); +4446 } +4447 +4448 // Transmit CONNECTION_CLOSE if necessary. +4449 // +4450 // If we received a valid packet and we are in the closed state we should respond +4451 // with a CONNECTION_CLOSE frame. +4452 // TODO: This SHOULD be rate-limited according to §10.2.1 of QUIC-TRANSPORT, but +4453 // that does not yet happen. This is triggered by each received packet. +4454 if matches!(self.state.as_type(), StateType::Closed) { +4455 // From https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1-7 +4456 // +4457 // While in the closing state we must either: +4458 // - discard packets coming from an un-validated remote OR +4459 // - ensure we do not send more than 3 times the received data +4460 // +4461 // Doing the 2nd would mean we would be able to send CONNECTION_CLOSE to a peer +4462 // who was (involuntary) migrated just at the time we initiated immediate +4463 // close. It is a lot more work though. So while we would like to do this for +4464 // now we only do 1. +4465 // +4466 // Another shortcoming of the current implementation is that when we have a +4467 // previous PathData which is validated and the remote matches that path, we +4468 // should schedule CONNECTION_CLOSE on that path. However currently we can not +4469 // schedule such a packet. We should also fix this some day. This makes us +4470 // vulnerable to an attacker faking a migration at the right time and then we'd +4471 // be unable to send the CONNECTION_CLOSE to the real remote. +4472 if self +4473 .paths +4474 .get(&path_id) +4475 .map(|p| p.data.validated && p.data.network_path == network_path) +4476 .unwrap_or(false) +4477 { +4478 self.connection_close_pending = true; +4479 } +4480 } +4481 } +4482 +4483 fn process_decrypted_packet( +4484 &mut self, +4485 now: Instant, +4486 network_path: FourTuple, +4487 path_id: PathId, +4488 number: Option<u64>, +4489 packet: Packet, +4490 qlog: &mut QlogRecvPacket, +4491 ) -> Result<(), ConnectionError> { +4492 if !self.paths.contains_key(&path_id) { +4493 // There is a chance this is a server side, first (for this path) packet, which would +4494 // be a protocol violation. It's more likely, however, that this is a packet of a +4495 // pruned path +4496 trace!(%path_id, ?number, "discarding packet for unknown path"); +4497 return Ok(()); +4498 } +4499 let state = match self.state.as_type() { +4500 StateType::Established => { +4501 match packet.header.space() { +4502 SpaceKind::Data => self.process_payload( +4503 now, +4504 network_path, +4505 path_id, +4506 number.unwrap(), +4507 packet, +4508 qlog, +4509 )?, +4510 _ if packet.header.has_frames() => { +4511 self.process_early_payload(now, path_id, packet, qlog)? +4512 } +4513 _ => { +4514 trace!("discarding unexpected pre-handshake packet"); +4515 } +4516 } +4517 return Ok(()); +4518 } +4519 StateType::Closed => { +4520 for result in frame::Iter::new(packet.payload.freeze())? { +4521 let frame = match result { +4522 Ok(frame) => frame, +4523 Err(err) => { +4524 debug!("frame decoding error: {err:?}"); +4525 continue; +4526 } +4527 }; +4528 qlog.frame(&frame); +4529 +4530 if let Frame::Padding = frame { +4531 continue; +4532 }; +4533 +4534 trace!(?frame, "processing frame in closed state"); +4535 +4536 self.path_stats.get_mut(path_id).frame_rx.record(frame.ty()); +4537 +4538 if let Frame::Close(_error) = frame { +4539 self.state.move_to_draining(None); +4540 self.endpoint_events.push_back(EndpointEventInner::Draining); +4541 break; +4542 } +4543 } +4544 return Ok(()); +4545 } +4546 StateType::Draining | StateType::Drained => return Ok(()), +4547 StateType::Handshake => self.state.as_handshake_mut().expect("checked"), +4548 }; +4549 +4550 match packet.header { +4551 Header::Retry { +4552 src_cid: remote_cid, +4553 .. +4554 } => { +4555 debug_assert_eq!(path_id, PathId::ZERO); +4556 if self.side.is_server() { +4557 return Err(TransportError::PROTOCOL_VIOLATION("client sent Retry").into()); +4558 } +4559 +4560 let is_valid_retry = self +4561 .remote_cids +4562 .get(&path_id) +4563 .map(|cids| cids.active()) +4564 .map(|orig_dst_cid| { +4565 self.crypto_state.session.is_valid_retry( +4566 orig_dst_cid, +4567 &packet.header_data, +4568 &packet.payload, +4569 ) +4570 }) +4571 .unwrap_or_default(); +4572 if self.total_authed_packets > 1 +4573 || packet.payload.len() <= 16 // token + 16 byte tag +4574 || !is_valid_retry +4575 { +4576 trace!("discarding invalid Retry"); +4577 // - After the client has received and processed an Initial or Retry +4578 // packet from the server, it MUST discard any subsequent Retry +4579 // packets that it receives. +4580 // - A client MUST discard a Retry packet with a zero-length Retry Token +4581 // field. +4582 // - Clients MUST discard Retry packets that have a Retry Integrity Tag +4583 // that cannot be validated +4584 return Ok(()); +4585 } +4586 +4587 trace!("retrying with CID {}", remote_cid); +4588 let client_hello = state.client_hello.take().unwrap(); +4589 self.retry_src_cid = Some(remote_cid); +4590 self.remote_cids +4591 .get_mut(&path_id) +4592 .expect("PathId::ZERO not yet abandoned, is_valid_retry would have been false") +4593 .update_initial_cid(remote_cid); +4594 self.remote_handshake_cid = remote_cid; +4595 +4596 let space = &mut self.spaces[SpaceId::Initial]; +4597 if let Some(info) = space.for_path(PathId::ZERO).take(0) { +4598 self.on_packet_acked(now, PathId::ZERO, 0, info); +4599 }; +4600 +4601 self.discard_space(now, SpaceKind::Initial); // Make sure we clean up after +4602 // any retransmitted Initials +4603 let crypto_space = &mut self.crypto_state.spaces[SpaceKind::Initial]; +4604 crypto_space.keys = Some( +4605 self.crypto_state +4606 .session +4607 .initial_keys(remote_cid, self.side.side()), +4608 ); +4609 crypto_space.crypto_offset = client_hello.len() as u64; +4610 +4611 let next_pn = self.spaces[SpaceId::Initial] +4612 .for_path(path_id) +4613 .next_packet_number; +4614 self.spaces[SpaceId::Initial] = { +4615 let mut space = PacketSpace::new(now, SpaceId::Initial, &mut self.rng); +4616 space.for_path(path_id).next_packet_number = next_pn; +4617 space.pending.crypto.push_back(frame::Crypto { +4618 offset: 0, +4619 data: client_hello, +4620 }); +4621 space +4622 }; +4623 +4624 // Retransmit all 0-RTT data +4625 let zero_rtt = mem::take( +4626 &mut self.spaces[SpaceId::Data] +4627 .for_path(PathId::ZERO) +4628 .sent_packets, +4629 ); +4630 for (_, info) in zero_rtt.into_iter() { +4631 self.paths +4632 .get_mut(&PathId::ZERO) +4633 .unwrap() +4634 .remove_in_flight(&info); +4635 self.spaces[SpaceId::Data].pending |= info.retransmits; +4636 } +4637 self.streams.retransmit_all_for_0rtt(); 4638 -4639 if self.side.is_client() { -4640 // Client-only because server params were set from the client's Initial -4641 let params = self -4642 .crypto_state -4643 .session -4644 .transport_parameters()? -4645 .ok_or_else(|| { -4646 TransportError::new( -4647 TransportErrorCode::crypto(0x6d), -4648 "transport parameters missing".to_owned(), -4649 ) -4650 })?; -4651 -4652 if self.has_0rtt() { -4653 if !self.crypto_state.session.early_data_accepted().unwrap() { -4654 debug_assert!(self.side.is_client()); -4655 debug!("0-RTT rejected"); -4656 self.crypto_state.accepted_0rtt = false; -4657 self.streams.zero_rtt_rejected(); -4658 -4659 // Discard already-queued frames -4660 self.spaces[SpaceId::Data].pending = Retransmits::default(); -4661 -4662 // Discard 0-RTT packets -4663 let sent_packets = mem::take( -4664 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, -4665 ); -4666 for (_, packet) in sent_packets.into_iter() { -4667 self.paths -4668 .get_mut(&path_id) -4669 .unwrap() -4670 .remove_in_flight(&packet); -4671 } -4672 } else { -4673 self.crypto_state.accepted_0rtt = true; -4674 params.validate_resumption_from(&self.peer_params)?; -4675 } -4676 } -4677 if let Some(token) = params.stateless_reset_token { -4678 let remote = self.path_data(path_id).network_path.remote; -4679 debug_assert!(!self.state.is_drained()); // requirement for endpoint events, checked above -4680 self.endpoint_events -4681 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); -4682 } -4683 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4684 self.issue_first_cids(now); -4685 } else { -4686 // Server-only -4687 self.spaces[SpaceId::Data].pending.handshake_done = true; -4688 self.discard_space(now, SpaceKind::Handshake); -4689 self.events.push_back(Event::HandshakeConfirmed); -4690 trace!("handshake confirmed"); -4691 } -4692 -4693 self.events.push_back(Event::Connected); -4694 self.state.move_to_established(); -4695 trace!("established"); -4696 -4697 // Multipath can only be enabled after the state has reached Established. -4698 // So this can not happen any earlier. -4699 self.issue_first_path_cids(now); -4700 Ok(()) -4701 } -4702 Header::Initial(InitialHeader { -4703 src_cid: remote_cid, -4704 dst_cid: local_cid, -4705 .. -4706 }) => { -4707 debug_assert_eq!(path_id, PathId::ZERO); -4708 if !state.remote_cid_set { -4709 trace!("switching remote CID to {}", remote_cid); -4710 let mut state = state.clone(); -4711 self.remote_cids -4712 .get_mut(&path_id) -4713 .expect("PathId::ZERO not yet abandoned") -4714 .update_initial_cid(remote_cid); -4715 self.remote_handshake_cid = remote_cid; -4716 self.original_remote_cid = remote_cid; -4717 state.remote_cid_set = true; -4718 self.state.move_to_handshake(state); -4719 } else if remote_cid != self.remote_handshake_cid { -4720 debug!( -4721 "discarding packet with mismatched remote CID: {} != {}", -4722 self.remote_handshake_cid, remote_cid -4723 ); -4724 return Ok(()); -4725 } -4726 -4727 let starting_space = self.highest_space; -4728 self.process_early_payload(now, path_id, packet, qlog)?; -4729 -4730 if self.side.is_server() -4731 && starting_space == SpaceKind::Initial -4732 && self.highest_space != SpaceKind::Initial -4733 { -4734 let params = self -4735 .crypto_state -4736 .session -4737 .transport_parameters()? -4738 .ok_or_else(|| { -4739 TransportError::new( -4740 TransportErrorCode::crypto(0x6d), -4741 "transport parameters missing".to_owned(), -4742 ) -4743 })?; -4744 self.handle_peer_params(params, local_cid, remote_cid, now)?; -4745 self.issue_first_cids(now); -4746 self.init_0rtt(now); -4747 } -4748 Ok(()) -4749 } -4750 Header::Long { -4751 ty: LongType::ZeroRtt, -4752 .. -4753 } => { -4754 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; -4755 Ok(()) -4756 } -4757 Header::VersionNegotiate { .. } => { -4758 if self.total_authed_packets > 1 { -4759 return Ok(()); -4760 } -4761 let supported = packet -4762 .payload -4763 .chunks(4) -4764 .any(|x| match <[u8; 4]>::try_from(x) { -4765 Ok(version) => self.version == u32::from_be_bytes(version), -4766 Err(_) => false, -4767 }); -4768 if supported { -4769 return Ok(()); -4770 } -4771 debug!("remote doesn't support our version"); -4772 Err(ConnectionError::VersionMismatch) -4773 } -4774 Header::Short { .. } => unreachable!( -4775 "short packets received during handshake are discarded in handle_packet" -4776 ), -4777 } -4778 } -4779 -4780 /// Process an Initial or Handshake packet payload -4781 fn process_early_payload( -4782 &mut self, -4783 now: Instant, -4784 path_id: PathId, -4785 packet: Packet, -4786 #[allow(unused)] qlog: &mut QlogRecvPacket, -4787 ) -> Result<(), TransportError> { -4788 debug_assert_ne!(packet.header.space(), SpaceKind::Data); -4789 debug_assert_eq!(path_id, PathId::ZERO); -4790 let payload_len = packet.payload.len(); -4791 let mut ack_eliciting = false; -4792 for result in frame::Iter::new(packet.payload.freeze())? { -4793 let frame = result?; -4794 qlog.frame(&frame); -4795 let span = match frame { -4796 Frame::Padding => continue, -4797 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), -4798 }; -4799 -4800 self.path_stats -4801 .for_path(path_id) -4802 .frame_rx -4803 .record(frame.ty()); -4804 -4805 let _guard = span.as_ref().map(|x| x.enter()); -4806 ack_eliciting |= frame.is_ack_eliciting(); -4807 -4808 // Process frames -4809 if frame.is_1rtt() && packet.header.space() != SpaceKind::Data { -4810 return Err(TransportError::PROTOCOL_VIOLATION( -4811 "illegal frame type in handshake", -4812 )); +4639 let token_len = packet.payload.len() - 16; +4640 let ConnectionSide::Client { ref mut token, .. } = self.side else { +4641 unreachable!("we already short-circuited if we're server"); +4642 }; +4643 *token = packet.payload.freeze().split_to(token_len); +4644 +4645 self.state = State::handshake(state::Handshake { +4646 expected_token: Bytes::new(), +4647 remote_cid_set: false, +4648 client_hello: None, +4649 allow_server_migration: self.config.server_handshake_migration, +4650 }); +4651 Ok(()) +4652 } +4653 Header::Long { +4654 ty: LongType::Handshake, +4655 src_cid: remote_cid, +4656 dst_cid: local_cid, +4657 .. +4658 } => { +4659 debug_assert_eq!(path_id, PathId::ZERO); +4660 if remote_cid != self.remote_handshake_cid { +4661 debug!( +4662 "discarding packet with mismatched remote CID: {} != {}", +4663 self.remote_handshake_cid, remote_cid +4664 ); +4665 return Ok(()); +4666 } +4667 self.on_path_validated(path_id); +4668 +4669 self.process_early_payload(now, path_id, packet, qlog)?; +4670 if self.state.is_closed() { +4671 return Ok(()); +4672 } +4673 +4674 if self.crypto_state.session.is_handshaking() { +4675 trace!("handshake ongoing"); +4676 return Ok(()); +4677 } +4678 +4679 if self.side.is_client() { +4680 // Client-only because server params were set from the client's Initial +4681 let params = self +4682 .crypto_state +4683 .session +4684 .transport_parameters()? +4685 .ok_or_else(|| { +4686 TransportError::new( +4687 TransportErrorCode::crypto(0x6d), +4688 "transport parameters missing".to_owned(), +4689 ) +4690 })?; +4691 +4692 if self.has_0rtt() { +4693 if !self.crypto_state.session.early_data_accepted().unwrap() { +4694 debug_assert!(self.side.is_client()); +4695 debug!("0-RTT rejected"); +4696 self.crypto_state.accepted_0rtt = false; +4697 self.streams.zero_rtt_rejected(); +4698 +4699 // Discard already-queued frames +4700 self.spaces[SpaceId::Data].pending = Retransmits::default(); +4701 +4702 // Discard 0-RTT packets +4703 let sent_packets = mem::take( +4704 &mut self.spaces[SpaceId::Data].for_path(path_id).sent_packets, +4705 ); +4706 for (_, packet) in sent_packets.into_iter() { +4707 self.paths +4708 .get_mut(&path_id) +4709 .unwrap() +4710 .remove_in_flight(&packet); +4711 } +4712 } else { +4713 self.crypto_state.accepted_0rtt = true; +4714 params.validate_resumption_from(&self.peer_params)?; +4715 } +4716 } +4717 if let Some(token) = params.stateless_reset_token { +4718 let remote = self.path_data(path_id).network_path.remote; +4719 debug_assert!(!self.state.is_drained()); // requirement for endpoint events, checked above +4720 self.endpoint_events +4721 .push_back(EndpointEventInner::ResetToken(path_id, remote, token)); +4722 } +4723 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4724 self.issue_first_cids(now); +4725 } else { +4726 // Server-only +4727 self.spaces[SpaceId::Data].pending.handshake_done = true; +4728 self.discard_space(now, SpaceKind::Handshake); +4729 self.events.push_back(Event::HandshakeConfirmed); +4730 trace!("handshake confirmed"); +4731 } +4732 +4733 self.events.push_back(Event::Connected); +4734 self.state.move_to_established(); +4735 trace!("established"); +4736 +4737 // Multipath can only be enabled after the state has reached Established. +4738 // So this can not happen any earlier. +4739 self.issue_first_path_cids(now); +4740 Ok(()) +4741 } +4742 Header::Initial(InitialHeader { +4743 src_cid: remote_cid, +4744 dst_cid: local_cid, +4745 .. +4746 }) => { +4747 debug_assert_eq!(path_id, PathId::ZERO); +4748 if !state.remote_cid_set { +4749 trace!("switching remote CID to {}", remote_cid); +4750 let mut state = state.clone(); +4751 self.remote_cids +4752 .get_mut(&path_id) +4753 .expect("PathId::ZERO not yet abandoned") +4754 .update_initial_cid(remote_cid); +4755 self.remote_handshake_cid = remote_cid; +4756 self.original_remote_cid = remote_cid; +4757 state.remote_cid_set = true; +4758 self.state.move_to_handshake(state); +4759 } else if remote_cid != self.remote_handshake_cid { +4760 debug!( +4761 "discarding packet with mismatched remote CID: {} != {}", +4762 self.remote_handshake_cid, remote_cid +4763 ); +4764 return Ok(()); +4765 } +4766 +4767 let starting_space = self.highest_space; +4768 self.process_early_payload(now, path_id, packet, qlog)?; +4769 +4770 if self.side.is_server() +4771 && starting_space == SpaceKind::Initial +4772 && self.highest_space != SpaceKind::Initial +4773 { +4774 let params = self +4775 .crypto_state +4776 .session +4777 .transport_parameters()? +4778 .ok_or_else(|| { +4779 TransportError::new( +4780 TransportErrorCode::crypto(0x6d), +4781 "transport parameters missing".to_owned(), +4782 ) +4783 })?; +4784 self.handle_peer_params(params, local_cid, remote_cid, now)?; +4785 self.issue_first_cids(now); +4786 self.init_0rtt(now); +4787 } +4788 Ok(()) +4789 } +4790 Header::Long { +4791 ty: LongType::ZeroRtt, +4792 .. +4793 } => { +4794 self.process_payload(now, network_path, path_id, number.unwrap(), packet, qlog)?; +4795 Ok(()) +4796 } +4797 Header::VersionNegotiate { .. } => { +4798 if self.total_authed_packets > 1 { +4799 return Ok(()); +4800 } +4801 let supported = packet +4802 .payload +4803 .chunks(4) +4804 .any(|x| match <[u8; 4]>::try_from(x) { +4805 Ok(version) => self.version == u32::from_be_bytes(version), +4806 Err(_) => false, +4807 }); +4808 if supported { +4809 return Ok(()); +4810 } +4811 debug!("remote doesn't support our version"); +4812 Err(ConnectionError::VersionMismatch) 4813 } -4814 -4815 match frame { -4816 Frame::Padding | Frame::Ping => {} -4817 Frame::Crypto(frame) => { -4818 self.read_crypto(packet.header.space().into(), &frame, payload_len)?; -4819 } -4820 Frame::Ack(ack) => { -4821 self.on_ack_received(now, packet.header.space().into(), ack)?; -4822 } -4823 Frame::PathAck(ack) => { -4824 span.as_ref() -4825 .map(|span| span.record("path", tracing::field::display(&ack.path_id))); -4826 self.on_path_ack_received(now, packet.header.space().into(), ack)?; -4827 } -4828 Frame::Close(reason) => { -4829 self.state.move_to_draining(Some(reason.into())); -4830 return Ok(()); -4831 } -4832 _ => { -4833 let mut err = -4834 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); -4835 err.frame = frame::MaybeFrame::Known(frame.ty()); -4836 return Err(err); -4837 } -4838 } -4839 } -4840 -4841 if ack_eliciting { -4842 // In the initial and handshake spaces, ACKs must be sent immediately -4843 self.spaces[packet.header.space()] -4844 .for_path(path_id) -4845 .pending_acks -4846 .set_immediate_ack_required(); -4847 } -4848 -4849 self.write_crypto(); -4850 Ok(()) -4851 } -4852 -4853 /// Processes the decrypted packet payload, always in the data space. -4854 fn process_payload( -4855 &mut self, -4856 now: Instant, -4857 network_path: FourTuple, -4858 path_id: PathId, -4859 number: u64, -4860 packet: Packet, -4861 #[allow(unused)] qlog: &mut QlogRecvPacket, -4862 ) -> Result<(), TransportError> { -4863 let is_multipath_negotiated = self.is_multipath_negotiated(); -4864 let payload = packet.payload.freeze(); -4865 let mut is_probing_packet = true; -4866 let mut close = None; -4867 let payload_len = payload.len(); -4868 let mut ack_eliciting = false; -4869 // if this packet triggers a path migration and includes a observed address frame, it's -4870 // stored here -4871 let mut migration_observed_addr = None; -4872 for result in frame::Iter::new(payload)? { -4873 let frame = result?; -4874 qlog.frame(&frame); -4875 let span = match frame { -4876 Frame::Padding => continue, -4877 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), -4878 }; -4879 -4880 self.path_stats -4881 .for_path(path_id) -4882 .frame_rx -4883 .record(frame.ty()); -4884 // Crypto, Stream and Datagram frames are special cased in order no pollute -4885 // the log with payload data -4886 match &frame { -4887 Frame::Crypto(f) => { -4888 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); -4889 } -4890 Frame::Stream(f) => { -4891 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); -4892 } -4893 Frame::Datagram(f) => { -4894 trace!(len = f.data.len(), "got frame DATAGRAM"); -4895 } -4896 f => { -4897 trace!("got frame {f}"); -4898 } -4899 } -4900 -4901 let _guard = span.enter(); -4902 if packet.header.is_0rtt() { -4903 match frame { -4904 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { -4905 return Err(TransportError::PROTOCOL_VIOLATION( -4906 "illegal frame type in 0-RTT", -4907 )); -4908 } -4909 _ => { -4910 if frame.is_1rtt() { -4911 return Err(TransportError::PROTOCOL_VIOLATION( -4912 "illegal frame type in 0-RTT", -4913 )); -4914 } -4915 } -4916 } -4917 } -4918 ack_eliciting |= frame.is_ack_eliciting(); -4919 -4920 // Check whether this could be a probing packet -4921 match frame { -4922 Frame::Padding -4923 | Frame::PathChallenge(_) -4924 | Frame::PathResponse(_) -4925 | Frame::NewConnectionId(_) -4926 | Frame::ObservedAddr(_) => {} -4927 _ => { -4928 is_probing_packet = false; +4814 Header::Short { .. } => unreachable!( +4815 "short packets received during handshake are discarded in handle_packet" +4816 ), +4817 } +4818 } +4819 +4820 /// Process an Initial or Handshake packet payload +4821 fn process_early_payload( +4822 &mut self, +4823 now: Instant, +4824 path_id: PathId, +4825 packet: Packet, +4826 #[allow(unused)] qlog: &mut QlogRecvPacket, +4827 ) -> Result<(), TransportError> { +4828 debug_assert_ne!(packet.header.space(), SpaceKind::Data); +4829 debug_assert_eq!(path_id, PathId::ZERO); +4830 let payload_len = packet.payload.len(); +4831 let mut ack_eliciting = false; +4832 for result in frame::Iter::new(packet.payload.freeze())? { +4833 let frame = result?; +4834 qlog.frame(&frame); +4835 let span = match frame { +4836 Frame::Padding => continue, +4837 _ => Some(trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty)), +4838 }; +4839 +4840 self.path_stats.get_mut(path_id).frame_rx.record(frame.ty()); +4841 +4842 let _guard = span.as_ref().map(|x| x.enter()); +4843 ack_eliciting |= frame.is_ack_eliciting(); +4844 +4845 // Process frames +4846 if frame.is_1rtt() && packet.header.space() != SpaceKind::Data { +4847 return Err(TransportError::PROTOCOL_VIOLATION( +4848 "illegal frame type in handshake", +4849 )); +4850 } +4851 +4852 match frame { +4853 Frame::Padding | Frame::Ping => {} +4854 Frame::Crypto(frame) => { +4855 self.read_crypto(packet.header.space().into(), &frame, payload_len)?; +4856 } +4857 Frame::Ack(ack) => { +4858 self.on_ack_received(now, packet.header.space().into(), ack)?; +4859 } +4860 Frame::PathAck(ack) => { +4861 span.as_ref() +4862 .map(|span| span.record("path", tracing::field::display(&ack.path_id))); +4863 self.on_path_ack_received(now, packet.header.space().into(), ack)?; +4864 } +4865 Frame::Close(reason) => { +4866 self.state.move_to_draining(Some(reason.into())); +4867 self.endpoint_events.push_back(EndpointEventInner::Draining); +4868 return Ok(()); +4869 } +4870 _ => { +4871 let mut err = +4872 TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); +4873 err.frame = frame::MaybeFrame::Known(frame.ty()); +4874 return Err(err); +4875 } +4876 } +4877 } +4878 +4879 if ack_eliciting { +4880 // In the initial and handshake spaces, ACKs must be sent immediately +4881 self.spaces[packet.header.space()] +4882 .for_path(path_id) +4883 .pending_acks +4884 .set_immediate_ack_required(); +4885 } +4886 +4887 self.write_crypto(); +4888 Ok(()) +4889 } +4890 +4891 /// Processes the decrypted packet payload, always in the data space. +4892 fn process_payload( +4893 &mut self, +4894 now: Instant, +4895 network_path: FourTuple, +4896 path_id: PathId, +4897 number: u64, +4898 packet: Packet, +4899 #[allow(unused)] qlog: &mut QlogRecvPacket, +4900 ) -> Result<(), TransportError> { +4901 let payload = packet.payload.freeze(); +4902 let mut is_probing_packet = true; +4903 let mut close = None; +4904 let payload_len = payload.len(); +4905 let mut ack_eliciting = false; +4906 // if this packet triggers a path migration and includes a observed address frame, it's +4907 // stored here +4908 let mut migration_observed_addr = None; +4909 for result in frame::Iter::new(payload)? { +4910 let frame = result?; +4911 qlog.frame(&frame); +4912 let span = match frame { +4913 Frame::Padding => continue, +4914 _ => trace_span!("frame", ty = %frame.ty(), path = tracing::field::Empty), +4915 }; +4916 +4917 self.path_stats.get_mut(path_id).frame_rx.record(frame.ty()); +4918 // Crypto, Stream and Datagram frames are special cased in order no pollute +4919 // the log with payload data +4920 match &frame { +4921 Frame::Crypto(f) => { +4922 trace!(offset = f.offset, len = f.data.len(), "got frame CRYPTO"); +4923 } +4924 Frame::Stream(f) => { +4925 trace!(id = %f.id, offset = f.offset, len = f.data.len(), fin = f.fin, "got frame STREAM"); +4926 } +4927 Frame::Datagram(f) => { +4928 trace!(len = f.data.len(), "got frame DATAGRAM"); 4929 } -4930 } -4931 -4932 match frame { -4933 Frame::Crypto(frame) => { -4934 self.read_crypto(SpaceId::Data, &frame, payload_len)?; -4935 } -4936 Frame::Stream(frame) => { -4937 if self.streams.received(frame, payload_len)?.should_transmit() { -4938 self.spaces[SpaceId::Data].pending.max_data = true; -4939 } -4940 } -4941 Frame::Ack(ack) => { -4942 self.on_ack_received(now, SpaceId::Data, ack)?; -4943 } -4944 Frame::PathAck(ack) => { -4945 if !self.is_multipath_negotiated() { -4946 return Err(TransportError::PROTOCOL_VIOLATION( -4947 "received PATH_ACK frame when multipath was not negotiated", -4948 )); +4930 f => { +4931 trace!("got frame {f}"); +4932 } +4933 } +4934 +4935 let _guard = span.enter(); +4936 if packet.header.is_0rtt() { +4937 match frame { +4938 Frame::Crypto(_) | Frame::Close(Close::Application(_)) => { +4939 return Err(TransportError::PROTOCOL_VIOLATION( +4940 "illegal frame type in 0-RTT", +4941 )); +4942 } +4943 _ => { +4944 if frame.is_1rtt() { +4945 return Err(TransportError::PROTOCOL_VIOLATION( +4946 "illegal frame type in 0-RTT", +4947 )); +4948 } 4949 } -4950 span.record("path", tracing::field::display(&ack.path_id)); -4951 self.on_path_ack_received(now, SpaceId::Data, ack)?; -4952 } -4953 Frame::Padding | Frame::Ping => {} -4954 Frame::Close(reason) => { -4955 close = Some(reason); -4956 } -4957 Frame::PathChallenge(challenge) => { -4958 let path = &mut self -4959 .path_mut(path_id) -4960 .expect("payload is processed only after the path becomes known"); -4961 path.path_responses.push(number, challenge.0, network_path); -4962 // If we were passively migrated (e.g. NAT rebinding), our local_ip will -4963 // not match. Once we processed a non-probing packet the local_ip will -4964 // finally be updated. -4965 if network_path.remote == path.network_path.remote { -4966 // PATH_CHALLENGE on active path, possible off-path packet -4967 // forwarding attack. Send a non-probing packet to recover the -4968 // active path. See -4969 // https://www.rfc-editor.org/rfc/rfc9000.html#section-9.3.3-3. In -4970 // rare cases NAT probes might also appear on-path and would also -4971 // get a non-probing packet as response. There is little harm in -4972 // this. -4973 match self.peer_supports_ack_frequency() { -4974 true => self.immediate_ack(path_id), -4975 false => { -4976 self.ping_path(path_id).ok(); -4977 } -4978 } -4979 } -4980 } -4981 Frame::PathResponse(response) => { -4982 // First try to see if this is a NAT probe response. -4983 if self -4984 .n0_nat_traversal -4985 .handle_path_response(network_path, response.0) -4986 { -4987 self.open_nat_traversed_paths(now); -4988 } else { -4989 // Try to see if this is a response to an on-path PATH_CHALLENGE. -4990 -4991 let path = self -4992 .paths -4993 .get_mut(&path_id) -4994 .expect("payload is processed only after the path becomes known"); -4995 -4996 use PathTimer::*; -4997 use paths::OnPathResponseReceived::*; -4998 match path -4999 .data -5000 .on_path_response_received(now, response.0, network_path) -5001 { -5002 OnPath { was_open } => { -5003 let qlog = self.qlog.with_time(now); -5004 -5005 self.timers.stop( -5006 Timer::PerPath(path_id, PathValidationFailed), -5007 qlog.clone(), -5008 ); -5009 self.timers.stop( -5010 Timer::PerPath(path_id, AbandonFromValidation), -5011 qlog.clone(), -5012 ); -5013 -5014 let next_challenge = path -5015 .data -5016 .earliest_on_path_expiring_challenge() -5017 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); -5018 self.timers.set_or_stop( -5019 Timer::PerPath(path_id, PathChallengeLost), -5020 next_challenge, -5021 qlog, -5022 ); -5023 -5024 if !was_open { -5025 if is_multipath_negotiated { -5026 self.events.push_back(Event::Path( -5027 PathEvent::Established { id: path_id }, -5028 )); -5029 } -5030 if let Some(observed) = -5031 path.data.last_observed_addr_report.as_ref() -5032 { -5033 self.events.push_back(Event::Path( -5034 PathEvent::ObservedAddr { -5035 id: path_id, -5036 addr: observed.socket_addr(), -5037 }, -5038 )); -5039 } -5040 } -5041 if let Some((_, ref mut prev)) = path.prev { -5042 // If an on-path response was received while there is a -5043 // previous path from a migration, then the new path is -5044 // validated and we can stop sending challenges that try to -5045 // re-validate the previous path. -5046 prev.reset_on_path_challenges(); -5047 } -5048 } -5049 Ignored { -5050 sent_on, -5051 current_path, -5052 } => { -5053 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE") -5054 } -5055 Unknown => debug!(%response, "ignoring invalid PATH_RESPONSE"), -5056 } -5057 } +4950 } +4951 } +4952 ack_eliciting |= frame.is_ack_eliciting(); +4953 +4954 // Check whether this could be a probing packet +4955 match frame { +4956 Frame::Padding +4957 | Frame::PathChallenge(_) +4958 | Frame::PathResponse(_) +4959 | Frame::NewConnectionId(_) +4960 | Frame::ObservedAddr(_) => {} +4961 _ => { +4962 is_probing_packet = false; +4963 } +4964 } +4965 +4966 match frame { +4967 Frame::Crypto(frame) => { +4968 self.read_crypto(SpaceId::Data, &frame, payload_len)?; +4969 } +4970 Frame::Stream(frame) => { +4971 if self.streams.received(frame, payload_len)?.should_transmit() { +4972 self.spaces[SpaceId::Data].pending.max_data = true; +4973 } +4974 } +4975 Frame::Ack(ack) => { +4976 self.on_ack_received(now, SpaceId::Data, ack)?; +4977 } +4978 Frame::PathAck(ack) => { +4979 if !self.is_multipath_negotiated() { +4980 return Err(TransportError::PROTOCOL_VIOLATION( +4981 "received PATH_ACK frame when multipath was not negotiated", +4982 )); +4983 } +4984 span.record("path", tracing::field::display(&ack.path_id)); +4985 self.on_path_ack_received(now, SpaceId::Data, ack)?; +4986 } +4987 Frame::Padding | Frame::Ping => {} +4988 Frame::Close(reason) => { +4989 close = Some(reason); +4990 } +4991 Frame::PathChallenge(challenge) => { +4992 self.spaces[SpaceKind::Data] +4993 .for_path(path_id) +4994 .pending_path_responses +4995 .push(number, challenge.0, network_path); +4996 // If we were passively migrated (e.g. NAT rebinding), our local_ip will +4997 // not match. Once we processed a non-probing packet the local_ip will +4998 // finally be updated. +4999 let path = &mut self +5000 .path_mut(path_id) +5001 .expect("payload is processed only after the path becomes known"); +5002 if network_path.remote == path.network_path.remote { +5003 // PATH_CHALLENGE on active path, possible off-path packet +5004 // forwarding attack. Send a non-probing packet to recover the +5005 // active path. See +5006 // https://www.rfc-editor.org/rfc/rfc9000.html#section-9.3.3-3. In +5007 // rare cases NAT probes might also appear on-path and would also +5008 // get a non-probing packet as response. There is little harm in +5009 // this. +5010 match self.peer_supports_ack_frequency() { +5011 true => self.immediate_ack(path_id), +5012 false => { +5013 self.ping_path(path_id).ok(); +5014 } +5015 } +5016 } +5017 } +5018 Frame::PathResponse(response) => { +5019 // First try to see if this is a NAT probe response. +5020 if self +5021 .n0_nat_traversal +5022 .handle_path_response(network_path, response.0) +5023 { +5024 self.open_nat_traversed_paths(now); +5025 } else { +5026 // Try to see if this is a response to an on-path PATH_CHALLENGE. +5027 self.handle_path_response_on_path(now, response, path_id); +5028 } +5029 } +5030 Frame::MaxData(frame::MaxData(bytes)) => { +5031 self.streams.received_max_data(bytes); +5032 } +5033 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { +5034 self.streams.received_max_stream_data(id, offset)?; +5035 } +5036 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { +5037 self.streams.received_max_streams(dir, count)?; +5038 } +5039 Frame::ResetStream(frame) => { +5040 if self.streams.received_reset(frame)?.should_transmit() { +5041 self.spaces[SpaceId::Data].pending.max_data = true; +5042 } +5043 } +5044 Frame::DataBlocked(DataBlocked(offset)) => { +5045 debug!(offset, "peer claims to be blocked at connection level"); +5046 } +5047 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { +5048 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { +5049 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); +5050 return Err(TransportError::STREAM_STATE_ERROR( +5051 "STREAM_DATA_BLOCKED on send-only stream", +5052 )); +5053 } +5054 debug!( +5055 stream = %id, +5056 offset, "peer claims to be blocked at stream level" +5057 ); 5058 } -5059 Frame::MaxData(frame::MaxData(bytes)) => { -5060 self.streams.received_max_data(bytes); -5061 } -5062 Frame::MaxStreamData(frame::MaxStreamData { id, offset }) => { -5063 self.streams.received_max_stream_data(id, offset)?; -5064 } -5065 Frame::MaxStreams(frame::MaxStreams { dir, count }) => { -5066 self.streams.received_max_streams(dir, count)?; -5067 } -5068 Frame::ResetStream(frame) => { -5069 if self.streams.received_reset(frame)?.should_transmit() { -5070 self.spaces[SpaceId::Data].pending.max_data = true; -5071 } -5072 } -5073 Frame::DataBlocked(DataBlocked(offset)) => { -5074 debug!(offset, "peer claims to be blocked at connection level"); -5075 } -5076 Frame::StreamDataBlocked(StreamDataBlocked { id, offset }) => { -5077 if id.initiator() == self.side.side() && id.dir() == Dir::Uni { -5078 debug!("got STREAM_DATA_BLOCKED on send-only {}", id); +5059 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { +5060 if limit > MAX_STREAM_COUNT { +5061 return Err(TransportError::FRAME_ENCODING_ERROR( +5062 "unrepresentable stream limit", +5063 )); +5064 } +5065 debug!( +5066 "peer claims to be blocked opening more than {} {} streams", +5067 limit, dir +5068 ); +5069 } +5070 Frame::StopSending(frame::StopSending { id, error_code }) => { +5071 if id.initiator() != self.side.side() { +5072 if id.dir() == Dir::Uni { +5073 debug!("got STOP_SENDING on recv-only {}", id); +5074 return Err(TransportError::STREAM_STATE_ERROR( +5075 "STOP_SENDING on recv-only stream", +5076 )); +5077 } +5078 } else if self.streams.is_local_unopened(id) { 5079 return Err(TransportError::STREAM_STATE_ERROR( -5080 "STREAM_DATA_BLOCKED on send-only stream", +5080 "STOP_SENDING on unopened stream", 5081 )); 5082 } -5083 debug!( -5084 stream = %id, -5085 offset, "peer claims to be blocked at stream level" -5086 ); -5087 } -5088 Frame::StreamsBlocked(StreamsBlocked { dir, limit }) => { -5089 if limit > MAX_STREAM_COUNT { -5090 return Err(TransportError::FRAME_ENCODING_ERROR( -5091 "unrepresentable stream limit", -5092 )); -5093 } -5094 debug!( -5095 "peer claims to be blocked opening more than {} {} streams", -5096 limit, dir -5097 ); -5098 } -5099 Frame::StopSending(frame::StopSending { id, error_code }) => { -5100 if id.initiator() != self.side.side() { -5101 if id.dir() == Dir::Uni { -5102 debug!("got STOP_SENDING on recv-only {}", id); -5103 return Err(TransportError::STREAM_STATE_ERROR( -5104 "STOP_SENDING on recv-only stream", -5105 )); -5106 } -5107 } else if self.streams.is_local_unopened(id) { -5108 return Err(TransportError::STREAM_STATE_ERROR( -5109 "STOP_SENDING on unopened stream", -5110 )); +5083 self.streams.received_stop_sending(id, error_code); +5084 } +5085 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { +5086 if let Some(ref path_id) = path_id { +5087 span.record("path", tracing::field::display(&path_id)); +5088 } +5089 let path_id = path_id.unwrap_or_default(); +5090 match self.local_cid_state.get_mut(&path_id) { +5091 None => debug!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), +5092 Some(cid_state) => { +5093 let allow_more_cids = cid_state +5094 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; +5095 +5096 // If the path has closed, we do not issue more CIDs for this path +5097 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 +5098 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. +5099 let has_path = !self.abandoned_paths.contains(&path_id); +5100 let allow_more_cids = allow_more_cids && has_path; +5101 +5102 debug_assert!(!self.state.is_drained()); // required for adding endpoint events, process_payload is never called for drained connections +5103 self.endpoint_events +5104 .push_back(EndpointEventInner::RetireConnectionId( +5105 now, +5106 path_id, +5107 sequence, +5108 allow_more_cids, +5109 )); +5110 } 5111 } -5112 self.streams.received_stop_sending(id, error_code); -5113 } -5114 Frame::RetireConnectionId(frame::RetireConnectionId { path_id, sequence }) => { -5115 if let Some(ref path_id) = path_id { -5116 span.record("path", tracing::field::display(&path_id)); -5117 } -5118 let path_id = path_id.unwrap_or_default(); -5119 match self.local_cid_state.get_mut(&path_id) { -5120 None => debug!(?path_id, "RETIRE_CONNECTION_ID for unknown path"), -5121 Some(cid_state) => { -5122 let allow_more_cids = cid_state -5123 .on_cid_retirement(sequence, self.peer_params.issue_cids_limit())?; -5124 -5125 // If the path has closed, we do not issue more CIDs for this path -5126 // For details see https://www.ietf.org/archive/id/draft-ietf-quic-multipath-17.html#section-3.2.2 -5127 // > an endpoint SHOULD provide new connection IDs for that path, if still open, using PATH_NEW_CONNECTION_ID frames. -5128 let has_path = !self.abandoned_paths.contains(&path_id); -5129 let allow_more_cids = allow_more_cids && has_path; -5130 -5131 debug_assert!(!self.state.is_drained()); // required for adding endpoint events, process_payload is never called for drained connections -5132 self.endpoint_events -5133 .push_back(EndpointEventInner::RetireConnectionId( -5134 now, -5135 path_id, -5136 sequence, -5137 allow_more_cids, -5138 )); -5139 } -5140 } -5141 } -5142 Frame::NewConnectionId(frame) => { -5143 let path_id = if let Some(path_id) = frame.path_id { -5144 if !self.is_multipath_negotiated() { -5145 return Err(TransportError::PROTOCOL_VIOLATION( -5146 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", -5147 )); -5148 } -5149 if path_id > self.local_max_path_id { -5150 return Err(TransportError::PROTOCOL_VIOLATION( -5151 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", -5152 )); -5153 } -5154 path_id -5155 } else { -5156 PathId::ZERO -5157 }; -5158 -5159 if let Some(ref path_id) = frame.path_id { -5160 span.record("path", tracing::field::display(&path_id)); -5161 } -5162 -5163 if self.abandoned_paths.contains(&path_id) { -5164 trace!("ignoring issued CID for abandoned path"); -5165 continue; -5166 } -5167 let remote_cids = self -5168 .remote_cids -5169 .entry(path_id) -5170 .or_insert_with(|| CidQueue::new(frame.id)); -5171 if remote_cids.active().is_empty() { -5172 return Err(TransportError::PROTOCOL_VIOLATION( -5173 "NEW_CONNECTION_ID when CIDs aren't in use", -5174 )); -5175 } -5176 if frame.retire_prior_to > frame.sequence { -5177 return Err(TransportError::PROTOCOL_VIOLATION( -5178 "NEW_CONNECTION_ID retiring unissued CIDs", -5179 )); -5180 } -5181 -5182 use crate::cid_queue::InsertError; -5183 match remote_cids.insert(frame) { -5184 Ok(None) => { -5185 self.open_nat_traversed_paths(now); -5186 } -5187 Ok(Some((retired, reset_token))) => { -5188 let pending_retired = -5189 &mut self.spaces[SpaceId::Data].pending.retire_cids; -5190 /// Ensure `pending_retired` cannot grow without bound. Limit is -5191 /// somewhat arbitrary but very permissive. -5192 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; -5193 // We don't bother counting in-flight frames because those are bounded -5194 // by congestion control. -5195 if (pending_retired.len() as u64) -5196 .saturating_add(retired.end.saturating_sub(retired.start)) -5197 > MAX_PENDING_RETIRED_CIDS -5198 { -5199 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( -5200 "queued too many retired CIDs", -5201 )); -5202 } -5203 pending_retired.extend(retired.map(|seq| (path_id, seq))); -5204 self.set_reset_token(path_id, network_path.remote, reset_token); -5205 self.open_nat_traversed_paths(now); -5206 } -5207 Err(InsertError::ExceedsLimit) => { -5208 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); -5209 } -5210 Err(InsertError::Retired) => { -5211 trace!("discarding already-retired"); -5212 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a -5213 // range of connection IDs larger than the active connection ID limit -5214 // was retired all at once via retire_prior_to. -5215 self.spaces[SpaceId::Data] -5216 .pending -5217 .retire_cids -5218 .push((path_id, frame.sequence)); -5219 continue; -5220 } -5221 }; -5222 -5223 if self.side.is_server() -5224 && path_id == PathId::ZERO -5225 && self -5226 .remote_cids -5227 .get(&PathId::ZERO) -5228 .map(|cids| cids.active_seq() == 0) -5229 .unwrap_or_default() -5230 { -5231 // We're a server still using the initial remote CID for the client, so -5232 // let's switch immediately to enable clientside stateless resets. -5233 self.update_remote_cid(PathId::ZERO); -5234 } -5235 } -5236 Frame::NewToken(NewToken { token }) => { -5237 let ConnectionSide::Client { -5238 token_store, -5239 server_name, -5240 .. -5241 } = &self.side -5242 else { -5243 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); -5244 }; -5245 if token.is_empty() { -5246 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); -5247 } -5248 trace!("got new token"); -5249 token_store.insert(server_name, token); -5250 } -5251 Frame::Datagram(datagram) => { -5252 if self -5253 .datagrams -5254 .received(datagram, &self.config.datagram_receive_buffer_size)? -5255 { -5256 self.events.push_back(Event::DatagramReceived); +5112 } +5113 Frame::NewConnectionId(frame) => { +5114 let path_id = if let Some(path_id) = frame.path_id { +5115 if !self.is_multipath_negotiated() { +5116 return Err(TransportError::PROTOCOL_VIOLATION( +5117 "received PATH_NEW_CONNECTION_ID frame when multipath was not negotiated", +5118 )); +5119 } +5120 if path_id > self.local_max_path_id { +5121 return Err(TransportError::PROTOCOL_VIOLATION( +5122 "PATH_NEW_CONNECTION_ID contains path_id exceeding current max", +5123 )); +5124 } +5125 path_id +5126 } else { +5127 PathId::ZERO +5128 }; +5129 +5130 if let Some(ref path_id) = frame.path_id { +5131 span.record("path", tracing::field::display(&path_id)); +5132 } +5133 +5134 if self.abandoned_paths.contains(&path_id) { +5135 trace!("ignoring issued CID for abandoned path"); +5136 continue; +5137 } +5138 let remote_cids = self +5139 .remote_cids +5140 .entry(path_id) +5141 .or_insert_with(|| CidQueue::new(frame.id)); +5142 if remote_cids.active().is_empty() { +5143 return Err(TransportError::PROTOCOL_VIOLATION( +5144 "NEW_CONNECTION_ID when CIDs aren't in use", +5145 )); +5146 } +5147 if frame.retire_prior_to > frame.sequence { +5148 return Err(TransportError::PROTOCOL_VIOLATION( +5149 "NEW_CONNECTION_ID retiring unissued CIDs", +5150 )); +5151 } +5152 +5153 use crate::cid_queue::InsertError; +5154 match remote_cids.insert(frame) { +5155 Ok(None) => { +5156 self.open_nat_traversed_paths(now); +5157 } +5158 Ok(Some((retired, reset_token))) => { +5159 let pending_retired = +5160 &mut self.spaces[SpaceId::Data].pending.retire_cids; +5161 /// Ensure `pending_retired` cannot grow without bound. Limit is +5162 /// somewhat arbitrary but very permissive. +5163 const MAX_PENDING_RETIRED_CIDS: u64 = CidQueue::LEN as u64 * 10; +5164 // We don't bother counting in-flight frames because those are bounded +5165 // by congestion control. +5166 if (pending_retired.len() as u64) +5167 .saturating_add(retired.end.saturating_sub(retired.start)) +5168 > MAX_PENDING_RETIRED_CIDS +5169 { +5170 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR( +5171 "queued too many retired CIDs", +5172 )); +5173 } +5174 pending_retired.extend(retired.map(|seq| (path_id, seq))); +5175 self.set_reset_token(path_id, network_path.remote, reset_token); +5176 self.open_nat_traversed_paths(now); +5177 } +5178 Err(InsertError::ExceedsLimit) => { +5179 return Err(TransportError::CONNECTION_ID_LIMIT_ERROR("")); +5180 } +5181 Err(InsertError::Retired) => { +5182 trace!("discarding already-retired"); +5183 // RETIRE_CONNECTION_ID might not have been previously sent if e.g. a +5184 // range of connection IDs larger than the active connection ID limit +5185 // was retired all at once via retire_prior_to. +5186 self.spaces[SpaceId::Data] +5187 .pending +5188 .retire_cids +5189 .push((path_id, frame.sequence)); +5190 continue; +5191 } +5192 }; +5193 +5194 if self.side.is_server() +5195 && path_id == PathId::ZERO +5196 && self +5197 .remote_cids +5198 .get(&PathId::ZERO) +5199 .map(|cids| cids.active_seq() == 0) +5200 .unwrap_or_default() +5201 { +5202 // We're a server still using the initial remote CID for the client, so +5203 // let's switch immediately to enable clientside stateless resets. +5204 self.update_remote_cid(PathId::ZERO); +5205 } +5206 } +5207 Frame::NewToken(NewToken { token }) => { +5208 let ConnectionSide::Client { +5209 token_store, +5210 server_name, +5211 .. +5212 } = &self.side +5213 else { +5214 return Err(TransportError::PROTOCOL_VIOLATION("client sent NEW_TOKEN")); +5215 }; +5216 if token.is_empty() { +5217 return Err(TransportError::FRAME_ENCODING_ERROR("empty token")); +5218 } +5219 trace!("got new token"); +5220 token_store.insert(server_name, token); +5221 } +5222 Frame::Datagram(datagram) => { +5223 if self +5224 .datagrams +5225 .received(datagram, &self.config.datagram_receive_buffer_size)? +5226 { +5227 self.events.push_back(Event::DatagramReceived); +5228 } +5229 } +5230 Frame::AckFrequency(ack_frequency) => { +5231 // This frame can only be sent in the Data space +5232 +5233 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { +5234 // The AckFrequency frame is stale (we have already received a more +5235 // recent one) +5236 continue; +5237 } +5238 +5239 // Update the params for all of our paths +5240 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { +5241 space.pending_acks.set_ack_frequency_params(&ack_frequency); +5242 +5243 // Our `max_ack_delay` has been updated, so we may need to adjust +5244 // its associated timeout. +5245 // Packets received on abandoned paths are always acknowledged immediately. +5246 if !self.abandoned_paths.contains(path_id) +5247 && let Some(timeout) = space +5248 .pending_acks +5249 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) +5250 { +5251 self.timers.set( +5252 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), +5253 timeout, +5254 self.qlog.with_time(now), +5255 ); +5256 } 5257 } 5258 } -5259 Frame::AckFrequency(ack_frequency) => { +5259 Frame::ImmediateAck => { 5260 // This frame can only be sent in the Data space -5261 -5262 if !self.ack_frequency.ack_frequency_received(&ack_frequency)? { -5263 // The AckFrequency frame is stale (we have already received a more -5264 // recent one) -5265 continue; -5266 } -5267 -5268 // Update the params for all of our paths -5269 for (path_id, space) in self.spaces[SpaceId::Data].number_spaces.iter_mut() { -5270 space.pending_acks.set_ack_frequency_params(&ack_frequency); -5271 -5272 // Our `max_ack_delay` has been updated, so we may need to adjust -5273 // its associated timeout. -5274 // Packets received on abandoned paths are always acknowledged immediately. -5275 if !self.abandoned_paths.contains(path_id) -5276 && let Some(timeout) = space -5277 .pending_acks -5278 .max_ack_delay_timeout(self.ack_frequency.max_ack_delay) -5279 { -5280 self.timers.set( -5281 Timer::PerPath(*path_id, PathTimer::MaxAckDelay), -5282 timeout, -5283 self.qlog.with_time(now), -5284 ); -5285 } -5286 } -5287 } -5288 Frame::ImmediateAck => { -5289 // This frame can only be sent in the Data space -5290 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { -5291 pns.pending_acks.set_immediate_ack_required(); -5292 } -5293 } -5294 Frame::HandshakeDone => { -5295 if self.side.is_server() { -5296 return Err(TransportError::PROTOCOL_VIOLATION( -5297 "client sent HANDSHAKE_DONE", -5298 )); -5299 } -5300 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { -5301 self.discard_space(now, SpaceKind::Handshake); -5302 self.events.push_back(Event::HandshakeConfirmed); -5303 trace!("handshake confirmed"); -5304 } -5305 } -5306 Frame::ObservedAddr(observed) => { -5307 // check if params allows the peer to send report and this node to receive it -5308 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); -5309 if !self -5310 .peer_params -5311 .address_discovery_role -5312 .should_report(&self.config.address_discovery_role) -5313 { -5314 return Err(TransportError::PROTOCOL_VIOLATION( -5315 "received OBSERVED_ADDRESS frame when not negotiated", -5316 )); -5317 } -5318 // must only be sent in data space -5319 if packet.header.space() != SpaceKind::Data { -5320 return Err(TransportError::PROTOCOL_VIOLATION( -5321 "OBSERVED_ADDRESS frame outside data space", -5322 )); -5323 } -5324 -5325 let path = self.path_data_mut(path_id); -5326 if path.network_path.is_probably_same_path(&network_path) { -5327 if let Some(updated) = path.update_observed_addr_report(observed) -5328 && path.open_status == paths::OpenStatus::Informed -5329 { -5330 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5331 id: path_id, -5332 addr: updated, -5333 })); -5334 // otherwise the event is reported when the path is deemed open -5335 } -5336 } else { -5337 // include in migration -5338 migration_observed_addr = Some(observed) -5339 } -5340 } -5341 Frame::PathAbandon(frame::PathAbandon { -5342 path_id, -5343 error_code, -5344 }) => { -5345 span.record("path", tracing::field::display(&path_id)); -5346 match self.close_path_inner( -5347 now, -5348 path_id, -5349 PathAbandonReason::RemoteAbandoned { -5350 error_code: error_code.into(), -5351 }, -5352 ) { -5353 Ok(()) => { -5354 trace!("peer abandoned path"); -5355 } -5356 Err(ClosePathError::ClosedPath) => { -5357 trace!("peer abandoned already closed path"); -5358 } -5359 Err(ClosePathError::MultipathNotNegotiated) => { -5360 return Err(TransportError::PROTOCOL_VIOLATION( -5361 "received PATH_ABANDON frame when multipath was not negotiated", -5362 )); -5363 } -5364 Err(ClosePathError::LastOpenPath) => { -5365 // Not reachable: close_path_inner allows remote abandons -5366 // for the last path. But handle gracefully just in case. -5367 error!( -5368 "peer abandoned last path but close_path_inner returned LastOpenPath" -5369 ); -5370 } -5371 }; -5372 -5373 // Start draining the path if it still exists and hasn't started draining yet. -5374 if let Some(path) = self.paths.get_mut(&path_id) -5375 && !mem::replace(&mut path.data.draining, true) -5376 { -5377 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); -5378 let pto = path.data.rtt.pto_base() + ack_delay; -5379 self.timers.set( -5380 Timer::PerPath(path_id, PathTimer::PathDrained), -5381 now + 3 * pto, -5382 self.qlog.with_time(now), -5383 ); -5384 -5385 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); -5386 } -5387 } -5388 Frame::PathStatusAvailable(info) => { -5389 span.record("path", tracing::field::display(&info.path_id)); -5390 if self.is_multipath_negotiated() { -5391 self.on_path_status( -5392 info.path_id, -5393 PathStatus::Available, -5394 info.status_seq_no, -5395 ); -5396 } else { -5397 return Err(TransportError::PROTOCOL_VIOLATION( -5398 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", -5399 )); -5400 } -5401 } -5402 Frame::PathStatusBackup(info) => { -5403 span.record("path", tracing::field::display(&info.path_id)); -5404 if self.is_multipath_negotiated() { -5405 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); -5406 } else { -5407 return Err(TransportError::PROTOCOL_VIOLATION( -5408 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", -5409 )); -5410 } -5411 } -5412 Frame::MaxPathId(frame::MaxPathId(path_id)) => { -5413 span.record("path", tracing::field::display(&path_id)); -5414 if !self.is_multipath_negotiated() { -5415 return Err(TransportError::PROTOCOL_VIOLATION( -5416 "received MAX_PATH_ID frame when multipath was not negotiated", -5417 )); -5418 } -5419 // frames that do not increase the path id are ignored -5420 if path_id > self.remote_max_path_id { -5421 self.remote_max_path_id = path_id; -5422 self.issue_first_path_cids(now); -5423 self.open_nat_traversed_paths(now); -5424 } -5425 } -5426 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { -5427 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5428 // be treated as a connection error of type PROTOCOL_VIOLATION. -5429 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5430 if self.is_multipath_negotiated() { -5431 if max_path_id > self.local_max_path_id { -5432 return Err(TransportError::PROTOCOL_VIOLATION( -5433 "PATHS_BLOCKED maximum path identifier was larger than local maximum", -5434 )); -5435 } -5436 debug!("received PATHS_BLOCKED({:?})", max_path_id); -5437 // TODO(@divma): ensure max concurrent paths -5438 } else { -5439 return Err(TransportError::PROTOCOL_VIOLATION( -5440 "received PATHS_BLOCKED frame when not multipath was not negotiated", -5441 )); -5442 } -5443 } -5444 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { -5445 // Nothing to do. This is recorded in the frame stats, but otherwise we -5446 // always issue all CIDs we're allowed to issue, so either this is an -5447 // impatient peer or a bug on our side. -5448 -5449 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST -5450 // be treated as a connection error of type PROTOCOL_VIOLATION. -5451 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> -5452 if self.is_multipath_negotiated() { -5453 if path_id > self.local_max_path_id { -5454 return Err(TransportError::PROTOCOL_VIOLATION( -5455 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", -5456 )); -5457 } -5458 if next_seq.0 -5459 > self -5460 .local_cid_state -5461 .get(&path_id) -5462 .map(|cid_state| cid_state.active_seq().1 + 1) -5463 .unwrap_or_default() -5464 { -5465 return Err(TransportError::PROTOCOL_VIOLATION( -5466 "PATH_CIDS_BLOCKED next sequence number larger than in local state", -5467 )); -5468 } -5469 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); -5470 } else { -5471 return Err(TransportError::PROTOCOL_VIOLATION( -5472 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", -5473 )); -5474 } -5475 } -5476 Frame::AddAddress(addr) => { -5477 let client_state = match self.n0_nat_traversal.client_side_mut() { -5478 Ok(state) => state, -5479 Err(err) => { -5480 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5481 "Nat traversal(ADD_ADDRESS): {err}" -5482 ))); -5483 } -5484 }; -5485 -5486 if !client_state.check_remote_address(&addr) { -5487 // if the address is not valid we flag it, but update anyway -5488 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); -5489 } -5490 -5491 match client_state.add_remote_address(addr) { -5492 Ok(maybe_added) => { -5493 if let Some(added) = maybe_added { -5494 self.events.push_back(Event::NatTraversal( -5495 n0_nat_traversal::Event::AddressAdded(added), -5496 )); -5497 } +5261 for pns in self.spaces[SpaceId::Data].iter_paths_mut() { +5262 pns.pending_acks.set_immediate_ack_required(); +5263 } +5264 } +5265 Frame::HandshakeDone => { +5266 if self.side.is_server() { +5267 return Err(TransportError::PROTOCOL_VIOLATION( +5268 "client sent HANDSHAKE_DONE", +5269 )); +5270 } +5271 if self.crypto_state.has_keys(EncryptionLevel::Handshake) { +5272 self.discard_space(now, SpaceKind::Handshake); +5273 self.events.push_back(Event::HandshakeConfirmed); +5274 trace!("handshake confirmed"); +5275 } +5276 } +5277 Frame::ObservedAddr(observed) => { +5278 // check if params allows the peer to send report and this node to receive it +5279 trace!(seq_no = %observed.seq_no, ip = %observed.ip, port = observed.port); +5280 if !self +5281 .peer_params +5282 .address_discovery_role +5283 .should_report(&self.config.address_discovery_role) +5284 { +5285 return Err(TransportError::PROTOCOL_VIOLATION( +5286 "received OBSERVED_ADDRESS frame when not negotiated", +5287 )); +5288 } +5289 // must only be sent in data space +5290 if packet.header.space() != SpaceKind::Data { +5291 return Err(TransportError::PROTOCOL_VIOLATION( +5292 "OBSERVED_ADDRESS frame outside data space", +5293 )); +5294 } +5295 +5296 let space_open_status = +5297 self.spaces[SpaceKind::Data].for_path(path_id).open_status; +5298 let path = self.path_data_mut(path_id); +5299 if path.network_path.remote == network_path.remote { +5300 if let Some(updated) = path.update_observed_addr_report(observed) +5301 && space_open_status == OpenStatus::Informed +5302 { +5303 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5304 id: path_id, +5305 addr: updated, +5306 })); +5307 // otherwise the event is reported when the path is deemed open +5308 } +5309 } else { +5310 // include in migration +5311 migration_observed_addr = Some(observed) +5312 } +5313 } +5314 Frame::PathAbandon(frame::PathAbandon { +5315 path_id, +5316 error_code, +5317 }) => { +5318 span.record("path", tracing::field::display(&path_id)); +5319 match self.close_path_inner( +5320 now, +5321 path_id, +5322 PathAbandonReason::RemoteAbandoned { +5323 error_code: error_code.into(), +5324 }, +5325 ) { +5326 Ok(()) => { +5327 trace!("peer abandoned path"); +5328 } +5329 Err(ClosePathError::ClosedPath) => { +5330 trace!("peer abandoned already closed path"); +5331 } +5332 Err(ClosePathError::MultipathNotNegotiated) => { +5333 return Err(TransportError::PROTOCOL_VIOLATION( +5334 "received PATH_ABANDON frame when multipath was not negotiated", +5335 )); +5336 } +5337 Err(ClosePathError::LastOpenPath) => { +5338 // Not reachable: close_path_inner allows remote abandons +5339 // for the last path. But handle gracefully just in case. +5340 error!( +5341 "peer abandoned last path but close_path_inner returned LastOpenPath" +5342 ); +5343 } +5344 }; +5345 +5346 // Start draining the path if it still exists and hasn't started draining yet. +5347 if let Some(path) = self.paths.get_mut(&path_id) +5348 && !mem::replace(&mut path.data.draining, true) +5349 { +5350 let ack_delay = self.ack_frequency.max_ack_delay_for_pto(); +5351 let pto = path.data.rtt.pto_base() + ack_delay; +5352 self.timers.set( +5353 Timer::PerPath(path_id, PathTimer::PathDrained), +5354 now + 3 * pto, +5355 self.qlog.with_time(now), +5356 ); +5357 +5358 self.set_max_path_id(now, self.local_max_path_id.saturating_add(1u8)); +5359 } +5360 } +5361 Frame::PathStatusAvailable(info) => { +5362 span.record("path", tracing::field::display(&info.path_id)); +5363 if self.is_multipath_negotiated() { +5364 self.on_path_status( +5365 info.path_id, +5366 PathStatus::Available, +5367 info.status_seq_no, +5368 ); +5369 } else { +5370 return Err(TransportError::PROTOCOL_VIOLATION( +5371 "received PATH_STATUS_AVAILABLE frame when multipath was not negotiated", +5372 )); +5373 } +5374 } +5375 Frame::PathStatusBackup(info) => { +5376 span.record("path", tracing::field::display(&info.path_id)); +5377 if self.is_multipath_negotiated() { +5378 self.on_path_status(info.path_id, PathStatus::Backup, info.status_seq_no); +5379 } else { +5380 return Err(TransportError::PROTOCOL_VIOLATION( +5381 "received PATH_STATUS_BACKUP frame when multipath was not negotiated", +5382 )); +5383 } +5384 } +5385 Frame::MaxPathId(frame::MaxPathId(path_id)) => { +5386 span.record("path", tracing::field::display(&path_id)); +5387 if !self.is_multipath_negotiated() { +5388 return Err(TransportError::PROTOCOL_VIOLATION( +5389 "received MAX_PATH_ID frame when multipath was not negotiated", +5390 )); +5391 } +5392 // frames that do not increase the path id are ignored +5393 if path_id > self.remote_max_path_id { +5394 self.remote_max_path_id = path_id; +5395 self.issue_first_path_cids(now); +5396 self.open_nat_traversed_paths(now); +5397 } +5398 } +5399 Frame::PathsBlocked(frame::PathsBlocked(max_path_id)) => { +5400 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5401 // be treated as a connection error of type PROTOCOL_VIOLATION. +5402 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5403 if self.is_multipath_negotiated() { +5404 if max_path_id > self.local_max_path_id { +5405 return Err(TransportError::PROTOCOL_VIOLATION( +5406 "PATHS_BLOCKED maximum path identifier was larger than local maximum", +5407 )); +5408 } +5409 } else { +5410 return Err(TransportError::PROTOCOL_VIOLATION( +5411 "received PATHS_BLOCKED frame when not multipath was not negotiated", +5412 )); +5413 } +5414 } +5415 Frame::PathCidsBlocked(frame::PathCidsBlocked { path_id, next_seq }) => { +5416 // Nothing to do. This is recorded in the frame stats, but otherwise we +5417 // always issue all CIDs we're allowed to issue, so either this is an +5418 // impatient peer or a bug on our side. +5419 +5420 // Receipt of a value of Maximum Path Identifier or Path Identifier that is higher than the local maximum value MUST +5421 // be treated as a connection error of type PROTOCOL_VIOLATION. +5422 // Ref <https://www.ietf.org/archive/id/draft-ietf-quic-multipath-14.html#name-paths_blocked-and-path_cids> +5423 if self.is_multipath_negotiated() { +5424 if path_id > self.local_max_path_id { +5425 return Err(TransportError::PROTOCOL_VIOLATION( +5426 "PATH_CIDS_BLOCKED path identifier was larger than local maximum", +5427 )); +5428 } +5429 if self +5430 .local_cid_state +5431 .get(&path_id) +5432 // The PATH_CIDS_BLOCKED frame may arrive after we've discarded the path state. +5433 // In that case, we can't check for the protocol violation. +5434 .is_some_and(|cid_state| next_seq.0 > cid_state.active_seq().1 + 1) +5435 { +5436 return Err(TransportError::PROTOCOL_VIOLATION( +5437 "PATH_CIDS_BLOCKED next sequence number larger than in local state", +5438 )); +5439 } +5440 debug!(%path_id, %next_seq, "received PATH_CIDS_BLOCKED"); +5441 } else { +5442 return Err(TransportError::PROTOCOL_VIOLATION( +5443 "received PATH_CIDS_BLOCKED frame when not multipath was not negotiated", +5444 )); +5445 } +5446 } +5447 Frame::AddAddress(addr) => { +5448 let client_state = match self.n0_nat_traversal.client_side_mut() { +5449 Ok(state) => state, +5450 Err(err) => { +5451 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5452 "Nat traversal(ADD_ADDRESS): {err}" +5453 ))); +5454 } +5455 }; +5456 +5457 if !client_state.check_remote_address(&addr) { +5458 // if the address is not valid we flag it, but update anyway +5459 warn!(?addr, "server sent illegal ADD_ADDRESS frame"); +5460 } +5461 +5462 match client_state.add_remote_address(addr) { +5463 Ok(maybe_added) => { +5464 if let Some(added) = maybe_added { +5465 self.events.push_back(Event::NatTraversal( +5466 n0_nat_traversal::Event::AddressAdded(added), +5467 )); +5468 } +5469 } +5470 Err(e) => { +5471 warn!(%e, "failed to add remote address") +5472 } +5473 } +5474 } +5475 Frame::RemoveAddress(addr) => { +5476 let client_state = match self.n0_nat_traversal.client_side_mut() { +5477 Ok(state) => state, +5478 Err(err) => { +5479 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5480 "Nat traversal(REMOVE_ADDRESS): {err}" +5481 ))); +5482 } +5483 }; +5484 if let Some(removed_addr) = client_state.remove_remote_address(addr) { +5485 self.events.push_back(Event::NatTraversal( +5486 n0_nat_traversal::Event::AddressRemoved(removed_addr), +5487 )); +5488 } +5489 } +5490 Frame::ReachOut(reach_out) => { +5491 let ipv6 = self.is_ipv6(); +5492 let server_state = match self.n0_nat_traversal.server_side_mut() { +5493 Ok(state) => state, +5494 Err(err) => { +5495 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5496 "Nat traversal(REACH_OUT): {err}" +5497 ))); 5498 } -5499 Err(e) => { -5500 warn!(%e, "failed to add remote address") -5501 } -5502 } -5503 } -5504 Frame::RemoveAddress(addr) => { -5505 let client_state = match self.n0_nat_traversal.client_side_mut() { -5506 Ok(state) => state, -5507 Err(err) => { -5508 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5509 "Nat traversal(REMOVE_ADDRESS): {err}" -5510 ))); -5511 } -5512 }; -5513 if let Some(removed_addr) = client_state.remove_remote_address(addr) { -5514 self.events.push_back(Event::NatTraversal( -5515 n0_nat_traversal::Event::AddressRemoved(removed_addr), -5516 )); -5517 } -5518 } -5519 Frame::ReachOut(reach_out) => { -5520 let ipv6 = self.is_ipv6(); -5521 let server_state = match self.n0_nat_traversal.server_side_mut() { -5522 Ok(state) => state, -5523 Err(err) => { -5524 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5525 "Nat traversal(REACH_OUT): {err}" -5526 ))); -5527 } -5528 }; -5529 -5530 let round_before = server_state.current_round(); -5531 -5532 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { -5533 return Err(TransportError::PROTOCOL_VIOLATION(format!( -5534 "Nat traversal(REACH_OUT): {err}" -5535 ))); -5536 } -5537 -5538 if server_state.current_round() > round_before { -5539 // A new round was started, reset the NAT probe retry timer. -5540 if let Some(delay) = -5541 self.n0_nat_traversal.retry_delay(self.config.initial_rtt) -5542 { -5543 self.timers.set( -5544 Timer::Conn(ConnTimer::NatTraversalProbeRetry), -5545 now + delay, -5546 self.qlog.with_time(now), -5547 ); -5548 } -5549 } -5550 } -5551 } -5552 } -5553 -5554 let space = self.spaces[SpaceId::Data].for_path(path_id); -5555 if space -5556 .pending_acks -5557 .packet_received(now, number, ack_eliciting, &space.dedup) -5558 { -5559 if self.abandoned_paths.contains(&path_id) { -5560 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from -5561 // abandoned paths. -5562 space.pending_acks.set_immediate_ack_required(); -5563 } else { -5564 self.timers.set( -5565 Timer::PerPath(path_id, PathTimer::MaxAckDelay), -5566 now + self.ack_frequency.max_ack_delay, -5567 self.qlog.with_time(now), -5568 ); -5569 } -5570 } -5571 -5572 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets -5573 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they -5574 // are only freed, and hence only issue credit, once the application has been notified -5575 // during a read on the stream. -5576 let pending = &mut self.spaces[SpaceId::Data].pending; -5577 self.streams.queue_max_stream_id(pending); -5578 -5579 if let Some(reason) = close { -5580 self.state.move_to_draining(Some(reason.into())); -5581 self.connection_close_pending = true; -5582 } -5583 -5584 // For Multipath any packet triggers migration. For RFC9000 or QNT (+ Multipath) -5585 // only non-probing packets trigger migration. -5586 let migrate_on_any_packet = -5587 self.is_multipath_negotiated() && !self.n0_nat_traversal.is_negotiated(); -5588 -5589 // Only migrate if this is the largest packet number seen. -5590 let is_largest_received_pn = Some(number) -5591 == self.spaces[SpaceId::Data] -5592 .for_path(path_id) -5593 .largest_received_packet_number; -5594 -5595 // If we receive a non-probing packet on a new local IP that means we had a NAT -5596 // rebinding-like migration. We update our local address but do not otherwise -5597 // validate the new path, we only need to validate the path if the peer migrates per -5598 // RFC9000 §9: https://www.rfc-editor.org/rfc/rfc9000.html#section-9-4 -5599 if (migrate_on_any_packet || !is_probing_packet) -5600 && is_largest_received_pn -5601 && self.local_ip_may_migrate() -5602 && let Some(new_local_ip) = network_path.local_ip -5603 { -5604 let path_data = self.path_data_mut(path_id); -5605 if path_data -5606 .network_path -5607 .local_ip -5608 .is_some_and(|ip| ip != new_local_ip) -5609 { -5610 debug!( -5611 %path_id, -5612 new_4tuple = %network_path, -5613 prev_4tuple = %path_data.network_path, -5614 "local address passive migration" -5615 ); -5616 } -5617 path_data.network_path.local_ip = Some(new_local_ip) -5618 } -5619 -5620 // If the peer migrated to a new address, trigger migration. -5621 if (migrate_on_any_packet || !is_probing_packet) -5622 && is_largest_received_pn -5623 && network_path.remote != self.path_data(path_id).network_path.remote -5624 && self.remote_may_migrate() -5625 { -5626 self.migrate(path_id, now, network_path, migration_observed_addr); -5627 // Break linkability, if possible -5628 self.update_remote_cid(path_id); -5629 self.spin = false; -5630 } -5631 -5632 Ok(()) -5633 } -5634 -5635 /// Opens any paths that have been successfully NAT traversed. -5636 fn open_nat_traversed_paths(&mut self, now: Instant) { -5637 while let Some(network_path) = self -5638 .n0_nat_traversal -5639 .client_side_mut() -5640 .ok() -5641 .and_then(|s| s.pop_pending_path_open()) -5642 { -5643 match self.open_path_ensure(network_path, PathStatus::Backup, now) { -5644 Ok((path_id, already_existed)) => { -5645 debug!( -5646 %path_id, -5647 ?network_path, -5648 new_path = !already_existed, -5649 "Opened NAT traversal path", -5650 ); -5651 } -5652 Err(err) => match err { -5653 PathError::MultipathNotNegotiated -5654 | PathError::ServerSideNotAllowed -5655 | PathError::ValidationFailed -5656 | PathError::InvalidRemoteAddress(_) => { -5657 error!( -5658 ?err, -5659 ?network_path, -5660 "Failed to open path for successful NAT traversal" -5661 ); -5662 } -5663 PathError::MaxPathIdReached | PathError::RemoteCidsExhausted => { -5664 // Temporary error, put back. -5665 self.n0_nat_traversal -5666 .client_side_mut() -5667 .map(|s| s.push_pending_path_open(network_path)) -5668 .ok(); -5669 debug!( -5670 ?err, -5671 ?network_path, -5672 "Blocked opening NAT traversal path, enqueued" -5673 ); -5674 return; -5675 } -5676 }, +5499 }; +5500 +5501 let round_before = server_state.current_round(); +5502 +5503 if let Err(err) = server_state.handle_reach_out(reach_out, ipv6) { +5504 return Err(TransportError::PROTOCOL_VIOLATION(format!( +5505 "Nat traversal(REACH_OUT): {err}" +5506 ))); +5507 } +5508 +5509 if server_state.current_round() > round_before { +5510 // A new round was started, reset the NAT probe retry timer. +5511 if let Some(delay) = +5512 self.n0_nat_traversal.retry_delay(self.config.initial_rtt) +5513 { +5514 self.timers.set( +5515 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +5516 now + delay, +5517 self.qlog.with_time(now), +5518 ); +5519 } +5520 } +5521 } +5522 } +5523 } +5524 +5525 let space = self.spaces[SpaceId::Data].for_path(path_id); +5526 if space +5527 .pending_acks +5528 .packet_received(now, number, ack_eliciting, &space.dedup) +5529 { +5530 if self.abandoned_paths.contains(&path_id) { +5531 // § 3.4.3 QUIC-MULTIPATH: promptly send ACKs for packets received from +5532 // abandoned paths. +5533 space.pending_acks.set_immediate_ack_required(); +5534 } else { +5535 self.timers.set( +5536 Timer::PerPath(path_id, PathTimer::MaxAckDelay), +5537 now + self.ack_frequency.max_ack_delay, +5538 self.qlog.with_time(now), +5539 ); +5540 } +5541 } +5542 +5543 // Issue stream ID credit due to ACKs of outgoing finish/resets and incoming finish/resets +5544 // on stopped streams. Incoming finishes/resets on open streams are not handled here as they +5545 // are only freed, and hence only issue credit, once the application has been notified +5546 // during a read on the stream. +5547 let pending = &mut self.spaces[SpaceId::Data].pending; +5548 self.streams.queue_max_stream_id(pending); +5549 +5550 if let Some(reason) = close { +5551 self.state.move_to_draining(Some(reason.into())); +5552 self.endpoint_events.push_back(EndpointEventInner::Draining); +5553 self.connection_close_pending = true; +5554 } +5555 +5556 // For Multipath any packet triggers migration. For RFC9000 or QNT (+ Multipath) +5557 // only non-probing packets trigger migration. +5558 let migrate_on_any_packet = +5559 self.is_multipath_negotiated() && !self.n0_nat_traversal.is_negotiated(); +5560 +5561 // Only migrate if this is the largest packet number seen. +5562 let is_largest_received_pn = Some(number) +5563 == self.spaces[SpaceId::Data] +5564 .for_path(path_id) +5565 .largest_received_packet_number; +5566 +5567 // If we receive a non-probing packet on a new local IP that means we had a NAT +5568 // rebinding-like migration. We update our local address but do not otherwise +5569 // validate the new path, we only need to validate the path if the peer migrates per +5570 // RFC9000 §9: https://www.rfc-editor.org/rfc/rfc9000.html#section-9-4 +5571 if (migrate_on_any_packet || !is_probing_packet) +5572 && is_largest_received_pn +5573 && self.local_ip_may_migrate() +5574 && let Some(new_local_ip) = network_path.local_ip +5575 { +5576 let path_data = self.path_data_mut(path_id); +5577 if path_data +5578 .network_path +5579 .local_ip +5580 .is_some_and(|ip| ip != new_local_ip) +5581 { +5582 debug!( +5583 %path_id, +5584 new_4tuple = %network_path, +5585 prev_4tuple = %path_data.network_path, +5586 "local address passive migration" +5587 ); +5588 } +5589 path_data.network_path.local_ip = Some(new_local_ip) +5590 } +5591 +5592 // If the peer migrated to a new address, trigger migration. +5593 if self.peer_may_migrate() +5594 && (migrate_on_any_packet || !is_probing_packet) +5595 && is_largest_received_pn +5596 && network_path.remote != self.path_data(path_id).network_path.remote +5597 { +5598 self.migrate(path_id, now, network_path, migration_observed_addr); +5599 // Break linkability, if possible +5600 self.update_remote_cid(path_id); +5601 self.spin = false; +5602 } +5603 +5604 Ok(()) +5605 } +5606 +5607 /// Handles any on-path PATH_RESPONSE frames. +5608 /// +5609 /// *path_id* and *network_path* are those on which the PATH_RESPONSE was received. +5610 fn handle_path_response_on_path( +5611 &mut self, +5612 now: Instant, +5613 response: frame::PathResponse, +5614 path_id: PathId, +5615 ) { +5616 let is_multipath_negotiated = self.is_multipath_negotiated(); +5617 let path = self +5618 .paths +5619 .get_mut(&path_id) +5620 .expect("payload is processed only after the path becomes known"); +5621 match path.data.on_path_response_received(now, response.0) { +5622 paths::OnPathResponseReceived::OnPath if !self.abandoned_paths.contains(&path_id) => { +5623 let qlog = self.qlog.with_time(now); +5624 self.timers.stop( +5625 Timer::PerPath(path_id, PathTimer::PathValidationFailed), +5626 qlog.clone(), +5627 ); +5628 self.timers.stop( +5629 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), +5630 qlog.clone(), +5631 ); +5632 let next_challenge = path +5633 .data +5634 .earliest_on_path_expiring_challenge() +5635 .map(|time| time + self.ack_frequency.max_ack_delay_for_pto()); +5636 self.timers.set_or_stop( +5637 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +5638 next_challenge, +5639 qlog, +5640 ); +5641 let pns = self.spaces[SpaceKind::Data].for_path(path_id); +5642 if !matches!(pns.open_status, OpenStatus::Informed) { +5643 if is_multipath_negotiated { +5644 self.events +5645 .push_back(Event::Path(PathEvent::Established { id: path_id })); +5646 } +5647 pns.open_status = OpenStatus::Informed; +5648 if let Some(observed) = path.data.last_observed_addr_report.as_ref() { +5649 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5650 id: path_id, +5651 addr: observed.socket_addr(), +5652 })); +5653 } +5654 } +5655 if let Some((_, ref mut prev)) = path.prev { +5656 // If an on-path response was received while there is a +5657 // previous path from a migration, then the new path is +5658 // validated and we can stop sending challenges that try to +5659 // re-validate the previous path. +5660 prev.reset_on_path_challenges(); +5661 } +5662 } +5663 paths::OnPathResponseReceived::OnPath => { +5664 trace!( +5665 %response, +5666 "ignoring PATH_RESPONSE received after path is abandoned" +5667 ); +5668 } +5669 paths::OnPathResponseReceived::Unknown => { +5670 debug!(%response, "ignoring invalid PATH_RESPONSE"); +5671 } +5672 paths::OnPathResponseReceived::Ignored { +5673 sent_on, +5674 current_path, +5675 } => { +5676 debug!(%sent_on, %current_path, %response, "ignoring valid PATH_RESPONSE"); 5677 } 5678 } 5679 } 5680 -5681 /// Migrates the 4-tuple of the path. -5682 /// -5683 /// This creates a new [`PathData`] for the migrated path and stores the previous -5684 /// [`PathData`] in [`PathState::prev`]. -5685 fn migrate( -5686 &mut self, -5687 path_id: PathId, -5688 now: Instant, -5689 network_path: FourTuple, -5690 observed_addr: Option<ObservedAddr>, -5691 ) { -5692 trace!( -5693 new_4tuple = %network_path, -5694 prev_4tuple = %self.path_data(path_id).network_path, -5695 %path_id, -5696 "migration initiated", -5697 ); -5698 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); -5699 // TODO(@divma): conditions for path migration in multipath are very specific, check them -5700 // again to prevent path migrations that should actually create a new path -5701 -5702 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. -5703 // Note that the congestion window will not grow until validation terminates. Helps mitigate -5704 // amplification attacks performed by spoofing source addresses. -5705 let prev_pto = self.pto(SpaceKind::Data, path_id); -5706 let path = self.paths.get_mut(&path_id).expect("known path"); -5707 let mut new_path_data = if network_path.remote.is_ipv4() -5708 && network_path.remote.ip() == path.data.network_path.remote.ip() -5709 { -5710 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) -5711 } else { -5712 let peer_max_udp_payload_size = -5713 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) -5714 .unwrap_or(u16::MAX); -5715 PathData::new( -5716 network_path, -5717 self.allow_mtud, -5718 Some(peer_max_udp_payload_size), -5719 self.path_generation_counter, -5720 now, -5721 &self.config, -5722 ) -5723 }; -5724 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); -5725 if let Some(report) = observed_addr -5726 && let Some(updated) = new_path_data.update_observed_addr_report(report) -5727 { -5728 tracing::info!("adding observed addr event from migration"); -5729 self.events.push_back(Event::Path(PathEvent::ObservedAddr { -5730 id: path_id, -5731 addr: updated, -5732 })); -5733 } -5734 new_path_data.pending_on_path_challenge = true; -5735 -5736 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); -5737 -5738 // Only store this as previous path if it was validated. For all we know there could -5739 // already be a previous path stored which might have been validated in the past, -5740 // which is more valuable than one that's not yet validated. -5741 // -5742 // With multipath it is possible that there are no remote CIDs for the path ID -5743 // yet. In this case we would never have sent on this path yet and would not be able -5744 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair -5745 // anyway. So don't store such a path either. -5746 if !prev_path_data.validated -5747 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) -5748 { -5749 prev_path_data.pending_on_path_challenge = true; -5750 // We haven't updated the remote CID yet, this captures the remote CID we were using on -5751 // the previous path. -5752 path.prev = Some((cid, prev_path_data)); -5753 } -5754 -5755 // We need to re-assign the correct remote to this path in qlog -5756 self.qlog.emit_tuple_assigned(path_id, network_path, now); -5757 -5758 self.timers.set( -5759 Timer::PerPath(path_id, PathTimer::PathValidationFailed), -5760 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), -5761 self.qlog.with_time(now), -5762 ); -5763 } -5764 -5765 /// Handle a change in the local address, i.e. an active migration -5766 /// -5767 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged -5768 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if -5769 /// this is not the case. -5770 /// -5771 /// Clients in a connection in which multipath has been negotiated should migrate paths to new -5772 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new -5773 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating -5774 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might -5775 /// allow it altogether. -5776 /// -5777 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be -5778 /// migrated to new a [`PathId`]. -5779 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is -5780 // lacking <https://github.com/n0-computer/noq/issues/364> -5781 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { -5782 debug!("network changed"); -5783 if self.state.is_drained() { -5784 return; -5785 } -5786 if self.highest_space < SpaceKind::Data { -5787 for path in self.paths.values_mut() { -5788 // Clear the local address for it to be obtained from the socket again. -5789 path.data.network_path.local_ip = None; -5790 } -5791 -5792 self.update_remote_cid(PathId::ZERO); -5793 self.ping(); -5794 -5795 return; -5796 } -5797 -5798 // Paths that can't recover so a new path should be open instead. If multipath is not -5799 // negotiated, this will be empty. -5800 let mut non_recoverable_paths = Vec::default(); -5801 let mut recoverable_paths = Vec::default(); -5802 let mut open_paths = 0; -5803 -5804 let is_multipath_negotiated = self.is_multipath_negotiated(); -5805 let is_client = self.side().is_client(); -5806 let immediate_ack_allowed = self.peer_supports_ack_frequency(); +5681 /// Opens any paths that have been successfully NAT traversed. +5682 fn open_nat_traversed_paths(&mut self, now: Instant) { +5683 while let Some(network_path) = self +5684 .n0_nat_traversal +5685 .client_side_mut() +5686 .ok() +5687 .and_then(|s| s.pop_pending_path_open()) +5688 { +5689 match self.open_path_ensure(network_path, PathStatus::Backup, now) { +5690 Ok((path_id, already_existed)) => { +5691 debug!( +5692 %path_id, +5693 ?network_path, +5694 new_path = !already_existed, +5695 "Opened NAT traversal path", +5696 ); +5697 } +5698 Err(err) => match err { +5699 PathError::MultipathNotNegotiated +5700 | PathError::ServerSideNotAllowed +5701 | PathError::ValidationFailed +5702 | PathError::InvalidRemoteAddress(_) => { +5703 error!( +5704 ?err, +5705 ?network_path, +5706 "Failed to open path for successful NAT traversal" +5707 ); +5708 } +5709 PathError::MaxPathIdReached | PathError::RemoteCidsExhausted => { +5710 // Temporary error, put back. +5711 self.n0_nat_traversal +5712 .client_side_mut() +5713 .map(|s| s.push_pending_path_open(network_path)) +5714 .ok(); +5715 debug!( +5716 ?err, +5717 ?network_path, +5718 "Blocked opening NAT traversal path, enqueued" +5719 ); +5720 return; +5721 } +5722 }, +5723 } +5724 } +5725 } +5726 +5727 /// Migrates the 4-tuple of the path. +5728 /// +5729 /// This creates a new [`PathData`] for the migrated path and stores the previous +5730 /// [`PathData`] in [`PathState::prev`]. +5731 fn migrate( +5732 &mut self, +5733 path_id: PathId, +5734 now: Instant, +5735 network_path: FourTuple, +5736 observed_addr: Option<ObservedAddr>, +5737 ) { +5738 trace!( +5739 new_4tuple = %network_path, +5740 prev_4tuple = %self.path_data(path_id).network_path, +5741 %path_id, +5742 "migration initiated", +5743 ); +5744 self.path_generation_counter = self.path_generation_counter.wrapping_add(1); +5745 // TODO(@divma): conditions for path migration in multipath are very specific, check them +5746 // again to prevent path migrations that should actually create a new path +5747 +5748 // Reset rtt/congestion state for new path unless it looks like a NAT rebinding. +5749 // Note that the congestion window will not grow until validation terminates. Helps mitigate +5750 // amplification attacks performed by spoofing source addresses. +5751 let prev_pto = self.pto(SpaceKind::Data, path_id); +5752 let path = self.paths.get_mut(&path_id).expect("known path"); +5753 let mut new_path_data = if network_path.remote.is_ipv4() +5754 && network_path.remote.ip() == path.data.network_path.remote.ip() +5755 { +5756 PathData::from_previous(network_path, &path.data, self.path_generation_counter, now) +5757 } else { +5758 let peer_max_udp_payload_size = +5759 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()) +5760 .unwrap_or(u16::MAX); +5761 PathData::new( +5762 network_path, +5763 self.allow_mtud, +5764 Some(peer_max_udp_payload_size), +5765 self.path_generation_counter, +5766 now, +5767 &self.config, +5768 ) +5769 }; +5770 new_path_data.last_observed_addr_report = path.data.last_observed_addr_report.clone(); +5771 if let Some(report) = observed_addr +5772 && let Some(updated) = new_path_data.update_observed_addr_report(report) +5773 { +5774 tracing::info!("adding observed addr event from migration"); +5775 self.events.push_back(Event::Path(PathEvent::ObservedAddr { +5776 id: path_id, +5777 addr: updated, +5778 })); +5779 } +5780 new_path_data.pending_challenge = true; +5781 new_path_data.pending.observed_address = self +5782 .config +5783 .address_discovery_role +5784 .should_report(&self.peer_params.address_discovery_role); +5785 +5786 let mut prev_path_data = mem::replace(&mut path.data, new_path_data); +5787 +5788 // Only store this as previous path if it was validated. For all we know there could +5789 // already be a previous path stored which might have been validated in the past, +5790 // which is more valuable than one that's not yet validated. +5791 // +5792 // With multipath it is possible that there are no remote CIDs for the path ID +5793 // yet. In this case we would never have sent on this path yet and would not be able +5794 // to send a PATH_CHALLENGE either, which is currently a fire-and-forget affair +5795 // anyway. So don't store such a path either. +5796 if !prev_path_data.validated +5797 && let Some(cid) = self.remote_cids.get(&path_id).map(CidQueue::active) +5798 { +5799 prev_path_data.pending_challenge = true; +5800 // We haven't updated the remote CID yet, this captures the remote CID we were using on +5801 // the previous path. +5802 path.prev = Some((cid, prev_path_data)); +5803 } +5804 +5805 // We need to re-assign the correct remote to this path in qlog +5806 self.qlog.emit_tuple_assigned(path_id, network_path, now); 5807 -5808 for (path_id, path) in self.paths.iter_mut() { -5809 if self.abandoned_paths.contains(path_id) { -5810 continue; -5811 } -5812 open_paths += 1; -5813 -5814 // Read the network path BEFORE clearing local_ip, so the hint can -5815 // check which interface the path was using. -5816 let network_path = path.data.network_path; -5817 -5818 // Clear the local address for it to be obtained from the socket again. This applies to -5819 // all paths, regardless of being considered recoverable or not -5820 path.data.network_path.local_ip = None; -5821 let remote = network_path.remote; -5822 -5823 // Without multipath, the connection tries to recover the single path, whereas with -5824 // multipath, even in a single-path scenario, we attempt to migrate the path to a new -5825 // PathId. -5826 let attempt_to_recover = if is_multipath_negotiated { -5827 // Use the hint to determine if the path can recover. When no hint is -5828 // provided, clients default to non-recoverable (abandon and re-open) -5829 // while servers default to recoverable (attempt in-place recovery). -5830 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) -5831 .unwrap_or(!is_client) -5832 } else { -5833 // In the non multipath case, we try to recover the single active path -5834 true -5835 }; -5836 -5837 if attempt_to_recover { -5838 recoverable_paths.push((*path_id, remote)); -5839 } else { -5840 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) -5841 } -5842 } -5843 -5844 /* NON RECOVERABLE PATHS */ -5845 // This are handled first, so that in case the treatment intended for these fails, we can -5846 // go the recoverable route instead. +5808 self.timers.set( +5809 Timer::PerPath(path_id, PathTimer::PathValidationFailed), +5810 now + 3 * cmp::max(self.pto(SpaceKind::Data, path_id), prev_pto), +5811 self.qlog.with_time(now), +5812 ); +5813 } +5814 +5815 /// Handle a change in the local address, i.e. an active migration +5816 /// +5817 /// In the general (non-multipath) case, paths will perform a RFC9000 migration and be pinged +5818 /// for a liveness check. This is the behaviour of a path assumed to be recoverable, even if +5819 /// this is not the case. +5820 /// +5821 /// Clients in a connection in which multipath has been negotiated should migrate paths to new +5822 /// [`PathId`]s. For paths that are known to be non-recoverable can be migrated to a new +5823 /// [`PathId`] by closing the current path, and opening a new one to the same remote. Treating +5824 /// paths as non recoverable when necessary accelerates connectivity re-establishment, or might +5825 /// allow it altogether. +5826 /// +5827 /// The optional `hint` allows callers to indicate when paths are non-recoverable and should be +5828 /// migrated to new a [`PathId`]. +5829 // NOTE: only clients are allowed to migrate, but generally dealing with RFC9000 migrations is +5830 // lacking <https://github.com/n0-computer/noq/issues/364> +5831 pub fn handle_network_change(&mut self, hint: Option<&dyn NetworkChangeHint>, now: Instant) { +5832 debug!("network changed"); +5833 if self.state.is_drained() { +5834 return; +5835 } +5836 if self.highest_space < SpaceKind::Data { +5837 for path in self.paths.values_mut() { +5838 // Clear the local address for it to be obtained from the socket again. +5839 path.data.network_path.local_ip = None; +5840 } +5841 +5842 self.update_remote_cid(PathId::ZERO); +5843 self.ping(); +5844 +5845 return; +5846 } 5847 -5848 // Decide if we need to close first or open first in the multipath case. -5849 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. -5850 // - Closing first risks this being the only open path. -5851 // We prefer closing paths first unless we identify this is the last open path. -5852 let open_first = open_paths == non_recoverable_paths.len(); +5848 // Paths that can't recover so a new path should be open instead. If multipath is not +5849 // negotiated, this will be empty. +5850 let mut non_recoverable_paths = Vec::default(); +5851 let mut recoverable_paths = Vec::default(); +5852 let mut open_paths = 0; 5853 -5854 for (path_id, remote, status) in non_recoverable_paths.into_iter() { -5855 let network_path = FourTuple { -5856 remote, -5857 local_ip: None, /* allow the local ip to be discovered */ -5858 }; -5859 -5860 if open_first && let Err(e) = self.open_path(network_path, status, now) { -5861 if self.side().is_client() { -5862 debug!(%e, "Failed to open new path for network change"); -5863 } -5864 // if this fails, let the path try to recover itself -5865 recoverable_paths.push((path_id, remote)); -5866 continue; -5867 } -5868 -5869 if let Err(e) = -5870 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) -5871 { -5872 debug!(%e,"Failed to close unrecoverable path after network change"); -5873 recoverable_paths.push((path_id, remote)); -5874 continue; -5875 } -5876 -5877 if !open_first && let Err(e) = self.open_path(network_path, status, now) { -5878 // Path has already been closed if we got here. Since the path was not recoverable, -5879 // this might be desirable in any case, because other paths exist (!open_first) and -5880 // this was is considered non recoverable -5881 debug!(%e,"Failed to open new path for network change"); -5882 } -5883 } -5884 -5885 /* RECOVERABLE PATHS */ +5854 let is_multipath_negotiated = self.is_multipath_negotiated(); +5855 let is_client = self.side().is_client(); +5856 let immediate_ack_allowed = self.peer_supports_ack_frequency(); +5857 +5858 for (path_id, path) in self.paths.iter_mut() { +5859 if self.abandoned_paths.contains(path_id) { +5860 continue; +5861 } +5862 open_paths += 1; +5863 +5864 // Read the network path BEFORE clearing local_ip, so the hint can +5865 // check which interface the path was using. +5866 let network_path = path.data.network_path; +5867 +5868 // Clear the local address for it to be obtained from the socket again. This applies to +5869 // all paths, regardless of being considered recoverable or not +5870 path.data.network_path.local_ip = None; +5871 let remote = network_path.remote; +5872 +5873 // Without multipath, the connection tries to recover the single path, whereas with +5874 // multipath, even in a single-path scenario, we attempt to migrate the path to a new +5875 // PathId. +5876 let attempt_to_recover = if is_multipath_negotiated { +5877 // Use the hint to determine if the path can recover. When no hint is +5878 // provided, clients default to non-recoverable (abandon and re-open) +5879 // while servers default to recoverable (attempt in-place recovery). +5880 hint.map(|h| h.is_path_recoverable(*path_id, network_path)) +5881 .unwrap_or(!is_client) +5882 } else { +5883 // In the non multipath case, we try to recover the single active path +5884 true +5885 }; 5886 -5887 for (path_id, remote) in recoverable_paths.into_iter() { -5888 // Schedule a Ping for a liveness check. -5889 if let Some(path_space) = self.spaces[SpaceId::Data].number_spaces.get_mut(&path_id) { -5890 path_space.ping_pending = true; -5891 -5892 if immediate_ack_allowed { -5893 path_space.immediate_ack_pending = true; -5894 } -5895 } -5896 -5897 // Reset PTO backoff so retransmits resume promptly. Congestion controller and -5898 // RTT are intentionally preserved for recoverable paths. We explicitly allow -5899 // this reset also during the handshake, so do not check -5900 // Self::peer_competed_handshake_address_validation. -5901 if let Some(path) = self.paths.get_mut(&path_id) { -5902 path.data.pto_count = 0; -5903 } -5904 self.set_loss_detection_timer(now, path_id); -5905 -5906 let Some((reset_token, retired)) = -5907 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) -5908 else { -5909 continue; -5910 }; -5911 -5912 // Retire the current remote CID and any CIDs we had to skip. -5913 self.spaces[SpaceId::Data] -5914 .pending -5915 .retire_cids -5916 .extend(retired.map(|seq| (path_id, seq))); -5917 -5918 debug_assert!(!self.state.is_drained()); // required for endpoint_events, checked above -5919 self.endpoint_events -5920 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); -5921 } -5922 } -5923 -5924 /// Switch to a previously unused remote connection ID, if possible -5925 fn update_remote_cid(&mut self, path_id: PathId) { -5926 let Some((reset_token, retired)) = self -5927 .remote_cids -5928 .get_mut(&path_id) -5929 .and_then(|cids| cids.next()) -5930 else { -5931 return; -5932 }; -5933 -5934 // Retire the current remote CID and any CIDs we had to skip. -5935 self.spaces[SpaceId::Data] -5936 .pending -5937 .retire_cids -5938 .extend(retired.map(|seq| (path_id, seq))); -5939 let remote = self.path_data(path_id).network_path.remote; -5940 self.set_reset_token(path_id, remote, reset_token); -5941 } -5942 -5943 /// Sends this reset token to the endpoint -5944 /// -5945 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer -5946 /// sends a reset token it knows to route it to this connection. See RFC 9000 section -5947 /// 10.3. Stateless Reset. -5948 /// -5949 /// Reset tokens are different for each path, the endpoint identifies paths by peer -5950 /// socket address however, not by path ID. -5951 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { -5952 debug_assert!(!self.state.is_drained()); // required for endpoint events, set_reset_token is never called for drained connections -5953 self.endpoint_events -5954 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5887 if attempt_to_recover { +5888 recoverable_paths.push((*path_id, remote)); +5889 } else { +5890 non_recoverable_paths.push((*path_id, remote, path.data.local_status())) +5891 } +5892 } +5893 +5894 /* NON RECOVERABLE PATHS */ +5895 // This are handled first, so that in case the treatment intended for these fails, we can +5896 // go the recoverable route instead. +5897 +5898 // Decide if we need to close first or open first in the multipath case. +5899 // - Opening first has a higher risk of getting limited by the negotiated MAX_PATH_ID. +5900 // - Closing first risks this being the only open path. +5901 // We prefer closing paths first unless we identify this is the last open path. +5902 let open_first = open_paths == non_recoverable_paths.len(); +5903 +5904 for (path_id, remote, status) in non_recoverable_paths.into_iter() { +5905 let network_path = FourTuple { +5906 remote, +5907 local_ip: None, /* allow the local ip to be discovered */ +5908 }; +5909 +5910 if open_first && let Err(e) = self.open_path(network_path, status, now) { +5911 if self.side().is_client() { +5912 debug!(%e, "Failed to open new path for network change"); +5913 } +5914 // if this fails, let the path try to recover itself +5915 recoverable_paths.push((path_id, remote)); +5916 continue; +5917 } +5918 +5919 if let Err(e) = +5920 self.close_path_inner(now, path_id, PathAbandonReason::UnusableAfterNetworkChange) +5921 { +5922 debug!(%e,"Failed to close unrecoverable path after network change"); +5923 recoverable_paths.push((path_id, remote)); +5924 continue; +5925 } +5926 +5927 if !open_first && let Err(e) = self.open_path(network_path, status, now) { +5928 // Path has already been closed if we got here. Since the path was not recoverable, +5929 // this might be desirable in any case, because other paths exist (!open_first) and +5930 // this was is considered non recoverable +5931 debug!(%e,"Failed to open new path for network change"); +5932 } +5933 } +5934 +5935 /* RECOVERABLE PATHS */ +5936 +5937 for (path_id, remote) in recoverable_paths.into_iter() { +5938 // Schedule a Ping for a liveness check. +5939 if let Some(path_space) = self.spaces[SpaceId::Data].number_spaces.get_mut(&path_id) { +5940 path_space.pending_ping = true; +5941 +5942 if immediate_ack_allowed { +5943 path_space.pending_immediate_ack = true; +5944 } +5945 } +5946 +5947 // Reset PTO backoff so retransmits resume promptly. Congestion controller and +5948 // RTT are intentionally preserved for recoverable paths. We explicitly allow +5949 // this reset also during the handshake, so do not check +5950 // Self::peer_competed_handshake_address_validation. +5951 if let Some(path) = self.paths.get_mut(&path_id) { +5952 path.data.pto_count = 0; +5953 } +5954 self.set_loss_detection_timer(now, path_id); 5955 -5956 // During the handshake the server sends a reset token in the transport -5957 // parameters. When we are the client and we receive the reset token during the -5958 // handshake we want this to affect our peer transport parameters. -5959 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten -5960 // shortly after this was called. And then the params don't have this anymore. -5961 if path_id == PathId::ZERO { -5962 self.peer_params.stateless_reset_token = Some(reset_token); -5963 } -5964 } -5965 -5966 /// Issue an initial set of connection IDs to the peer upon connection -5967 fn issue_first_cids(&mut self, now: Instant) { -5968 if self -5969 .local_cid_state -5970 .get(&PathId::ZERO) -5971 .expect("PathId::ZERO exists when the connection is created") -5972 .cid_len() -5973 == 0 -5974 { -5975 return; -5976 } -5977 -5978 // Subtract 1 to account for the CID we supplied while handshaking -5979 let mut n = self.peer_params.issue_cids_limit() - 1; -5980 if let ConnectionSide::Server { server_config } = &self.side -5981 && server_config.has_preferred_address() -5982 { -5983 // We also sent a CID in the transport parameters -5984 n -= 1; -5985 } -5986 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -5987 self.endpoint_events -5988 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -5989 } -5990 -5991 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued -5992 /// -5993 /// Later CIDs are issued when CIDs expire or are retired by the peer. -5994 fn issue_first_path_cids(&mut self, now: Instant) { -5995 if let Some(max_path_id) = self.max_path_id() { -5996 let mut path_id = self.max_path_id_with_cids.next(); -5997 while path_id <= max_path_id { -5998 self.endpoint_events -5999 .push_back(EndpointEventInner::NeedIdentifiers( -6000 path_id, -6001 now, -6002 self.peer_params.issue_cids_limit(), -6003 )); -6004 path_id = path_id.next(); -6005 } -6006 self.max_path_id_with_cids = max_path_id; -6007 } -6008 } -6009 -6010 /// Populates a packet with frames -6011 /// -6012 /// This tries to fit as many frames as possible into the packet. -6013 /// -6014 /// *path_exclusive_only* means to only build frames which can only be sent on this -6015 /// *path. This is used in multipath for backup paths while there is still an active -6016 /// *path. -6017 fn populate_packet<'a, 'b>( -6018 &mut self, -6019 now: Instant, -6020 space_id: SpaceId, -6021 path_id: PathId, -6022 scheduling_info: &PathSchedulingInfo, -6023 builder: &mut PacketBuilder<'a, 'b>, -6024 ) { -6025 let is_multipath_negotiated = self.is_multipath_negotiated(); -6026 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); -6027 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; -6028 let stats = &mut self.path_stats.for_path(path_id).frame_tx; -6029 let space = &mut self.spaces[space_id]; -6030 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -6031 space -6032 .for_path(path_id) -6033 .pending_acks -6034 .maybe_ack_non_eliciting(); -6035 -6036 // HANDSHAKE_DONE -6037 if !is_0rtt -6038 && !scheduling_info.is_abandoned -6039 && scheduling_info.may_send_data -6040 && mem::replace(&mut space.pending.handshake_done, false) -6041 { -6042 builder.write_frame(frame::HandshakeDone, stats); -6043 } -6044 -6045 // PING -6046 if !scheduling_info.is_abandoned -6047 && mem::replace(&mut space.for_path(path_id).ping_pending, false) -6048 { -6049 builder.write_frame(frame::Ping, stats); -6050 } -6051 -6052 // IMMEDIATE_ACK -6053 if !scheduling_info.is_abandoned -6054 && mem::replace(&mut space.for_path(path_id).immediate_ack_pending, false) -6055 { -6056 debug_assert_eq!( -6057 space_id, -6058 SpaceId::Data, -6059 "immediate acks must be sent in the data space" -6060 ); -6061 builder.write_frame(frame::ImmediateAck, stats); -6062 } -6063 -6064 // ACK -6065 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -6066 for path_id in space -6067 .number_spaces -6068 .iter_mut() -6069 .filter(|(_, pns)| pns.pending_acks.can_send()) -6070 .map(|(&path_id, _)| path_id) -6071 .collect::<Vec<_>>() -6072 { -6073 Self::populate_acks( -6074 now, -6075 self.receiving_ecn, -6076 path_id, -6077 space_id, -6078 space, -6079 is_multipath_negotiated, -6080 builder, -6081 stats, -6082 space_has_keys, -6083 ); -6084 } -6085 } -6086 -6087 // ACK_FREQUENCY -6088 if !scheduling_info.is_abandoned +5956 let Some((reset_token, retired)) = +5957 self.remote_cids.get_mut(&path_id).and_then(CidQueue::next) +5958 else { +5959 continue; +5960 }; +5961 +5962 // Retire the current remote CID and any CIDs we had to skip. +5963 self.spaces[SpaceId::Data] +5964 .pending +5965 .retire_cids +5966 .extend(retired.map(|seq| (path_id, seq))); +5967 +5968 debug_assert!(!self.state.is_drained()); // required for endpoint_events, checked above +5969 self.endpoint_events +5970 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +5971 } +5972 } +5973 +5974 /// Switch to a previously unused remote connection ID, if possible +5975 fn update_remote_cid(&mut self, path_id: PathId) { +5976 let Some((reset_token, retired)) = self +5977 .remote_cids +5978 .get_mut(&path_id) +5979 .and_then(|cids| cids.next()) +5980 else { +5981 return; +5982 }; +5983 +5984 // Retire the current remote CID and any CIDs we had to skip. +5985 self.spaces[SpaceId::Data] +5986 .pending +5987 .retire_cids +5988 .extend(retired.map(|seq| (path_id, seq))); +5989 let remote = self.path_data(path_id).network_path.remote; +5990 self.set_reset_token(path_id, remote, reset_token); +5991 } +5992 +5993 /// Sends this reset token to the endpoint +5994 /// +5995 /// The endpoint needs to know the reset tokens issued by the peer, so that if the peer +5996 /// sends a reset token it knows to route it to this connection. See RFC 9000 section +5997 /// 10.3. Stateless Reset. +5998 /// +5999 /// Reset tokens are different for each path, the endpoint identifies paths by peer +6000 /// socket address however, not by path ID. +6001 fn set_reset_token(&mut self, path_id: PathId, remote: SocketAddr, reset_token: ResetToken) { +6002 debug_assert!(!self.state.is_drained()); // required for endpoint events, set_reset_token is never called for drained connections +6003 self.endpoint_events +6004 .push_back(EndpointEventInner::ResetToken(path_id, remote, reset_token)); +6005 +6006 // During the handshake the server sends a reset token in the transport +6007 // parameters. When we are the client and we receive the reset token during the +6008 // handshake we want this to affect our peer transport parameters. +6009 // TODO(flub): Pretty sure this is pointless, the entire params is overwritten +6010 // shortly after this was called. And then the params don't have this anymore. +6011 if path_id == PathId::ZERO { +6012 self.peer_params.stateless_reset_token = Some(reset_token); +6013 } +6014 } +6015 +6016 /// Issue an initial set of connection IDs to the peer upon connection +6017 fn issue_first_cids(&mut self, now: Instant) { +6018 if self +6019 .local_cid_state +6020 .get(&PathId::ZERO) +6021 .expect("PathId::ZERO exists when the connection is created") +6022 .cid_len() +6023 == 0 +6024 { +6025 return; +6026 } +6027 +6028 // Subtract 1 to account for the CID we supplied while handshaking +6029 let mut n = self.peer_params.issue_cids_limit() - 1; +6030 if let ConnectionSide::Server { server_config } = &self.side +6031 && server_config.has_preferred_address() +6032 { +6033 // We also sent a CID in the transport parameters +6034 n -= 1; +6035 } +6036 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events +6037 self.endpoint_events +6038 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +6039 } +6040 +6041 /// Issues an initial set of CIDs for paths that have not yet had any CIDs issued +6042 /// +6043 /// Later CIDs are issued when CIDs expire or are retired by the peer. +6044 fn issue_first_path_cids(&mut self, now: Instant) { +6045 if let Some(max_path_id) = self.max_path_id() { +6046 let mut path_id = self.max_path_id_with_cids.next(); +6047 while path_id <= max_path_id { +6048 self.endpoint_events +6049 .push_back(EndpointEventInner::NeedIdentifiers( +6050 path_id, +6051 now, +6052 self.peer_params.issue_cids_limit(), +6053 )); +6054 path_id = path_id.next(); +6055 } +6056 self.max_path_id_with_cids = max_path_id; +6057 } +6058 } +6059 +6060 /// Populates a packet with frames +6061 /// +6062 /// This tries to fit as many frames as possible into the packet. +6063 /// +6064 /// *path_exclusive_only* means to only build frames which can only be sent on this +6065 /// *path. This is used in multipath for backup paths while there is still an active +6066 /// *path. +6067 fn populate_packet<'a, 'b>( +6068 &mut self, +6069 now: Instant, +6070 space_id: SpaceId, +6071 path_id: PathId, +6072 scheduling_info: &PathSchedulingInfo, +6073 builder: &mut PacketBuilder<'a, 'b>, +6074 ) { +6075 let is_multipath_negotiated = self.is_multipath_negotiated(); +6076 let space_has_keys = self.crypto_state.has_keys(space_id.encryption_level()); +6077 let is_0rtt = space_id == SpaceId::Data && !space_has_keys; +6078 let stats = &mut self.path_stats.get_mut(path_id).frame_tx; +6079 let space = &mut self.spaces[space_id]; +6080 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +6081 space +6082 .for_path(path_id) +6083 .pending_acks +6084 .maybe_ack_non_eliciting(); +6085 +6086 // HANDSHAKE_DONE +6087 if !is_0rtt +6088 && !scheduling_info.is_abandoned 6089 && scheduling_info.may_send_data -6090 && mem::replace(&mut space.pending.ack_frequency, false) +6090 && mem::replace(&mut space.pending.handshake_done, false) 6091 { -6092 let sequence_number = self.ack_frequency.next_sequence_number(); -6093 -6094 // Safe to unwrap because this is always provided when ACK frequency is enabled -6095 let config = self.config.ack_frequency_config.as_ref().unwrap(); -6096 -6097 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error -6098 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( -6099 path.rtt.get(), -6100 config, -6101 &self.peer_params, -6102 ); -6103 -6104 let frame = frame::AckFrequency { -6105 sequence: sequence_number, -6106 ack_eliciting_threshold: config.ack_eliciting_threshold, -6107 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), -6108 reordering_threshold: config.reordering_threshold, -6109 }; -6110 builder.write_frame(frame, stats); -6111 -6112 self.ack_frequency -6113 .ack_frequency_sent(path_id, builder.packet_number, max_ack_delay); -6114 } -6115 -6116 // PATH_CHALLENGE -6117 if !scheduling_info.is_abandoned -6118 && space_id == SpaceId::Data -6119 && path.pending_on_path_challenge -6120 && !self.state.is_closed() -6121 && builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND -6122 // we don't want to send new challenges if we are already closing -6123 { -6124 path.pending_on_path_challenge = false; -6125 -6126 let token = self.rng.random(); -6127 path.record_path_challenge_sent(now, token, path.network_path); -6128 // Generate a new challenge every time we send a new PATH_CHALLENGE -6129 let challenge = frame::PathChallenge(token); -6130 builder.write_frame(challenge, stats); -6131 builder.require_padding(); -6132 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); -6133 match path.open_status { -6134 paths::OpenStatus::Sent | paths::OpenStatus::Informed => {} -6135 paths::OpenStatus::Pending => { -6136 path.open_status = paths::OpenStatus::Sent; -6137 self.timers.set( -6138 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), -6139 now + 3 * pto, -6140 self.qlog.with_time(now), -6141 ); -6142 } -6143 } -6144 -6145 self.timers.set( -6146 Timer::PerPath(path_id, PathTimer::PathChallengeLost), -6147 now + pto, -6148 self.qlog.with_time(now), -6149 ); -6150 -6151 if is_multipath_negotiated && !path.validated && path.pending_on_path_challenge { -6152 // queue informing the path status along with the challenge -6153 space.pending.path_status.insert(path_id); -6154 } -6155 -6156 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless -6157 // of whether one has already been sent on this path. -6158 if space_id == SpaceId::Data -6159 && self -6160 .config -6161 .address_discovery_role -6162 .should_report(&self.peer_params.address_discovery_role) -6163 { -6164 let frame = frame::ObservedAddr::new( -6165 path.network_path.remote, -6166 self.next_observed_addr_seq_no, -6167 ); -6168 if builder.frame_space_remaining() > frame.size() { -6169 builder.write_frame(frame, stats); -6170 -6171 self.next_observed_addr_seq_no = -6172 self.next_observed_addr_seq_no.saturating_add(1u8); -6173 path.observed_addr_sent = true; -6174 -6175 space.pending.observed_addr = false; -6176 } -6177 } -6178 } -6179 -6180 // PATH_RESPONSE -6181 if !scheduling_info.is_abandoned -6182 && space_id == SpaceId::Data -6183 && builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND -6184 && let Some(token) = path.path_responses.pop_on_path(path.network_path) -6185 { -6186 let response = frame::PathResponse(token); -6187 builder.write_frame(response, stats); -6188 builder.require_padding(); -6189 -6190 // NOTE: this is technically not required but might be useful to ride the -6191 // request/response nature of path challenges to refresh an observation -6192 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. -6193 if space_id == SpaceId::Data -6194 && self -6195 .config -6196 .address_discovery_role -6197 .should_report(&self.peer_params.address_discovery_role) -6198 { -6199 let frame = frame::ObservedAddr::new( -6200 path.network_path.remote, -6201 self.next_observed_addr_seq_no, -6202 ); -6203 if builder.frame_space_remaining() > frame.size() { -6204 builder.write_frame(frame, stats); -6205 -6206 self.next_observed_addr_seq_no = -6207 self.next_observed_addr_seq_no.saturating_add(1u8); -6208 path.observed_addr_sent = true; +6092 builder.write_frame(frame::HandshakeDone, stats); +6093 } +6094 +6095 // PING +6096 if !scheduling_info.is_abandoned +6097 && mem::replace(&mut space.for_path(path_id).pending_ping, false) +6098 { +6099 builder.write_frame(frame::Ping, stats); +6100 } +6101 +6102 // IMMEDIATE_ACK +6103 if !scheduling_info.is_abandoned +6104 && mem::replace(&mut space.for_path(path_id).pending_immediate_ack, false) +6105 { +6106 debug_assert_eq!( +6107 space_id, +6108 SpaceId::Data, +6109 "immediate acks must be sent in the data space" +6110 ); +6111 builder.write_frame(frame::ImmediateAck, stats); +6112 } +6113 +6114 // ACK +6115 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { +6116 for path_id in space +6117 .number_spaces +6118 .iter_mut() +6119 .filter(|(_, pns)| pns.pending_acks.can_send()) +6120 .map(|(&path_id, _)| path_id) +6121 .collect::<Vec<_>>() +6122 { +6123 Self::populate_acks( +6124 now, +6125 self.receiving_ecn, +6126 path_id, +6127 space_id, +6128 space, +6129 is_multipath_negotiated, +6130 builder, +6131 stats, +6132 space_has_keys, +6133 ); +6134 } +6135 } +6136 +6137 // ACK_FREQUENCY +6138 if !scheduling_info.is_abandoned +6139 && scheduling_info.may_send_data +6140 && mem::replace(&mut space.pending.ack_frequency, false) +6141 { +6142 let sequence_number = self.ack_frequency.next_sequence_number(); +6143 +6144 // Safe to unwrap because this is always provided when ACK frequency is enabled +6145 let config = self.config.ack_frequency_config.as_ref().unwrap(); +6146 +6147 // Ensure the delay is within bounds to avoid a PROTOCOL_VIOLATION error +6148 let max_ack_delay = self.ack_frequency.candidate_max_ack_delay( +6149 path.rtt.get(), +6150 config, +6151 &self.peer_params, +6152 ); +6153 +6154 let frame = frame::AckFrequency { +6155 sequence: sequence_number, +6156 ack_eliciting_threshold: config.ack_eliciting_threshold, +6157 request_max_ack_delay: max_ack_delay.as_micros().try_into().unwrap_or(VarInt::MAX), +6158 reordering_threshold: config.reordering_threshold, +6159 }; +6160 builder.write_frame(frame, stats); +6161 +6162 self.ack_frequency +6163 .ack_frequency_sent(path_id, builder.packet_number, max_ack_delay); +6164 } +6165 +6166 // PATH_CHALLENGE (on-path) +6167 if !scheduling_info.is_abandoned +6168 && space_id == SpaceId::Data +6169 && path.pending_challenge +6170 // we don't want to send new challenges if we are already closing +6171 && !self.state.is_closed() +6172 && builder.frame_space_remaining() > frame::PathChallenge::SIZE_BOUND +6173 // An on-path PATH_CHALLENGE must be part of datagrams expanded to the +6174 // MIN_INITIAL_SIZE (1200 bytes). +6175 && builder.buf.segment_size() >= usize::from(MIN_INITIAL_SIZE) +6176 { +6177 path.pending_challenge = false; +6178 +6179 let token = self.rng.random(); +6180 path.record_path_challenge_sent(now, token, path.network_path); +6181 // Generate a new challenge every time we send a new PATH_CHALLENGE +6182 let challenge = frame::PathChallenge(token); +6183 builder.write_frame(challenge, stats); +6184 builder.require_padding(); +6185 let pto = self.ack_frequency.max_ack_delay_for_pto() + path.rtt.pto_base(); +6186 let pns = space.for_path(path_id); +6187 match pns.open_status { +6188 OpenStatus::Sent | OpenStatus::Informed => {} +6189 OpenStatus::Pending => { +6190 pns.open_status = OpenStatus::Sent; +6191 self.timers.set( +6192 Timer::PerPath(path_id, PathTimer::AbandonFromValidation), +6193 now + 3 * pto, +6194 self.qlog.with_time(now), +6195 ); +6196 } +6197 } +6198 +6199 self.timers.set( +6200 Timer::PerPath(path_id, PathTimer::PathChallengeLost), +6201 now + path.on_path_challenge_expiry(), +6202 self.qlog.with_time(now), +6203 ); +6204 +6205 if is_multipath_negotiated && !path.validated && path.pending_challenge { +6206 // queue informing the path status along with the challenge +6207 space.pending.path_status.insert(path_id); +6208 } 6209 -6210 space.pending.observed_addr = false; -6211 } -6212 } -6213 } -6214 -6215 // REACH_OUT -6216 while !scheduling_info.is_abandoned -6217 && scheduling_info.may_send_data -6218 && let Some(reach_out) = space -6219 .pending -6220 .reach_out -6221 .pop_if(|frame| builder.frame_space_remaining() >= frame.size()) -6222 { -6223 builder.write_frame(reach_out, stats); -6224 } -6225 -6226 // PATH_ABANDON -6227 if space_id == SpaceId::Data -6228 && scheduling_info.is_abandoned -6229 && scheduling_info.may_self_abandon -6230 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -6231 && let Some(error_code) = space.pending.path_abandon.remove(&path_id) -6232 { -6233 let frame = frame::PathAbandon { -6234 path_id, -6235 error_code, -6236 }; -6237 builder.write_frame(frame, stats); -6238 -6239 // Consider remotely issued CIDs as retired now that we have sent this frame at -6240 // least once. -6241 self.remote_cids.remove(&path_id); -6242 } -6243 while space_id == SpaceId::Data -6244 && scheduling_info.may_send_data -6245 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() -6246 && let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() -6247 { -6248 let frame = frame::PathAbandon { -6249 path_id: abandoned_path_id, -6250 error_code, -6251 }; -6252 builder.write_frame(frame, stats); -6253 -6254 // Consider remotely issued CIDs as retired now that we have sent this frame at -6255 // least once. -6256 self.remote_cids.remove(&abandoned_path_id); -6257 } -6258 -6259 // OBSERVED_ADDR -6260 if !scheduling_info.is_abandoned -6261 && scheduling_info.may_send_data -6262 && space_id == SpaceId::Data -6263 && self -6264 .config -6265 .address_discovery_role -6266 .should_report(&self.peer_params.address_discovery_role) -6267 && (!path.observed_addr_sent || space.pending.observed_addr) -6268 { -6269 let frame = -6270 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); -6271 if builder.frame_space_remaining() > frame.size() { -6272 builder.write_frame(frame, stats); -6273 -6274 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); -6275 path.observed_addr_sent = true; +6210 // Always include an OBSERVED_ADDR frame with a PATH_CHALLENGE, regardless +6211 // of whether one has already been sent on this path. +6212 path.pending.observed_address = self +6213 .config +6214 .address_discovery_role +6215 .should_report(&self.peer_params.address_discovery_role); +6216 } +6217 +6218 // PATH_RESPONSE (on-path) +6219 if !scheduling_info.is_abandoned +6220 && space_id == SpaceId::Data +6221 && builder.frame_space_remaining() > frame::PathResponse::SIZE_BOUND +6222 // An on-path PATH_RESPONSE must be part of datagrams expanded to the +6223 // MIN_INITIAL_SIZE (1200 bytes). +6224 && builder.buf.segment_size() >= usize::from(MIN_INITIAL_SIZE) +6225 && let Some(token) = space.for_path(path_id).pending_path_responses.pop_on_path(path.network_path) +6226 { +6227 let response = frame::PathResponse(token); +6228 builder.write_frame(response, stats); +6229 builder.require_padding(); +6230 +6231 // NOTE: this is technically not required but might be useful to ride the +6232 // request/response nature of path challenges to refresh an observation +6233 // Since PATH_RESPONSE is a probing frame, this is allowed by the spec. +6234 path.pending.observed_address = self +6235 .config +6236 .address_discovery_role +6237 .should_report(&self.peer_params.address_discovery_role); +6238 } +6239 +6240 // ADD_ADDRESS +6241 while space_id == SpaceId::Data +6242 && !scheduling_info.is_abandoned +6243 && scheduling_info.may_send_data +6244 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() +6245 { +6246 if let Some(added_address) = space.pending.add_address.pop_last() { +6247 builder.write_frame(added_address, stats); +6248 } else { +6249 break; +6250 } +6251 } +6252 +6253 // REMOVE_ADDRESS +6254 while space_id == SpaceId::Data +6255 && !scheduling_info.is_abandoned +6256 && scheduling_info.may_send_data +6257 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() +6258 { +6259 if let Some(removed_address) = space.pending.remove_address.pop_last() { +6260 builder.write_frame(removed_address, stats); +6261 } else { +6262 break; +6263 } +6264 } +6265 +6266 // REACH_OUT +6267 while !scheduling_info.is_abandoned +6268 && scheduling_info.may_send_data +6269 && let Some(reach_out) = space +6270 .pending +6271 .reach_out +6272 .pop_if(|frame| builder.frame_space_remaining() >= frame.size()) +6273 { +6274 builder.write_frame(reach_out, stats); +6275 } 6276 -6277 space.pending.observed_addr = false; -6278 } -6279 } -6280 -6281 // CRYPTO -6282 while !is_0rtt -6283 && !scheduling_info.is_abandoned -6284 && scheduling_info.may_send_data -6285 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND -6286 { -6287 let Some(mut frame) = space.pending.crypto.pop_front() else { -6288 break; -6289 }; -6290 -6291 // Calculate the maximum amount of crypto data we can store in the buffer. -6292 // Since the offset is known, we can reserve the exact size required to encode it. -6293 // For length we reserve 2bytes which allows to encode up to 2^14, -6294 // which is more than what fits into normally sized QUIC frames. -6295 let max_crypto_data_size = builder.frame_space_remaining() -6296 - 1 // Frame Type -6297 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) -6298 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes -6299 -6300 let len = frame -6301 .data -6302 .len() -6303 .min(2usize.pow(14) - 1) -6304 .min(max_crypto_data_size); -6305 -6306 let data = frame.data.split_to(len); -6307 let offset = frame.offset; -6308 let truncated = frame::Crypto { offset, data }; -6309 builder.write_frame(truncated, stats); -6310 -6311 if !frame.data.is_empty() { -6312 frame.offset += len as u64; -6313 space.pending.crypto.push_front(frame); -6314 } -6315 } -6316 -6317 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP -6318 while space_id == SpaceId::Data -6319 && !scheduling_info.is_abandoned -6320 && scheduling_info.may_send_data -6321 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() -6322 { -6323 let Some(path_id) = space.pending.path_status.pop_first() else { -6324 break; -6325 }; -6326 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { -6327 trace!(%path_id, "discarding queued path status for unknown path"); -6328 continue; -6329 }; -6330 -6331 let seq = path.status.seq(); -6332 match path.local_status() { -6333 PathStatus::Available => { -6334 let frame = frame::PathStatusAvailable { -6335 path_id, -6336 status_seq_no: seq, -6337 }; -6338 builder.write_frame(frame, stats); -6339 } -6340 PathStatus::Backup => { -6341 let frame = frame::PathStatusBackup { -6342 path_id, -6343 status_seq_no: seq, -6344 }; -6345 builder.write_frame(frame, stats); -6346 } -6347 } -6348 } +6277 // PATH_ABANDON +6278 if space_id == SpaceId::Data +6279 && scheduling_info.is_abandoned +6280 && scheduling_info.may_self_abandon +6281 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +6282 && let Some(error_code) = space.pending.path_abandon.remove(&path_id) +6283 { +6284 let frame = frame::PathAbandon { +6285 path_id, +6286 error_code, +6287 }; +6288 builder.write_frame(frame, stats); +6289 +6290 // Consider remotely issued CIDs as retired now that we have sent this frame at +6291 // least once. +6292 self.remote_cids.remove(&path_id); +6293 } +6294 while space_id == SpaceId::Data +6295 && scheduling_info.may_send_data +6296 && frame::PathAbandon::SIZE_BOUND <= builder.frame_space_remaining() +6297 && let Some((abandoned_path_id, error_code)) = space.pending.path_abandon.pop_first() +6298 { +6299 let frame = frame::PathAbandon { +6300 path_id: abandoned_path_id, +6301 error_code, +6302 }; +6303 builder.write_frame(frame, stats); +6304 +6305 // Consider remotely issued CIDs as retired now that we have sent this frame at +6306 // least once. +6307 self.remote_cids.remove(&abandoned_path_id); +6308 } +6309 +6310 // OBSERVED_ADDR +6311 if !scheduling_info.is_abandoned +6312 && space_id == SpaceId::Data +6313 && path.pending.observed_address +6314 { +6315 let frame = +6316 frame::ObservedAddr::new(path.network_path.remote, self.next_observed_addr_seq_no); +6317 if builder.frame_space_remaining() > frame.size() { +6318 builder.write_frame(frame, stats); +6319 +6320 self.next_observed_addr_seq_no = self.next_observed_addr_seq_no.saturating_add(1u8); +6321 path.pending.observed_address = false; +6322 } +6323 } +6324 +6325 // CRYPTO +6326 while !is_0rtt +6327 && !scheduling_info.is_abandoned +6328 && scheduling_info.may_send_data +6329 && builder.frame_space_remaining() > frame::Crypto::SIZE_BOUND +6330 { +6331 let Some(mut frame) = space.pending.crypto.pop_front() else { +6332 break; +6333 }; +6334 +6335 // Calculate the maximum amount of crypto data we can store in the buffer. +6336 // Since the offset is known, we can reserve the exact size required to encode it. +6337 // For length we reserve 2bytes which allows to encode up to 2^14, +6338 // which is more than what fits into normally sized QUIC frames. +6339 let max_crypto_data_size = builder.frame_space_remaining() +6340 - 1 // Frame Type +6341 - VarInt::size(unsafe { VarInt::from_u64_unchecked(frame.offset) }) +6342 - 2; // Maximum encoded length for frame size, given we send less than 2^14 bytes +6343 +6344 let len = frame +6345 .data +6346 .len() +6347 .min(2usize.pow(14) - 1) +6348 .min(max_crypto_data_size); 6349 -6350 // MAX_PATH_ID -6351 if space_id == SpaceId::Data -6352 && !scheduling_info.is_abandoned -6353 && scheduling_info.may_send_data -6354 && space.pending.max_path_id -6355 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() -6356 { -6357 let frame = frame::MaxPathId(self.local_max_path_id); -6358 builder.write_frame(frame, stats); -6359 space.pending.max_path_id = false; -6360 } -6361 -6362 // PATHS_BLOCKED -6363 if space_id == SpaceId::Data -6364 && !scheduling_info.is_abandoned -6365 && scheduling_info.may_send_data -6366 && space.pending.paths_blocked -6367 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -6368 { -6369 let frame = frame::PathsBlocked(self.remote_max_path_id); -6370 builder.write_frame(frame, stats); -6371 space.pending.paths_blocked = false; -6372 } -6373 -6374 // PATH_CIDS_BLOCKED -6375 while space_id == SpaceId::Data -6376 && !scheduling_info.is_abandoned -6377 && scheduling_info.may_send_data -6378 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() -6379 { -6380 let Some(path_id) = space.pending.path_cids_blocked.pop_first() else { -6381 break; -6382 }; -6383 let next_seq = match self.remote_cids.get(&path_id) { -6384 Some(cid_queue) => VarInt(cid_queue.active_seq() + 1), -6385 None => VarInt(0), -6386 }; -6387 let frame = frame::PathCidsBlocked { path_id, next_seq }; -6388 builder.write_frame(frame, stats); -6389 } -6390 -6391 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS -6392 if space_id == SpaceId::Data -6393 && !scheduling_info.is_abandoned -6394 && scheduling_info.may_send_data -6395 { -6396 self.streams -6397 .write_control_frames(builder, &mut space.pending, stats); -6398 } -6399 -6400 // NEW_CONNECTION_ID -6401 let cid_len = self -6402 .local_cid_state -6403 .values() -6404 .map(|cid_state| cid_state.cid_len()) -6405 .max() -6406 .expect("some local CID state must exist"); -6407 let new_cid_size_bound = -6408 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); -6409 while !scheduling_info.is_abandoned -6410 && scheduling_info.may_send_data -6411 && builder.frame_space_remaining() > new_cid_size_bound +6350 let data = frame.data.split_to(len); +6351 let offset = frame.offset; +6352 let truncated = frame::Crypto { offset, data }; +6353 builder.write_frame(truncated, stats); +6354 +6355 if !frame.data.is_empty() { +6356 frame.offset += len as u64; +6357 space.pending.crypto.push_front(frame); +6358 } +6359 } +6360 +6361 // PATH_STATUS_AVAILABLE & PATH_STATUS_BACKUP +6362 while space_id == SpaceId::Data +6363 && !scheduling_info.is_abandoned +6364 && scheduling_info.may_send_data +6365 && frame::PathStatusAvailable::SIZE_BOUND <= builder.frame_space_remaining() +6366 { +6367 let Some(path_id) = space.pending.path_status.pop_first() else { +6368 break; +6369 }; +6370 let Some(path) = self.paths.get(&path_id).map(|path_state| &path_state.data) else { +6371 trace!(%path_id, "discarding queued path status for unknown path"); +6372 continue; +6373 }; +6374 +6375 let seq = path.status.seq(); +6376 match path.local_status() { +6377 PathStatus::Available => { +6378 let frame = frame::PathStatusAvailable { +6379 path_id, +6380 status_seq_no: seq, +6381 }; +6382 builder.write_frame(frame, stats); +6383 } +6384 PathStatus::Backup => { +6385 let frame = frame::PathStatusBackup { +6386 path_id, +6387 status_seq_no: seq, +6388 }; +6389 builder.write_frame(frame, stats); +6390 } +6391 } +6392 } +6393 +6394 // MAX_PATH_ID +6395 if space_id == SpaceId::Data +6396 && !scheduling_info.is_abandoned +6397 && scheduling_info.may_send_data +6398 && space.pending.max_path_id +6399 && frame::MaxPathId::SIZE_BOUND <= builder.frame_space_remaining() +6400 { +6401 let frame = frame::MaxPathId(self.local_max_path_id); +6402 builder.write_frame(frame, stats); +6403 space.pending.max_path_id = false; +6404 } +6405 +6406 // PATHS_BLOCKED +6407 if space_id == SpaceId::Data +6408 && !scheduling_info.is_abandoned +6409 && scheduling_info.may_send_data +6410 && frame::PathsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +6411 && let Some(remote_max_path_id) = space.pending.paths_blocked.take() 6412 { -6413 let Some(issued) = space.pending.new_cids.pop() else { -6414 break; -6415 }; -6416 // Path was discarded after this CID was queued, drop. -6417 let Some(cid_state) = self.local_cid_state.get(&issued.path_id) else { -6418 debug!( -6419 path = %issued.path_id, seq = issued.sequence, -6420 "dropping queued NEW_CONNECTION_ID for discarded path", -6421 ); -6422 continue; -6423 }; -6424 let retire_prior_to = cid_state.retire_prior_to(); -6425 -6426 let cid_path_id = match is_multipath_negotiated { -6427 true => Some(issued.path_id), -6428 false => { -6429 debug_assert_eq!(issued.path_id, PathId::ZERO); -6430 None -6431 } -6432 }; -6433 let frame = frame::NewConnectionId { -6434 path_id: cid_path_id, -6435 sequence: issued.sequence, -6436 retire_prior_to, -6437 id: issued.id, -6438 reset_token: issued.reset_token, -6439 }; -6440 builder.write_frame(frame, stats); -6441 } -6442 -6443 // RETIRE_CONNECTION_ID -6444 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); -6445 while !scheduling_info.is_abandoned -6446 && scheduling_info.may_send_data -6447 && builder.frame_space_remaining() > retire_cid_bound -6448 { -6449 let (path_id, sequence) = match space.pending.retire_cids.pop() { -6450 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), -6451 Some((path_id, seq)) => (Some(path_id), seq), -6452 None => break, -6453 }; -6454 let frame = frame::RetireConnectionId { path_id, sequence }; -6455 builder.write_frame(frame, stats); -6456 } -6457 -6458 // DATAGRAM -6459 let mut sent_datagrams = false; -6460 while !scheduling_info.is_abandoned -6461 && scheduling_info.may_send_data -6462 && builder.frame_space_remaining() > Datagram::SIZE_BOUND -6463 && space_id == SpaceId::Data -6464 { -6465 match self.datagrams.write(builder, stats) { -6466 true => { -6467 sent_datagrams = true; -6468 } -6469 false => break, -6470 } -6471 } -6472 if self.datagrams.send_blocked && sent_datagrams { -6473 self.events.push_back(Event::DatagramsUnblocked); -6474 self.datagrams.send_blocked = false; -6475 } -6476 -6477 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; -6478 -6479 // NEW_TOKEN -6480 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { -6481 while let Some(network_path) = space.pending.new_tokens.pop() { -6482 debug_assert_eq!(space_id, SpaceId::Data); -6483 let ConnectionSide::Server { server_config } = &self.side else { -6484 panic!("NEW_TOKEN frames should not be enqueued by clients"); -6485 }; -6486 -6487 if !network_path.is_probably_same_path(&path.network_path) { -6488 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only -6489 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN -6490 // frames upon an path change. Instead, when the new path becomes validated, -6491 // NEW_TOKEN frames may be enqueued for the new path instead. -6492 continue; -6493 } -6494 -6495 let token = Token::new( -6496 TokenPayload::Validation { -6497 ip: network_path.remote.ip(), -6498 issued: server_config.time_source.now(), -6499 }, -6500 &mut self.rng, -6501 ); -6502 let new_token = NewToken { -6503 token: token.encode(&*server_config.token_key).into(), -6504 }; -6505 -6506 if builder.frame_space_remaining() < new_token.size() { -6507 space.pending.new_tokens.push(network_path); -6508 break; -6509 } -6510 -6511 builder.write_frame(new_token, stats); -6512 builder.retransmits_mut().new_tokens.push(network_path); -6513 } +6413 let frame = frame::PathsBlocked(remote_max_path_id); +6414 builder.write_frame(frame, stats); +6415 } +6416 +6417 // PATH_CIDS_BLOCKED +6418 while space_id == SpaceId::Data +6419 && !scheduling_info.is_abandoned +6420 && scheduling_info.may_send_data +6421 && frame::PathCidsBlocked::SIZE_BOUND <= builder.frame_space_remaining() +6422 { +6423 let Some((path_id, next_seq)) = space.pending.path_cids_blocked.pop_first() else { +6424 break; +6425 }; +6426 let frame = frame::PathCidsBlocked { path_id, next_seq }; +6427 builder.write_frame(frame, stats); +6428 } +6429 +6430 // RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS +6431 if space_id == SpaceId::Data +6432 && !scheduling_info.is_abandoned +6433 && scheduling_info.may_send_data +6434 { +6435 self.streams +6436 .write_control_frames(builder, &mut space.pending, stats); +6437 } +6438 +6439 // NEW_CONNECTION_ID +6440 let cid_len = self +6441 .local_cid_state +6442 .values() +6443 .map(|cid_state| cid_state.cid_len()) +6444 .max() +6445 .expect("some local CID state must exist"); +6446 let new_cid_size_bound = +6447 frame::NewConnectionId::size_bound(is_multipath_negotiated, cid_len); +6448 while !scheduling_info.is_abandoned +6449 && scheduling_info.may_send_data +6450 && builder.frame_space_remaining() > new_cid_size_bound +6451 { +6452 let Some(issued) = space.pending.new_cids.pop() else { +6453 break; +6454 }; +6455 // Path was discarded after this CID was queued, drop. +6456 let Some(cid_state) = self.local_cid_state.get(&issued.path_id) else { +6457 debug!( +6458 path = %issued.path_id, seq = issued.sequence, +6459 "dropping queued NEW_CONNECTION_ID for discarded path", +6460 ); +6461 continue; +6462 }; +6463 let retire_prior_to = cid_state.retire_prior_to(); +6464 +6465 let cid_path_id = match is_multipath_negotiated { +6466 true => Some(issued.path_id), +6467 false => { +6468 debug_assert_eq!(issued.path_id, PathId::ZERO); +6469 None +6470 } +6471 }; +6472 let frame = frame::NewConnectionId { +6473 path_id: cid_path_id, +6474 sequence: issued.sequence, +6475 retire_prior_to, +6476 id: issued.id, +6477 reset_token: issued.reset_token, +6478 }; +6479 builder.write_frame(frame, stats); +6480 } +6481 +6482 // RETIRE_CONNECTION_ID +6483 let retire_cid_bound = frame::RetireConnectionId::size_bound(is_multipath_negotiated); +6484 while !scheduling_info.is_abandoned +6485 && scheduling_info.may_send_data +6486 && builder.frame_space_remaining() > retire_cid_bound +6487 { +6488 let (path_id, sequence) = match space.pending.retire_cids.pop() { +6489 Some((PathId::ZERO, seq)) if !is_multipath_negotiated => (None, seq), +6490 Some((path_id, seq)) => (Some(path_id), seq), +6491 None => break, +6492 }; +6493 let frame = frame::RetireConnectionId { path_id, sequence }; +6494 builder.write_frame(frame, stats); +6495 } +6496 +6497 // DATAGRAM +6498 let mut sent_datagrams = false; +6499 while !scheduling_info.is_abandoned +6500 && scheduling_info.may_send_data +6501 && builder.frame_space_remaining() > Datagram::SIZE_BOUND +6502 && space_id == SpaceId::Data +6503 { +6504 match self.datagrams.write(builder, stats) { +6505 true => { +6506 sent_datagrams = true; +6507 } +6508 false => break, +6509 } +6510 } +6511 if self.datagrams.send_blocked && sent_datagrams { +6512 self.events.push_back(Event::DatagramsUnblocked); +6513 self.datagrams.send_blocked = false; 6514 } 6515 -6516 // ADD_ADDRESS -6517 while space_id == SpaceId::Data -6518 && !scheduling_info.is_abandoned -6519 && scheduling_info.may_send_data -6520 && frame::AddAddress::SIZE_BOUND <= builder.frame_space_remaining() -6521 { -6522 if let Some(added_address) = space.pending.add_address.pop_last() { -6523 builder.write_frame(added_address, stats); -6524 } else { -6525 break; -6526 } -6527 } -6528 -6529 // REMOVE_ADDRESS -6530 while space_id == SpaceId::Data -6531 && !scheduling_info.is_abandoned -6532 && scheduling_info.may_send_data -6533 && frame::RemoveAddress::SIZE_BOUND <= builder.frame_space_remaining() -6534 { -6535 if let Some(removed_address) = space.pending.remove_address.pop_last() { -6536 builder.write_frame(removed_address, stats); -6537 } else { -6538 break; -6539 } -6540 } -6541 -6542 // STREAM -6543 if !scheduling_info.is_abandoned -6544 && scheduling_info.may_send_data -6545 && space_id == SpaceId::Data -6546 { -6547 self.streams -6548 .write_stream_frames(builder, self.config.send_fairness, stats); -6549 } -6550 } -6551 -6552 /// Write pending ACKs into a buffer -6553 fn populate_acks<'a, 'b>( -6554 now: Instant, -6555 receiving_ecn: bool, -6556 path_id: PathId, -6557 space_id: SpaceId, -6558 space: &mut PacketSpace, -6559 is_multipath_negotiated: bool, -6560 builder: &mut PacketBuilder<'a, 'b>, -6561 stats: &mut FrameStats, -6562 space_has_keys: bool, -6563 ) { -6564 // 0-RTT packets must never carry acks (which would have to be of handshake packets) -6565 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); -6566 -6567 debug_assert!( -6568 is_multipath_negotiated || path_id == PathId::ZERO, -6569 "Only PathId::ZERO allowed without multipath (have {path_id:?})" -6570 ); -6571 if is_multipath_negotiated { -6572 debug_assert!( -6573 space_id == SpaceId::Data || path_id == PathId::ZERO, -6574 "path acks must be sent in 1RTT space (have {space_id:?})" -6575 ); -6576 } -6577 -6578 let pns = space.for_path(path_id); -6579 let ranges = pns.pending_acks.ranges(); -6580 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); -6581 let ecn = if receiving_ecn { -6582 Some(&pns.ecn_counters) -6583 } else { -6584 None -6585 }; -6586 -6587 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; -6588 // TODO: This should come from `TransportConfig` if that gets configurable. -6589 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; -6590 let delay = delay_micros >> ack_delay_exp.into_inner(); -6591 -6592 if is_multipath_negotiated && space_id == SpaceId::Data { -6593 if !ranges.is_empty() { -6594 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); -6595 builder.write_frame(frame, stats); -6596 } -6597 } else { -6598 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); -6599 } -6600 } -6601 -6602 fn close_common(&mut self) { -6603 trace!("connection closed"); -6604 self.timers.reset(); -6605 } -6606 -6607 fn set_close_timer(&mut self, now: Instant) { -6608 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of -6609 // all paths. -6610 let pto_max = self.max_pto_for_space(self.highest_space); -6611 self.timers.set( -6612 Timer::Conn(ConnTimer::Close), -6613 now + 3 * pto_max, -6614 self.qlog.with_time(now), -6615 ); -6616 } -6617 -6618 /// Handle transport parameters received from the peer -6619 /// -6620 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the -6621 /// *packet into which the transport parameters arrived. -6622 fn handle_peer_params( -6623 &mut self, -6624 params: TransportParameters, -6625 local_cid: ConnectionId, -6626 remote_cid: ConnectionId, -6627 now: Instant, -6628 ) -> Result<(), TransportError> { -6629 if Some(self.original_remote_cid) != params.initial_src_cid -6630 || (self.side.is_client() -6631 && (Some(self.initial_dst_cid) != params.original_dst_cid -6632 || self.retry_src_cid != params.retry_src_cid)) -6633 { -6634 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( -6635 "CID authentication failure", -6636 )); -6637 } -6638 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { -6639 return Err(TransportError::PROTOCOL_VIOLATION( -6640 "multipath must not use zero-length CIDs", -6641 )); -6642 } -6643 -6644 self.set_peer_params(params); -6645 self.qlog.emit_peer_transport_params_received(self, now); -6646 -6647 Ok(()) -6648 } -6649 -6650 fn set_peer_params(&mut self, params: TransportParameters) { -6651 self.streams.set_params(&params); -6652 self.idle_timeout = -6653 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); -6654 trace!("negotiated max idle timeout {:?}", self.idle_timeout); -6655 -6656 if let Some(ref info) = params.preferred_address { -6657 // During the handshake PathId::ZERO exists. -6658 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { -6659 path_id: None, -6660 sequence: 1, -6661 id: info.connection_id, -6662 reset_token: info.stateless_reset_token, -6663 retire_prior_to: 0, -6664 }) -6665 .expect( -6666 "preferred address CID is the first received, and hence is guaranteed to be legal", -6667 ); -6668 let remote = self.path_data(PathId::ZERO).network_path.remote; -6669 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); -6670 } -6671 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); -6672 -6673 let mut multipath_enabled = false; -6674 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( -6675 self.config.get_initial_max_path_id(), -6676 params.initial_max_path_id, -6677 ) { -6678 // multipath is enabled, register the local and remote maximums -6679 self.local_max_path_id = local_max_path_id; -6680 self.remote_max_path_id = remote_max_path_id; -6681 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); -6682 debug!(%initial_max_path_id, "multipath negotiated"); -6683 multipath_enabled = true; -6684 } +6516 let path = &mut self.paths.get_mut(&path_id).expect("known path").data; +6517 +6518 // NEW_TOKEN +6519 if !scheduling_info.is_abandoned && scheduling_info.may_send_data { +6520 while let Some(network_path) = space.pending.new_tokens.pop() { +6521 debug_assert_eq!(space_id, SpaceId::Data); +6522 let ConnectionSide::Server { server_config } = &self.side else { +6523 panic!("NEW_TOKEN frames should not be enqueued by clients"); +6524 }; +6525 +6526 if !network_path.is_probably_same_path(&path.network_path) { +6527 // NEW_TOKEN frames contain tokens bound to a client's IP address, and are only +6528 // useful if used from the same IP address. Thus, we abandon enqueued NEW_TOKEN +6529 // frames upon an path change. Instead, when the new path becomes validated, +6530 // NEW_TOKEN frames may be enqueued for the new path instead. +6531 continue; +6532 } +6533 +6534 let token = Token::new( +6535 TokenPayload::Validation { +6536 ip: network_path.remote.ip(), +6537 issued: server_config.time_source.now(), +6538 }, +6539 &mut self.rng, +6540 ); +6541 let new_token = NewToken { +6542 token: token.encode(&*server_config.token_key).into(), +6543 }; +6544 +6545 if builder.frame_space_remaining() < new_token.size() { +6546 space.pending.new_tokens.push(network_path); +6547 break; +6548 } +6549 +6550 builder.write_frame(new_token, stats); +6551 builder.retransmits_mut().new_tokens.push(network_path); +6552 } +6553 } +6554 +6555 // STREAM +6556 if !scheduling_info.is_abandoned +6557 && scheduling_info.may_send_data +6558 && space_id == SpaceId::Data +6559 { +6560 self.streams +6561 .write_stream_frames(builder, self.config.send_fairness, stats); +6562 } +6563 } +6564 +6565 /// Write pending ACKs into a buffer +6566 fn populate_acks<'a, 'b>( +6567 now: Instant, +6568 receiving_ecn: bool, +6569 path_id: PathId, +6570 space_id: SpaceId, +6571 space: &mut PacketSpace, +6572 is_multipath_negotiated: bool, +6573 builder: &mut PacketBuilder<'a, 'b>, +6574 stats: &mut FrameStats, +6575 space_has_keys: bool, +6576 ) { +6577 // 0-RTT packets must never carry acks (which would have to be of handshake packets) +6578 debug_assert!(space_has_keys, "tried to send ACK in 0-RTT"); +6579 +6580 debug_assert!( +6581 is_multipath_negotiated || path_id == PathId::ZERO, +6582 "Only PathId::ZERO allowed without multipath (have {path_id:?})" +6583 ); +6584 if is_multipath_negotiated { +6585 debug_assert!( +6586 space_id == SpaceId::Data || path_id == PathId::ZERO, +6587 "path acks must be sent in 1RTT space (have {space_id:?})" +6588 ); +6589 } +6590 +6591 let pns = space.for_path(path_id); +6592 let ranges = pns.pending_acks.ranges(); +6593 debug_assert!(!ranges.is_empty(), "can not send empty ACK range"); +6594 let ecn = if receiving_ecn { +6595 Some(&pns.ecn_counters) +6596 } else { +6597 None +6598 }; +6599 +6600 let delay_micros = pns.pending_acks.ack_delay(now).as_micros() as u64; +6601 // TODO: This should come from `TransportConfig` if that gets configurable. +6602 let ack_delay_exp = TransportParameters::default().ack_delay_exponent; +6603 let delay = delay_micros >> ack_delay_exp.into_inner(); +6604 +6605 if is_multipath_negotiated && space_id == SpaceId::Data { +6606 if !ranges.is_empty() { +6607 let frame = frame::PathAck::encoder(path_id, delay, ranges, ecn); +6608 builder.write_frame(frame, stats); +6609 } +6610 } else { +6611 builder.write_frame(frame::Ack::encoder(delay, ranges, ecn), stats); +6612 } +6613 } +6614 +6615 fn close_common(&mut self) { +6616 trace!("connection closed"); +6617 self.timers.reset(); +6618 } +6619 +6620 fn set_close_timer(&mut self, now: Instant) { +6621 // QUIC-MULTIPATH § 2.6 Connection Closure: draining for 3*PTO using the max PTO of +6622 // all paths. +6623 let pto_max = self.max_pto_for_space(self.highest_space); +6624 self.timers.set( +6625 Timer::Conn(ConnTimer::Close), +6626 now + 3 * pto_max, +6627 self.qlog.with_time(now), +6628 ); +6629 } +6630 +6631 /// Handle transport parameters received from the peer +6632 /// +6633 /// *remote_cid* and *local_cid* are the source and destination CIDs respectively of the +6634 /// *packet into which the transport parameters arrived. +6635 fn handle_peer_params( +6636 &mut self, +6637 params: TransportParameters, +6638 local_cid: ConnectionId, +6639 remote_cid: ConnectionId, +6640 now: Instant, +6641 ) -> Result<(), TransportError> { +6642 if Some(self.original_remote_cid) != params.initial_src_cid +6643 || (self.side.is_client() +6644 && (Some(self.initial_dst_cid) != params.original_dst_cid +6645 || self.retry_src_cid != params.retry_src_cid)) +6646 { +6647 return Err(TransportError::TRANSPORT_PARAMETER_ERROR( +6648 "CID authentication failure", +6649 )); +6650 } +6651 if params.initial_max_path_id.is_some() && (local_cid.is_empty() || remote_cid.is_empty()) { +6652 return Err(TransportError::PROTOCOL_VIOLATION( +6653 "multipath must not use zero-length CIDs", +6654 )); +6655 } +6656 +6657 self.set_peer_params(params); +6658 self.qlog.emit_peer_transport_params_received(self, now); +6659 +6660 Ok(()) +6661 } +6662 +6663 fn set_peer_params(&mut self, params: TransportParameters) { +6664 self.streams.set_params(&params); +6665 self.idle_timeout = +6666 negotiate_max_idle_timeout(self.config.max_idle_timeout, Some(params.max_idle_timeout)); +6667 trace!("negotiated max idle timeout {:?}", self.idle_timeout); +6668 +6669 if let Some(ref info) = params.preferred_address { +6670 // During the handshake PathId::ZERO exists. +6671 self.remote_cids.get_mut(&PathId::ZERO).expect("not yet abandoned").insert(frame::NewConnectionId { +6672 path_id: None, +6673 sequence: 1, +6674 id: info.connection_id, +6675 reset_token: info.stateless_reset_token, +6676 retire_prior_to: 0, +6677 }) +6678 .expect( +6679 "preferred address CID is the first received, and hence is guaranteed to be legal", +6680 ); +6681 let remote = self.path_data(PathId::ZERO).network_path.remote; +6682 self.set_reset_token(PathId::ZERO, remote, info.stateless_reset_token); +6683 } +6684 self.ack_frequency.peer_max_ack_delay = get_max_ack_delay(&params); 6685 -6686 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = -6687 self.config -6688 .max_remote_nat_traversal_addresses -6689 .zip(params.max_remote_nat_traversal_addresses) -6690 { -6691 if multipath_enabled { -6692 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); -6693 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); -6694 self.n0_nat_traversal = n0_nat_traversal::State::new( -6695 max_remote_addresses, -6696 max_local_addresses, -6697 self.side(), -6698 ); -6699 debug!( -6700 %max_remote_addresses, %max_local_addresses, -6701 "n0's nat traversal negotiated" -6702 ); -6703 } else { -6704 debug!("n0 nat traversal enabled for both endpoints, but multipath is missing") -6705 } -6706 } -6707 -6708 self.peer_params = params; -6709 let peer_max_udp_payload_size = -6710 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); -6711 self.path_data_mut(PathId::ZERO) -6712 .mtud -6713 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); -6714 } -6715 -6716 /// Decrypts a packet, returning the packet number on success -6717 fn decrypt_packet( -6718 &mut self, -6719 now: Instant, -6720 path_id: PathId, -6721 packet: &mut Packet, -6722 ) -> Result<Option<u64>, Option<TransportError>> { -6723 let result = self -6724 .crypto_state -6725 .decrypt_packet_body(packet, path_id, &self.spaces)?; -6726 -6727 let Some(result) = result else { -6728 return Ok(None); -6729 }; -6730 -6731 if result.outgoing_key_update_acked -6732 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() -6733 { -6734 prev.end_packet = Some((result.packet_number, now)); -6735 self.set_key_discard_timer(now, packet.header.space()); -6736 } -6737 -6738 if result.incoming_key_update { -6739 trace!("key update authenticated"); -6740 self.crypto_state -6741 .update_keys(Some((result.packet_number, now)), true); -6742 self.set_key_discard_timer(now, packet.header.space()); -6743 } -6744 -6745 Ok(Some(result.packet_number)) -6746 } -6747 -6748 fn peer_supports_ack_frequency(&self) -> bool { -6749 self.peer_params.min_ack_delay.is_some() -6750 } -6751 -6752 /// Send an IMMEDIATE_ACK frame to the remote endpoint -6753 /// -6754 /// According to the spec, this will result in an error if the remote endpoint does not support -6755 /// the Acknowledgement Frequency extension -6756 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { -6757 debug_assert_eq!( -6758 self.highest_space, -6759 SpaceKind::Data, -6760 "immediate ack must be written in the data space" -6761 ); -6762 self.spaces[SpaceId::Data] -6763 .for_path(path_id) -6764 .immediate_ack_pending = true; +6686 let mut multipath_enabled = false; +6687 if let (Some(local_max_path_id), Some(remote_max_path_id)) = ( +6688 self.config.get_initial_max_path_id(), +6689 params.initial_max_path_id, +6690 ) { +6691 // multipath is enabled, register the local and remote maximums +6692 self.local_max_path_id = local_max_path_id; +6693 self.remote_max_path_id = remote_max_path_id; +6694 let initial_max_path_id = local_max_path_id.min(remote_max_path_id); +6695 debug!(%initial_max_path_id, "multipath negotiated"); +6696 multipath_enabled = true; +6697 } +6698 +6699 if let Some((max_locally_allowed_remote_addresses, max_remotely_allowed_remote_addresses)) = +6700 self.config +6701 .max_remote_nat_traversal_addresses +6702 .zip(params.max_remote_nat_traversal_addresses) +6703 { +6704 if multipath_enabled { +6705 let max_local_addresses = max_remotely_allowed_remote_addresses.get(); +6706 let max_remote_addresses = max_locally_allowed_remote_addresses.get(); +6707 self.n0_nat_traversal = n0_nat_traversal::State::new( +6708 max_remote_addresses, +6709 max_local_addresses, +6710 self.side(), +6711 ); +6712 debug!( +6713 %max_remote_addresses, %max_local_addresses, +6714 "n0's nat traversal negotiated" +6715 ); +6716 } else { +6717 debug!("n0 nat traversal enabled for both endpoints, but multipath is missing") +6718 } +6719 } +6720 +6721 self.peer_params = params; +6722 let peer_max_udp_payload_size = +6723 u16::try_from(self.peer_params.max_udp_payload_size.into_inner()).unwrap_or(u16::MAX); +6724 let address_discovery_negotiated = self +6725 .config +6726 .address_discovery_role +6727 .should_report(&self.peer_params.address_discovery_role); +6728 +6729 let path = self.path_data_mut(PathId::ZERO); +6730 path.pending.observed_address = address_discovery_negotiated; +6731 path.mtud +6732 .on_peer_max_udp_payload_size_received(peer_max_udp_payload_size); +6733 } +6734 +6735 /// Decrypts a packet, returning the packet number on success +6736 fn decrypt_packet( +6737 &mut self, +6738 now: Instant, +6739 path_id: PathId, +6740 packet: &mut Packet, +6741 ) -> Result<Option<u64>, Option<TransportError>> { +6742 let result = self +6743 .crypto_state +6744 .decrypt_packet_body(packet, path_id, &self.spaces)?; +6745 +6746 let Some(result) = result else { +6747 return Ok(None); +6748 }; +6749 +6750 if result.outgoing_key_update_acked +6751 && let Some(prev) = self.crypto_state.prev_crypto.as_mut() +6752 { +6753 prev.end_packet = Some((result.packet_number, now)); +6754 self.set_key_discard_timer(now, packet.header.space()); +6755 } +6756 +6757 if result.incoming_key_update { +6758 trace!("key update authenticated"); +6759 self.crypto_state +6760 .update_keys(Some((result.packet_number, now)), true); +6761 self.set_key_discard_timer(now, packet.header.space()); +6762 } +6763 +6764 Ok(Some(result.packet_number)) 6765 } 6766 -6767 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests -6768 #[cfg(test)] -6769 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { -6770 let ConnectionEventInner::Datagram(DatagramConnectionEvent { -6771 path_id, -6772 first_decode, -6773 remaining, -6774 .. -6775 }) = &event.0 -6776 else { -6777 return None; -6778 }; -6779 -6780 if remaining.is_some() { -6781 panic!("Packets should never be coalesced in tests"); -6782 } -6783 -6784 let decrypted_header = self -6785 .crypto_state -6786 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; -6787 -6788 let mut packet = decrypted_header.packet?; -6789 self.crypto_state -6790 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) -6791 .ok()?; -6792 -6793 Some(packet.payload.to_vec()) -6794 } -6795 -6796 /// The number of bytes of packets containing retransmittable frames that have not been -6797 /// acknowledged or declared lost. -6798 #[cfg(test)] -6799 pub(crate) fn bytes_in_flight(&self) -> u64 { -6800 // TODO(@divma): consider including for multipath? -6801 self.path_data(PathId::ZERO).in_flight.bytes -6802 } -6803 -6804 /// Number of bytes worth of non-ack-only packets that may be sent -6805 #[cfg(test)] -6806 pub(crate) fn congestion_window(&self) -> u64 { -6807 let path = self.path_data(PathId::ZERO); -6808 path.congestion -6809 .window() -6810 .saturating_sub(path.in_flight.bytes) -6811 } -6812 -6813 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running -6814 #[cfg(test)] -6815 pub(crate) fn is_idle(&self) -> bool { -6816 let current_timers = self.timers.values(); -6817 current_timers -6818 .into_iter() -6819 .filter(|(timer, _)| { -6820 !matches!( -6821 timer, -6822 Timer::Conn(ConnTimer::KeepAlive) -6823 | Timer::PerPath(_, PathTimer::PathKeepAlive) -6824 | Timer::Conn(ConnTimer::PushNewCid) -6825 | Timer::Conn(ConnTimer::KeyDiscard) -6826 ) -6827 }) -6828 .min_by_key(|(_, time)| *time) -6829 .is_none_or(|(timer, _)| { -6830 matches!( -6831 timer, -6832 Timer::Conn(ConnTimer::Idle) | Timer::PerPath(_, PathTimer::PathIdle) -6833 ) -6834 }) -6835 } -6836 -6837 /// Whether explicit congestion notification is in use on outgoing packets. -6838 #[cfg(test)] -6839 pub(crate) fn using_ecn(&self) -> bool { -6840 self.path_data(PathId::ZERO).sending_ecn -6841 } -6842 -6843 /// The number of received bytes in the current path -6844 #[cfg(test)] -6845 pub(crate) fn total_recvd(&self) -> u64 { -6846 self.path_data(PathId::ZERO).total_recvd -6847 } -6848 -6849 #[cfg(test)] -6850 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { -6851 self.local_cid_state -6852 .get(&PathId::ZERO) -6853 .unwrap() -6854 .active_seq() -6855 } -6856 -6857 #[cfg(test)] -6858 #[track_caller] -6859 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { -6860 self.local_cid_state -6861 .get(&PathId(path_id)) -6862 .unwrap() -6863 .active_seq() -6864 } -6865 -6866 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame -6867 /// with updated `retire_prior_to` field set to `v` -6868 #[cfg(test)] -6869 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { -6870 let n = self -6871 .local_cid_state -6872 .get_mut(&PathId::ZERO) -6873 .unwrap() -6874 .assign_retire_seq(v); -6875 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events -6876 self.endpoint_events -6877 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); -6878 } -6879 -6880 /// Check the current active remote CID sequence for `PathId::ZERO` -6881 #[cfg(test)] -6882 pub(crate) fn active_remote_cid_seq(&self) -> u64 { -6883 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() -6884 } -6885 -6886 /// Returns the detected maximum udp payload size for the current path +6767 fn peer_supports_ack_frequency(&self) -> bool { +6768 self.peer_params.min_ack_delay.is_some() +6769 } +6770 +6771 /// Send an IMMEDIATE_ACK frame to the remote endpoint +6772 /// +6773 /// According to the spec, this will result in an error if the remote endpoint does not support +6774 /// the Acknowledgement Frequency extension +6775 pub(crate) fn immediate_ack(&mut self, path_id: PathId) { +6776 debug_assert_eq!( +6777 self.highest_space, +6778 SpaceKind::Data, +6779 "immediate ack must be written in the data space" +6780 ); +6781 self.spaces[SpaceId::Data] +6782 .for_path(path_id) +6783 .pending_immediate_ack = true; +6784 } +6785 +6786 /// Decodes a packet, returning its decrypted payload, so it can be inspected in tests +6787 #[cfg(test)] +6788 pub(crate) fn decode_packet(&self, event: &ConnectionEvent) -> Option<Vec<u8>> { +6789 let ConnectionEventInner::Datagram(DatagramConnectionEvent { +6790 path_id, +6791 first_decode, +6792 remaining, +6793 .. +6794 }) = &event.0 +6795 else { +6796 return None; +6797 }; +6798 +6799 if remaining.is_some() { +6800 panic!("Packets should never be coalesced in tests"); +6801 } +6802 +6803 let decrypted_header = self +6804 .crypto_state +6805 .unprotect_header(first_decode.clone(), self.peer_params.stateless_reset_token)?; +6806 +6807 let mut packet = decrypted_header.packet?; +6808 self.crypto_state +6809 .decrypt_packet_body(&mut packet, *path_id, &self.spaces) +6810 .ok()?; +6811 +6812 Some(packet.payload.to_vec()) +6813 } +6814 +6815 /// The number of bytes of packets containing retransmittable frames that have not been +6816 /// acknowledged or declared lost. +6817 #[cfg(test)] +6818 pub(crate) fn bytes_in_flight(&self) -> u64 { +6819 // TODO(@divma): consider including for multipath? +6820 self.path_data(PathId::ZERO).in_flight.bytes +6821 } +6822 +6823 /// Number of bytes worth of non-ack-only packets that may be sent +6824 #[cfg(test)] +6825 pub(crate) fn congestion_window(&self) -> u64 { +6826 let path = self.path_data(PathId::ZERO); +6827 path.congestion +6828 .window() +6829 .saturating_sub(path.in_flight.bytes) +6830 } +6831 +6832 /// Whether no timers but keepalive, idle, rtt, pushnewcid, and key discard are running +6833 #[cfg(test)] +6834 pub(crate) fn is_idle(&self) -> bool { +6835 let current_timers = self.timers.values(); +6836 current_timers +6837 .into_iter() +6838 .filter(|(timer, _)| { +6839 !matches!( +6840 timer, +6841 Timer::Conn(ConnTimer::KeepAlive) +6842 | Timer::PerPath(_, PathTimer::PathKeepAlive) +6843 | Timer::Conn(ConnTimer::PushNewCid) +6844 | Timer::Conn(ConnTimer::KeyDiscard) +6845 ) +6846 }) +6847 .min_by_key(|(_, time)| *time) +6848 .is_none_or(|(timer, _)| { +6849 matches!( +6850 timer, +6851 Timer::Conn(ConnTimer::Idle) | Timer::PerPath(_, PathTimer::PathIdle) +6852 ) +6853 }) +6854 } +6855 +6856 /// Whether explicit congestion notification is in use on outgoing packets. +6857 #[cfg(test)] +6858 pub(crate) fn using_ecn(&self) -> bool { +6859 self.path_data(PathId::ZERO).sending_ecn +6860 } +6861 +6862 /// The number of received bytes in the current path +6863 #[cfg(test)] +6864 pub(crate) fn total_recvd(&self) -> u64 { +6865 self.path_data(PathId::ZERO).total_recvd +6866 } +6867 +6868 #[cfg(test)] +6869 pub(crate) fn active_local_cid_seq(&self) -> (u64, u64) { +6870 self.local_cid_state +6871 .get(&PathId::ZERO) +6872 .unwrap() +6873 .active_seq() +6874 } +6875 +6876 #[cfg(test)] +6877 #[track_caller] +6878 pub(crate) fn active_local_path_cid_seq(&self, path_id: u32) -> (u64, u64) { +6879 self.local_cid_state +6880 .get(&PathId(path_id)) +6881 .unwrap() +6882 .active_seq() +6883 } +6884 +6885 /// Instruct the peer to replace previously issued CIDs by sending a NEW_CONNECTION_ID frame +6886 /// with updated `retire_prior_to` field set to `v` 6887 #[cfg(test)] -6888 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { -6889 self.path_data(path_id).current_mtu() -6890 } -6891 -6892 /// Triggers path validation on all paths -6893 #[cfg(test)] -6894 pub(crate) fn trigger_path_validation(&mut self) { -6895 for path in self.paths.values_mut() { -6896 path.data.pending_on_path_challenge = true; -6897 } -6898 } -6899 -6900 /// Simulates a protocol violation error for test purposes. -6901 #[cfg(test)] -6902 pub fn simulate_protocol_violation(&mut self, now: Instant) { -6903 if !self.state.is_closed() { -6904 self.state -6905 .move_to_closed(TransportError::PROTOCOL_VIOLATION("simulated violation")); -6906 self.close_common(); -6907 if !self.state.is_drained() { -6908 self.set_close_timer(now); -6909 } -6910 self.connection_close_pending = true; -6911 } -6912 } -6913 -6914 /// Whether we have on-path 1-RTT data to send. -6915 /// -6916 /// This checks for frames that can only be sent in the data space (1-RTT): -6917 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. -6918 /// - Pending PATH_RESPONSE frames. -6919 /// - Pending data to send in STREAM frames. -6920 /// - Pending DATAGRAM frames to send. -6921 /// -6922 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that -6923 /// may need to be sent. -6924 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { -6925 let space_specific = self.paths.get(&path_id).is_some_and(|path| { -6926 path.data.pending_on_path_challenge || !path.data.path_responses.is_empty() -6927 }); -6928 -6929 // Stream control frames are checked in PacketSpace::can_send, only check data here. -6930 let other = self.streams.can_send_stream_data() -6931 || self -6932 .datagrams -6933 .outgoing -6934 .front() -6935 .is_some_and(|x| x.size(true) <= max_size); -6936 -6937 // All `false` fields are set in PacketSpace::can_send. -6938 SendableFrames { -6939 acks: false, -6940 close: false, -6941 space_specific, -6942 other, -6943 } -6944 } -6945 -6946 /// Terminate the connection instantly, without sending a close packet -6947 fn kill(&mut self, reason: ConnectionError) { -6948 self.close_common(); -6949 self.state.move_to_drained(Some(reason)); -6950 // move_to_drained checks that we were never in drained before, so we -6951 // never sent a `Drained` event before (it's illegal to send more events after drained). -6952 self.endpoint_events.push_back(EndpointEventInner::Drained); -6953 } -6954 -6955 /// Storage size required for the largest packet that can be transmitted on all currently -6956 /// available paths -6957 /// -6958 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. -6959 /// -6960 /// When multipath is enabled, this value is the minimum MTU across all available paths. -6961 pub fn current_mtu(&self) -> u16 { -6962 self.paths -6963 .iter() -6964 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) -6965 .map(|(_path_id, path_state)| path_state.data.current_mtu()) -6966 .min() -6967 .unwrap_or(INITIAL_MTU) -6968 } -6969 -6970 /// Size of non-frame data for a 1-RTT packet -6971 /// -6972 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are -6973 /// frames. Changes if the length of the remote connection ID changes, which is expected to be -6974 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in -6975 /// latency and packet loss. -6976 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { -6977 let pn_len = PacketNumber::new( -6978 pn, -6979 self.spaces[SpaceId::Data] -6980 .for_path(path) -6981 .largest_acked_packet_pn -6982 .unwrap_or(0), -6983 ) -6984 .len(); -6985 -6986 // 1 byte for flags -6987 1 + self -6988 .remote_cids -6989 .get(&path) -6990 .map(|cids| cids.active().len()) -6991 .unwrap_or(20) // Max CID len in QUIC v1 -6992 + pn_len -6993 + self.tag_len_1rtt() -6994 } -6995 -6996 fn predict_1rtt_overhead_no_pn(&self) -> usize { -6997 let pn_len = 4; -6998 -6999 let cid_len = self -7000 .remote_cids -7001 .values() -7002 .map(|cids| cids.active().len()) -7003 .max() -7004 .unwrap_or(20); // Max CID len in QUIC v1 -7005 -7006 // 1 byte for flags -7007 1 + cid_len + pn_len + self.tag_len_1rtt() -7008 } -7009 -7010 fn tag_len_1rtt(&self) -> usize { -7011 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys -7012 let packet_crypto = self -7013 .crypto_state -7014 .encryption_keys(SpaceKind::Data, self.side.side()) -7015 .map(|(_header, packet, _level)| packet); -7016 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of -7017 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, -7018 // but that would needlessly prevent sending datagrams during 0-RTT. -7019 packet_crypto.map_or(16, |x| x.tag_len()) -7020 } -7021 -7022 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate -7023 fn on_path_validated(&mut self, path_id: PathId) { -7024 self.path_data_mut(path_id).validated = true; -7025 let ConnectionSide::Server { server_config } = &self.side else { -7026 return; -7027 }; -7028 let network_path = self.path_data(path_id).network_path; -7029 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; -7030 new_tokens.clear(); -7031 for _ in 0..server_config.validation_token.sent { -7032 new_tokens.push(network_path); -7033 } -7034 } -7035 -7036 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP -7037 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { -7038 if let Some(path) = self.paths.get_mut(&path_id) { -7039 path.data.status.remote_update(status, status_seq_no); -7040 } else { -7041 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); -7042 } -7043 self.events.push_back( -7044 PathEvent::RemoteStatus { -7045 id: path_id, -7046 status, -7047 } -7048 .into(), -7049 ); -7050 } -7051 -7052 /// Returns the maximum [`PathId`] to be used for sending in this connection. -7053 /// -7054 /// This is calculated as minimum between the local and remote's maximums when multipath is -7055 /// enabled, or `None` when disabled. -7056 /// -7057 /// For data that's received, we should use [`Self::local_max_path_id`] instead. -7058 /// The reasoning is that the remote might already have updated to its own newer -7059 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. -7060 fn max_path_id(&self) -> Option<PathId> { -7061 if self.is_multipath_negotiated() { -7062 Some(self.remote_max_path_id.min(self.local_max_path_id)) -7063 } else { -7064 None -7065 } -7066 } -7067 -7068 /// Returns whether this connection has a socket that supports IPv6. -7069 /// -7070 /// TODO(matheus23): This is related to noq endpoint state's `ipv6` bool. We should move that info -7071 /// here instead of trying to hack around not knowing it exactly. -7072 fn is_ipv6(&self) -> bool { -7073 self.paths -7074 .values() -7075 .any(|p| p.data.network_path.remote.is_ipv6()) -7076 } -7077 -7078 /// Add addresses the local endpoint considers are reachable for nat traversal. -7079 pub fn add_nat_traversal_address( -7080 &mut self, -7081 address: SocketAddr, -7082 ) -> Result<(), n0_nat_traversal::Error> { -7083 if let Some(added) = self.n0_nat_traversal.add_local_address(address)? { -7084 self.spaces[SpaceId::Data].pending.add_address.insert(added); -7085 }; -7086 Ok(()) -7087 } -7088 -7089 /// Removes an address the endpoing no longer considers reachable for nat traversal -7090 /// -7091 /// Addresses not present in the set will be silently ignored. -7092 pub fn remove_nat_traversal_address( -7093 &mut self, -7094 address: SocketAddr, -7095 ) -> Result<(), n0_nat_traversal::Error> { -7096 if let Some(removed) = self.n0_nat_traversal.remove_local_address(address)? { -7097 self.spaces[SpaceId::Data] -7098 .pending -7099 .remove_address -7100 .insert(removed); -7101 } -7102 Ok(()) -7103 } -7104 -7105 /// Get the current local nat traversal addresses -7106 pub fn get_local_nat_traversal_addresses( -7107 &self, -7108 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7109 self.n0_nat_traversal.get_local_nat_traversal_addresses() -7110 } -7111 -7112 /// Get the currently advertised nat traversal addresses by the server -7113 pub fn get_remote_nat_traversal_addresses( -7114 &self, -7115 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7116 Ok(self -7117 .n0_nat_traversal -7118 .client_side()? -7119 .get_remote_nat_traversal_addresses()) -7120 } -7121 -7122 /// Initiates a new nat traversal round -7123 /// -7124 /// A nat traversal round involves advertising the client's local addresses in -7125 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new -7126 /// round is initiated, the previous one is cancelled. -7127 /// -7128 /// For all probes that succeed, if any, a new path will be opened on the successful -7129 /// 4-tuple. -7130 /// -7131 /// Returns the server addresses that are now being probed. If addresses fail due to -7132 /// spurious errors, these might succeed later and not be returned in this set. -7133 pub fn initiate_nat_traversal_round( -7134 &mut self, -7135 now: Instant, +6888 pub(crate) fn rotate_local_cid(&mut self, v: u64, now: Instant) { +6889 let n = self +6890 .local_cid_state +6891 .get_mut(&PathId::ZERO) +6892 .unwrap() +6893 .assign_retire_seq(v); +6894 debug_assert!(!self.state.is_drained()); // requirement for endpoint_events +6895 self.endpoint_events +6896 .push_back(EndpointEventInner::NeedIdentifiers(PathId::ZERO, now, n)); +6897 } +6898 +6899 /// Check the current active remote CID sequence for `PathId::ZERO` +6900 #[cfg(test)] +6901 pub(crate) fn active_remote_cid_seq(&self) -> u64 { +6902 self.remote_cids.get(&PathId::ZERO).unwrap().active_seq() +6903 } +6904 +6905 /// Returns the detected maximum udp payload size for the current path +6906 #[cfg(test)] +6907 pub(crate) fn path_mtu(&self, path_id: PathId) -> u16 { +6908 self.path_data(path_id).current_mtu() +6909 } +6910 +6911 /// Triggers path validation on all paths +6912 #[cfg(test)] +6913 pub(crate) fn trigger_path_validation(&mut self) { +6914 for path in self.paths.values_mut() { +6915 path.data.pending_challenge = true; +6916 } +6917 } +6918 +6919 /// Simulates a protocol violation error for test purposes. +6920 #[cfg(test)] +6921 pub fn simulate_protocol_violation(&mut self, now: Instant) { +6922 if !self.state.is_closed() { +6923 self.state +6924 .move_to_closed(TransportError::PROTOCOL_VIOLATION("simulated violation")); +6925 self.close_common(); +6926 if !self.state.is_drained() { +6927 self.set_close_timer(now); +6928 } +6929 self.connection_close_pending = true; +6930 } +6931 } +6932 +6933 /// Whether we have **on-path** 1-RTT data to send. +6934 /// +6935 /// This checks for frames that can only be sent in the data space (1-RTT): +6936 /// - Pending PATH_CHALLENGE frames on the active and previous path if just migrated. +6937 /// - Pending PATH_RESPONSE frames. +6938 /// - Pending data to send in STREAM frames. +6939 /// - Pending DATAGRAM frames to send. +6940 /// +6941 /// See also [`PacketSpace::can_send`] which keeps track of all other frame types that +6942 /// may need to be sent. +6943 fn can_send_1rtt(&self, path_id: PathId, max_size: usize) -> SendableFrames { +6944 let network_path = self.path_data(path_id).network_path; +6945 let space_specific = self +6946 .paths +6947 .get(&path_id) +6948 .is_some_and(|path| path.data.pending_challenge || !path.data.pending.is_empty()) +6949 || self.spaces[SpaceKind::Data] +6950 .number_spaces +6951 .get(&path_id) +6952 .is_some_and(|pns| pns.pending_path_responses.has_pending_on_path(network_path)); +6953 +6954 // Stream control frames are checked in PacketSpace::can_send, only check data here. +6955 let other = self.streams.can_send_stream_data() +6956 || self +6957 .datagrams +6958 .outgoing +6959 .front() +6960 .is_some_and(|x| x.size(true) <= max_size); +6961 +6962 // All `false` fields are set in PacketSpace::can_send. +6963 SendableFrames { +6964 acks: false, +6965 close: false, +6966 space_specific, +6967 other, +6968 } +6969 } +6970 +6971 /// Terminate the connection instantly, without sending a close packet +6972 fn kill(&mut self, reason: ConnectionError) { +6973 self.close_common(); +6974 let was_draining = self.state.move_to_drained(Some(reason)); +6975 if !was_draining { +6976 self.endpoint_events.push_back(EndpointEventInner::Draining); +6977 } +6978 // move_to_drained checks that we were never in drained before, so we +6979 // never sent a `Drained` event before (it's illegal to send more events after drained). +6980 self.endpoint_events.push_back(EndpointEventInner::Drained); +6981 } +6982 +6983 /// Storage size required for the largest packet that can be transmitted on all currently +6984 /// available paths +6985 /// +6986 /// Buffers passed to [`Connection::poll_transmit`] should be at least this large. +6987 /// +6988 /// When multipath is enabled, this value is the minimum MTU across all available paths. +6989 pub fn current_mtu(&self) -> u16 { +6990 self.paths +6991 .iter() +6992 .filter(|&(path_id, _path_state)| !self.abandoned_paths.contains(path_id)) +6993 .map(|(_path_id, path_state)| path_state.data.current_mtu()) +6994 .min() +6995 .unwrap_or(INITIAL_MTU) +6996 } +6997 +6998 /// Size of non-frame data for a 1-RTT packet +6999 /// +7000 /// Quantifies space consumed by the QUIC header and AEAD tag. All other bytes in a packet are +7001 /// frames. Changes if the length of the remote connection ID changes, which is expected to be +7002 /// rare. If `pn` is specified, may additionally change unpredictably due to variations in +7003 /// latency and packet loss. +7004 fn predict_1rtt_overhead(&mut self, pn: u64, path: PathId) -> usize { +7005 let pn_len = PacketNumber::new( +7006 pn, +7007 self.spaces[SpaceId::Data] +7008 .for_path(path) +7009 .largest_acked_packet_pn +7010 .unwrap_or(0), +7011 ) +7012 .len(); +7013 +7014 // 1 byte for flags +7015 1 + self +7016 .remote_cids +7017 .get(&path) +7018 .map(|cids| cids.active().len()) +7019 .unwrap_or(20) // Max CID len in QUIC v1 +7020 + pn_len +7021 + self.tag_len_1rtt() +7022 } +7023 +7024 fn predict_1rtt_overhead_no_pn(&self) -> usize { +7025 let pn_len = 4; +7026 +7027 let cid_len = self +7028 .remote_cids +7029 .values() +7030 .map(|cids| cids.active().len()) +7031 .max() +7032 .unwrap_or(20); // Max CID len in QUIC v1 +7033 +7034 // 1 byte for flags +7035 1 + cid_len + pn_len + self.tag_len_1rtt() +7036 } +7037 +7038 fn tag_len_1rtt(&self) -> usize { +7039 // encryption_keys for Data space returns 1-RTT keys if available, otherwise 0-RTT keys +7040 let packet_crypto = self +7041 .crypto_state +7042 .encryption_keys(SpaceKind::Data, self.side.side()) +7043 .map(|(_header, packet, _level)| packet); +7044 // If neither Data nor 0-RTT keys are available, make a reasonable tag length guess. As of +7045 // this writing, all QUIC cipher suites use 16-byte tags. We could return `None` instead, +7046 // but that would needlessly prevent sending datagrams during 0-RTT. +7047 packet_crypto.map_or(16, |x| x.tag_len()) +7048 } +7049 +7050 /// Mark the path as validated, and enqueue NEW_TOKEN frames to be sent as appropriate +7051 fn on_path_validated(&mut self, path_id: PathId) { +7052 self.path_data_mut(path_id).validated = true; +7053 let ConnectionSide::Server { server_config } = &self.side else { +7054 return; +7055 }; +7056 let network_path = self.path_data(path_id).network_path; +7057 let new_tokens = &mut self.spaces[SpaceId::Data as usize].pending.new_tokens; +7058 new_tokens.clear(); +7059 for _ in 0..server_config.validation_token.sent { +7060 new_tokens.push(network_path); +7061 } +7062 } +7063 +7064 /// Handle new path status information: PATH_STATUS_AVAILABLE, PATH_STATUS_BACKUP +7065 fn on_path_status(&mut self, path_id: PathId, status: PathStatus, status_seq_no: VarInt) { +7066 if let Some(path) = self.paths.get_mut(&path_id) { +7067 path.data.status.remote_update(status, status_seq_no); +7068 } else { +7069 debug!("PATH_STATUS_AVAILABLE received unknown path {:?}", path_id); +7070 } +7071 self.events.push_back( +7072 PathEvent::RemoteStatus { +7073 id: path_id, +7074 status, +7075 } +7076 .into(), +7077 ); +7078 } +7079 +7080 /// Returns the maximum [`PathId`] to be used for sending in this connection. +7081 /// +7082 /// This is calculated as minimum between the local and remote's maximums when multipath is +7083 /// enabled, or `None` when disabled. +7084 /// +7085 /// For data that's received, we should use [`Self::local_max_path_id`] instead. +7086 /// The reasoning is that the remote might already have updated to its own newer +7087 /// [`Self::max_path_id`] after sending out a `MAX_PATH_ID` frame, but it got re-ordered. +7088 fn max_path_id(&self) -> Option<PathId> { +7089 if self.is_multipath_negotiated() { +7090 Some(self.remote_max_path_id.min(self.local_max_path_id)) +7091 } else { +7092 None +7093 } +7094 } +7095 +7096 /// Returns whether this connection has a socket that supports IPv6. +7097 /// +7098 /// TODO(matheus23): This is related to noq endpoint state's `ipv6` bool. We should move that info +7099 /// here instead of trying to hack around not knowing it exactly. +7100 pub(crate) fn is_ipv6(&self) -> bool { +7101 self.paths +7102 .values() +7103 .any(|p| p.data.network_path.remote.is_ipv6()) +7104 } +7105 +7106 /// Add addresses the local endpoint considers are reachable for nat traversal. +7107 pub fn add_nat_traversal_address( +7108 &mut self, +7109 address: SocketAddr, +7110 ) -> Result<(), n0_nat_traversal::Error> { +7111 if let Some(added) = self.n0_nat_traversal.add_local_address(address)? { +7112 self.spaces[SpaceId::Data].pending.add_address.insert(added); +7113 }; +7114 Ok(()) +7115 } +7116 +7117 /// Removes an address the endpoing no longer considers reachable for nat traversal +7118 /// +7119 /// Addresses not present in the set will be silently ignored. +7120 pub fn remove_nat_traversal_address( +7121 &mut self, +7122 address: SocketAddr, +7123 ) -> Result<(), n0_nat_traversal::Error> { +7124 if let Some(removed) = self.n0_nat_traversal.remove_local_address(address)? { +7125 self.spaces[SpaceId::Data] +7126 .pending +7127 .remove_address +7128 .insert(removed); +7129 } +7130 Ok(()) +7131 } +7132 +7133 /// Get the current local nat traversal addresses +7134 pub fn get_local_nat_traversal_addresses( +7135 &self, 7136 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { -7137 if self.state.is_closed() { -7138 return Err(n0_nat_traversal::Error::Closed); -7139 } -7140 -7141 let ipv6 = self.is_ipv6(); -7142 let client_state = self.n0_nat_traversal.client_side_mut()?; -7143 let (mut reach_out_frames, probed_addrs) = -7144 client_state.initiate_nat_traversal_round(ipv6)?; -7145 if let Some(delay) = self.n0_nat_traversal.retry_delay(self.config.initial_rtt) { -7146 self.timers.set( -7147 Timer::Conn(ConnTimer::NatTraversalProbeRetry), -7148 now + delay, -7149 self.qlog.with_time(now), -7150 ); -7151 } -7152 -7153 self.spaces[SpaceId::Data] -7154 .pending -7155 .reach_out -7156 .append(&mut reach_out_frames); -7157 -7158 Ok(probed_addrs) -7159 } -7160 -7161 /// Whether the handshake is considered **confirmed**. -7162 /// -7163 /// <https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2> defines a handshake to be -7164 /// confirmed when you know the peer successfully received and successfully processed -7165 /// your TLS Finished message. -7166 /// -7167 /// Implementation-wise this is the point at which the handshake crypto keys are -7168 /// discarded. So we can use this to know if the handshake is confirmed. -7169 fn is_handshake_confirmed(&self) -> bool { -7170 !self.is_handshaking() && !self.crypto_state.has_keys(EncryptionLevel::Handshake) -7171 } -7172} -7173 -7174impl fmt::Debug for Connection { -7175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -7176 f.debug_struct("Connection") -7177 .field("handshake_cid", &self.handshake_cid) -7178 .finish() -7179 } -7180} -7181 -7182/// Hints when the caller identifies a network change. -7183pub trait NetworkChangeHint: std::fmt::Debug + 'static { -7184 /// Inform the connection if a path may recover after a network change. -7185 /// -7186 /// After network changes, paths may not be recoverable. In this case, waiting for the path to -7187 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] -7188 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to -7189 /// the same remote, closing the current one, instead of migrating the path. +7137 self.n0_nat_traversal.get_local_nat_traversal_addresses() +7138 } +7139 +7140 /// Get the currently advertised nat traversal addresses by the server +7141 pub fn get_remote_nat_traversal_addresses( +7142 &self, +7143 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7144 Ok(self +7145 .n0_nat_traversal +7146 .client_side()? +7147 .get_remote_nat_traversal_addresses()) +7148 } +7149 +7150 /// Initiates a new nat traversal round +7151 /// +7152 /// A nat traversal round involves advertising the client's local addresses in +7153 /// `REACH_OUT` frames, and initiating probing of the known remote addresses. When a new +7154 /// round is initiated, the previous one is cancelled. +7155 /// +7156 /// For all probes that succeed, if any, a new path will be opened on the successful +7157 /// 4-tuple. +7158 /// +7159 /// Returns the server addresses that are now being probed. If addresses fail due to +7160 /// spurious errors, these might succeed later and not be returned in this set. +7161 pub fn initiate_nat_traversal_round( +7162 &mut self, +7163 now: Instant, +7164 ) -> Result<Vec<SocketAddr>, n0_nat_traversal::Error> { +7165 if self.state.is_closed() { +7166 return Err(n0_nat_traversal::Error::Closed); +7167 } +7168 +7169 let ipv6 = self.is_ipv6(); +7170 let client_state = self.n0_nat_traversal.client_side_mut()?; +7171 let (mut reach_out_frames, probed_addrs) = +7172 client_state.initiate_nat_traversal_round(ipv6)?; +7173 if let Some(delay) = self.n0_nat_traversal.retry_delay(self.config.initial_rtt) { +7174 self.timers.set( +7175 Timer::Conn(ConnTimer::NatTraversalProbeRetry), +7176 now + delay, +7177 self.qlog.with_time(now), +7178 ); +7179 } +7180 +7181 self.spaces[SpaceId::Data] +7182 .pending +7183 .reach_out +7184 .append(&mut reach_out_frames); +7185 +7186 Ok(probed_addrs) +7187 } +7188 +7189 /// Whether the handshake is considered **confirmed**. 7190 /// -7191 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. -7192 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; -7193} -7194 -7195/// Return value for [`Connection::poll_transmit_path_space`]. -7196#[derive(Debug)] -7197enum PollPathSpaceStatus { -7198 /// Nothing to send in the space, nothing was written into the [`TransmitBuf`]. -7199 NothingToSend { -7200 /// If true there was data to send but congestion control did not allow so. -7201 congestion_blocked: bool, -7202 }, -7203 /// One or more packets have been written into the [`TransmitBuf`]. -7204 WrotePacket { -7205 /// The highest packet number. -7206 last_packet_number: u64, -7207 /// Whether to pad an already started datagram in the next packet. -7208 /// -7209 /// When packets in Initial, 0-RTT or Handshake packet do not fill the entire -7210 /// datagram they may decide to coalesce with the next packet from a higher -7211 /// encryption level on the same path. But the earlier packet may require specific -7212 /// size requirements for the datagram they are sent in. -7213 /// -7214 /// If a space did not complete the datagram, they use this to request the correct -7215 /// padding in the final packet of the datagram so that the final datagram will have -7216 /// the correct size. -7217 /// -7218 /// If a space did fill an entire datagram, it leaves this to the default of -7219 /// [`PadDatagram::No`]. -7220 pad_datagram: PadDatagram, -7221 }, -7222 /// Send the contents of the transmit immediately. -7223 /// -7224 /// Packets were written and the GSO batch must end now, regardless from whether higher -7225 /// spaces still have frames to write. This is used when the last datagram written would -7226 /// require too much padding to continue a GSO batch, which would waste space on the -7227 /// wire. -7228 Send { -7229 /// The highest packet number written into the transmit. -7230 last_packet_number: u64, -7231 }, -7232} -7233 -7234/// Information used to decide what frames to schedule into which packets. -7235/// -7236/// Primarily used by [`Connection::poll_transmit_on_path`] and the functions that help -7237/// building packets for it: [`Connection::poll_transmit_path_space`] and -7238/// [`Connection::populate_packet`]. -7239#[derive(Debug, Copy, Clone)] -7240struct PathSchedulingInfo { -7241 /// Whether the path is abandoned. -7242 /// -7243 /// Note that a path that is abandoned but still has CIDs can still send a packet. After -7244 /// sending that packet the CIDs issued by the remote have to be considered retired as -7245 /// well. -7246 is_abandoned: bool, -7247 /// Whether the path may send [`SpaceKind::Data`] frames. -7248 /// -7249 /// Some paths should only send frames from [`SendableFrames::space_specific`]. All other -7250 /// frames are essentially frames that can be sent on any [`SpaceKind::Data`] space. For -7251 /// those we want to respect packet scheduling rules however. -7252 /// -7253 /// Roughly speaking data frames are only sent on spaces that have CIDs, are not -7254 /// abandoned and have no *better* spaces. However see to comments where this is -7255 /// populated for the exact packet scheduling implementation. -7256 /// -7257 /// This essentially marks this paths as the best validated space ID. Except during -7258 /// the handshake in which case it does not need to be validated. Several paths could be -7259 /// equally good and all have this set to `true`, in that case packet scheduling can -7260 /// choose which path to use. Currently it chooses the lowest path that is not -7261 /// congestion blocked. -7262 /// -7263 /// Note that once in the closed or draining states this will never be true. -7264 may_send_data: bool, -7265 /// Whether the path may send a CONNECTION_CLOSE frame. -7266 /// -7267 /// This essentially marks this path as the best validated space ID with a fallback -7268 /// to unvalidated spaces if there are no validated spaces. Like for -7269 /// [`Self::may_send_data`] other paths could be equally good. -7270 may_send_close: bool, -7271 may_self_abandon: bool, -7272} -7273 -7274#[derive(Debug, Copy, Clone, PartialEq, Eq)] -7275enum PathBlocked { -7276 No, -7277 AntiAmplification, -7278 Congestion, -7279 Pacing, -7280} -7281 -7282/// Fields of `Connection` specific to it being client-side or server-side -7283enum ConnectionSide { -7284 Client { -7285 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded -7286 token: Bytes, -7287 token_store: Arc<dyn TokenStore>, -7288 server_name: String, -7289 }, -7290 Server { -7291 server_config: Arc<ServerConfig>, -7292 }, -7293} -7294 -7295impl ConnectionSide { -7296 fn is_client(&self) -> bool { -7297 self.side().is_client() -7298 } -7299 -7300 fn is_server(&self) -> bool { -7301 self.side().is_server() -7302 } -7303 -7304 fn side(&self) -> Side { -7305 match *self { -7306 Self::Client { .. } => Side::Client, -7307 Self::Server { .. } => Side::Server, -7308 } -7309 } -7310} -7311 -7312impl From<SideArgs> for ConnectionSide { -7313 fn from(side: SideArgs) -> Self { -7314 match side { -7315 SideArgs::Client { -7316 token_store, -7317 server_name, -7318 } => Self::Client { -7319 token: token_store.take(&server_name).unwrap_or_default(), -7320 token_store, -7321 server_name, -7322 }, -7323 SideArgs::Server { -7324 server_config, -7325 pref_addr_cid: _, -7326 path_validated: _, -7327 } => Self::Server { server_config }, -7328 } -7329 } -7330} -7331 -7332/// Parameters to `Connection::new` specific to it being client-side or server-side -7333pub(crate) enum SideArgs { -7334 Client { -7335 token_store: Arc<dyn TokenStore>, -7336 server_name: String, -7337 }, -7338 Server { -7339 server_config: Arc<ServerConfig>, -7340 pref_addr_cid: Option<ConnectionId>, -7341 path_validated: bool, -7342 }, -7343} -7344 -7345impl SideArgs { -7346 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { -7347 match *self { -7348 Self::Client { .. } => None, -7349 Self::Server { pref_addr_cid, .. } => pref_addr_cid, -7350 } -7351 } -7352 -7353 pub(crate) fn path_validated(&self) -> bool { -7354 match *self { -7355 Self::Client { .. } => true, -7356 Self::Server { path_validated, .. } => path_validated, -7357 } -7358 } -7359 -7360 pub(crate) fn side(&self) -> Side { -7361 match *self { -7362 Self::Client { .. } => Side::Client, -7363 Self::Server { .. } => Side::Server, -7364 } -7365 } -7366} +7191 /// <https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2> defines a handshake to be +7192 /// confirmed when you know the peer successfully received and successfully processed +7193 /// your TLS Finished message. +7194 /// +7195 /// Implementation-wise this is the point at which the handshake crypto keys are +7196 /// discarded. So we can use this to know if the handshake is confirmed. +7197 fn is_handshake_confirmed(&self) -> bool { +7198 !self.is_handshaking() && !self.crypto_state.has_keys(EncryptionLevel::Handshake) +7199 } +7200} +7201 +7202impl fmt::Debug for Connection { +7203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +7204 f.debug_struct("Connection") +7205 .field("handshake_cid", &self.handshake_cid) +7206 .finish() +7207 } +7208} +7209 +7210/// The set of abandoned paths. +7211/// +7212/// Implementation based on ArrayRangeSet to share more code. The memory space is +7213/// proportional to the number of concurrently open paths allowed. So does not grow +7214/// unbounded. +7215#[derive(Debug, Default)] +7216struct AbandonedPaths(ArrayRangeSet<ABANDONED_PATH_INLINE_RANGES, u32>); +7217 +7218/// Size of the stack-allocated array in the [`ArrayRangeSet`]. +7219/// +7220/// A range is 2 u32's, so this is 16 * (4 + 4) = 128 bytes. A good size for inline data, +7221/// with plenty of ranges for common multipath use. +7222const ABANDONED_PATH_INLINE_RANGES: usize = 16; +7223 +7224impl AbandonedPaths { +7225 /// The number of abandoned paths. +7226 fn len(&self) -> u32 { +7227 self.0.elts_count() +7228 } +7229 +7230 /// The largest abandoned path. +7231 fn max(&self) -> Option<PathId> { +7232 self.0.max().map(PathId::from) +7233 } +7234 +7235 /// Whether the path is already abandoned. +7236 fn contains(&self, val: &PathId) -> bool { +7237 self.0.contains(val.as_u32()) +7238 } +7239 +7240 /// Adds another abandoned path. +7241 fn insert(&mut self, val: PathId) { +7242 self.0.insert_one(val.as_u32()); +7243 } +7244} +7245 +7246/// Hints when the caller identifies a network change. +7247pub trait NetworkChangeHint: std::fmt::Debug + 'static { +7248 /// Inform the connection if a path may recover after a network change. +7249 /// +7250 /// After network changes, paths may not be recoverable. In this case, waiting for the path to +7251 /// become idle may take longer than what is desirable. If [`Self::is_path_recoverable`] +7252 /// returns `false`, a multipath-enabled, client-side connection will establish a new path to +7253 /// the same remote, closing the current one, instead of migrating the path. +7254 /// +7255 /// Paths that are deemed recoverable will simply be sent a PING for a liveness check. +7256 fn is_path_recoverable(&self, path_id: PathId, network_path: FourTuple) -> bool; +7257} +7258 +7259/// Return value for [`Connection::poll_transmit_path_space`]. +7260#[derive(Debug)] +7261enum PollPathSpaceStatus { +7262 /// Nothing to send in the space, nothing was written into the [`TransmitBuf`]. +7263 NothingToSend { +7264 /// If true there was data to send but congestion control did not allow so. +7265 congestion_blocked: bool, +7266 }, +7267 /// One or more packets have been written into the [`TransmitBuf`]. +7268 WrotePacket { +7269 /// The highest packet number. +7270 last_packet_number: u64, +7271 /// Whether to pad an already started datagram in the next packet. +7272 /// +7273 /// When packets in Initial, 0-RTT or Handshake packet do not fill the entire +7274 /// datagram they may decide to coalesce with the next packet from a higher +7275 /// encryption level on the same path. But the earlier packet may require specific +7276 /// size requirements for the datagram they are sent in. +7277 /// +7278 /// If a space did not complete the datagram, they use this to request the correct +7279 /// padding in the final packet of the datagram so that the final datagram will have +7280 /// the correct size. +7281 /// +7282 /// If a space did fill an entire datagram, it leaves this to the default of +7283 /// [`PadDatagram::No`]. +7284 pad_datagram: PadDatagram, +7285 }, +7286 /// Send the contents of the transmit immediately. +7287 /// +7288 /// Packets were written and the GSO batch must end now, regardless from whether higher +7289 /// spaces still have frames to write. This is used when the last datagram written would +7290 /// require too much padding to continue a GSO batch, which would waste space on the +7291 /// wire. +7292 Send { +7293 /// The highest packet number written into the transmit. +7294 last_packet_number: u64, +7295 }, +7296} +7297 +7298/// Information used to decide what frames to schedule into which packets. +7299/// +7300/// Primarily used by [`Connection::poll_transmit_on_path`] and the functions that help +7301/// building packets for it: [`Connection::poll_transmit_path_space`] and +7302/// [`Connection::populate_packet`]. +7303#[derive(Debug, Copy, Clone)] +7304struct PathSchedulingInfo { +7305 /// Whether the path is abandoned. +7306 /// +7307 /// Note that a path that is abandoned but still has CIDs can still send a packet. After +7308 /// sending that packet the CIDs issued by the remote have to be considered retired as +7309 /// well. +7310 is_abandoned: bool, +7311 /// Whether the path may send [`SpaceKind::Data`] frames. +7312 /// +7313 /// Some paths should only send frames from [`SendableFrames::space_specific`]. All other +7314 /// frames are essentially frames that can be sent on any [`SpaceKind::Data`] space. For +7315 /// those we want to respect packet scheduling rules however. +7316 /// +7317 /// Roughly speaking data frames are only sent on spaces that have CIDs, are not +7318 /// abandoned and have no *better* spaces. However see to comments where this is +7319 /// populated for the exact packet scheduling implementation. +7320 /// +7321 /// This essentially marks this paths as the best validated space ID. Except during +7322 /// the handshake in which case it does not need to be validated. Several paths could be +7323 /// equally good and all have this set to `true`, in that case packet scheduling can +7324 /// choose which path to use. Currently it chooses the lowest path that is not +7325 /// congestion blocked. +7326 /// +7327 /// Note that once in the closed or draining states this will never be true. +7328 may_send_data: bool, +7329 /// Whether the path may send a CONNECTION_CLOSE frame. +7330 /// +7331 /// This essentially marks this path as the best validated space ID with a fallback +7332 /// to unvalidated spaces if there are no validated spaces. Like for +7333 /// [`Self::may_send_data`] other paths could be equally good. +7334 may_send_close: bool, +7335 may_self_abandon: bool, +7336} +7337 +7338#[derive(Debug, Copy, Clone, PartialEq, Eq)] +7339enum PathBlocked { +7340 No, +7341 AntiAmplification, +7342 Congestion, +7343 Pacing, +7344} +7345 +7346/// Fields of `Connection` specific to it being client-side or server-side +7347enum ConnectionSide { +7348 Client { +7349 /// Sent in every outgoing Initial packet. Always empty after Initial keys are discarded +7350 token: Bytes, +7351 token_store: Arc<dyn TokenStore>, +7352 server_name: String, +7353 }, +7354 Server { +7355 server_config: Arc<ServerConfig>, +7356 }, +7357} +7358 +7359impl ConnectionSide { +7360 fn is_client(&self) -> bool { +7361 self.side().is_client() +7362 } +7363 +7364 fn is_server(&self) -> bool { +7365 self.side().is_server() +7366 } 7367 -7368/// Reasons why a connection might be lost -7369#[derive(Debug, Error, Clone, PartialEq, Eq)] -7370pub enum ConnectionError { -7371 /// The peer doesn't implement any supported version -7372 #[error("peer doesn't implement any supported version")] -7373 VersionMismatch, -7374 /// The peer violated the QUIC specification as understood by this implementation -7375 #[error(transparent)] -7376 TransportError(#[from] TransportError), -7377 /// The peer's QUIC stack aborted the connection automatically -7378 #[error("aborted by peer: {0}")] -7379 ConnectionClosed(frame::ConnectionClose), -7380 /// The peer closed the connection -7381 #[error("closed by peer: {0}")] -7382 ApplicationClosed(frame::ApplicationClose), -7383 /// The peer is unable to continue processing this connection, usually due to having restarted -7384 #[error("reset by peer")] -7385 Reset, -7386 /// Communication with the peer has lapsed for longer than the negotiated idle timeout -7387 /// -7388 /// If neither side is sending keep-alives, a connection will time out after a long enough idle -7389 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] -7390 /// and [`TransportConfig::keep_alive_interval()`]. -7391 #[error("timed out")] -7392 TimedOut, -7393 /// The local application closed the connection -7394 #[error("closed")] -7395 LocallyClosed, -7396 /// The connection could not be created because not enough of the CID space is available -7397 /// -7398 /// Try using longer connection IDs. -7399 #[error("CIDs exhausted")] -7400 CidsExhausted, -7401} -7402 -7403impl From<Close> for ConnectionError { -7404 fn from(x: Close) -> Self { -7405 match x { -7406 Close::Connection(reason) => Self::ConnectionClosed(reason), -7407 Close::Application(reason) => Self::ApplicationClosed(reason), -7408 } -7409 } -7410} -7411 -7412// For compatibility with API consumers -7413impl From<ConnectionError> for io::Error { -7414 fn from(x: ConnectionError) -> Self { -7415 use ConnectionError::*; -7416 let kind = match x { -7417 TimedOut => io::ErrorKind::TimedOut, -7418 Reset => io::ErrorKind::ConnectionReset, -7419 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, -7420 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { -7421 io::ErrorKind::Other -7422 } -7423 }; -7424 Self::new(kind, x) -7425 } -7426} -7427 -7428/// Errors that might trigger a path being closed -7429// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API -7430#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] -7431pub enum PathError { -7432 /// The extension was not negotiated with the peer -7433 #[error("multipath extension not negotiated")] -7434 MultipathNotNegotiated, -7435 /// Paths can only be opened client-side -7436 #[error("the server side may not open a path")] -7437 ServerSideNotAllowed, -7438 /// Current limits do not allow us to open more paths -7439 #[error("maximum number of concurrent paths reached")] -7440 MaxPathIdReached, -7441 /// No remote CIDs available to open a new path -7442 #[error("remoted CIDs exhausted")] -7443 RemoteCidsExhausted, -7444 /// Path could not be validated and will be abandoned -7445 #[error("path validation failed")] -7446 ValidationFailed, -7447 /// The remote address for the path is not supported by the endpoint -7448 #[error("invalid remote address")] -7449 InvalidRemoteAddress(SocketAddr), -7450} -7451 -7452/// Errors triggered when abandoning a path -7453#[derive(Debug, Error, Clone, Eq, PartialEq)] -7454pub enum ClosePathError { -7455 /// Multipath is not negotiated -7456 #[error("Multipath extension not negotiated")] -7457 MultipathNotNegotiated, -7458 /// The path is already closed or was never opened -7459 #[error("closed path")] -7460 ClosedPath, -7461 /// Cannot close the last remaining open path via the local API. -7462 /// -7463 /// Use [`Connection::close`] to end the connection instead. -7464 #[error("last open path")] -7465 LastOpenPath, -7466} -7467 -7468/// Error when the multipath extension was not negotiated, but attempted to be used. -7469#[derive(Debug, Error, Clone, Copy)] -7470#[error("Multipath extension not negotiated")] -7471pub struct MultipathNotNegotiated { -7472 _private: (), -7473} -7474 -7475/// Events of interest to the application -7476#[derive(Debug)] -7477pub enum Event { -7478 /// The connection's handshake data is ready -7479 HandshakeDataReady, -7480 /// The connection was successfully established -7481 Connected, -7482 /// The TLS handshake was confirmed -7483 HandshakeConfirmed, -7484 /// The connection was lost -7485 /// -7486 /// Emitted when the connection is closed due to an error, a timeout, or the peer closing it. -7487 /// This is **not** emitted when the local application closes the connection via -7488 /// [`Connection::close()`](crate::Connection::close). In that case, pending operations will -7489 /// fail with [`ConnectionError::LocallyClosed`]. -7490 ConnectionLost { -7491 /// Reason that the connection was closed -7492 reason: ConnectionError, -7493 }, -7494 /// Stream events -7495 Stream(StreamEvent), -7496 /// One or more application datagrams have been received -7497 DatagramReceived, -7498 /// One or more application datagrams have been sent after blocking -7499 DatagramsUnblocked, -7500 /// (Multi)Path events -7501 Path(PathEvent), -7502 /// n0's nat traversal events -7503 NatTraversal(n0_nat_traversal::Event), -7504} -7505 -7506impl From<PathEvent> for Event { -7507 fn from(source: PathEvent) -> Self { -7508 Self::Path(source) -7509 } -7510} -7511 -7512fn get_max_ack_delay(params: &TransportParameters) -> Duration { -7513 Duration::from_micros(params.max_ack_delay.0 * 1000) +7368 fn side(&self) -> Side { +7369 match *self { +7370 Self::Client { .. } => Side::Client, +7371 Self::Server { .. } => Side::Server, +7372 } +7373 } +7374} +7375 +7376impl From<SideArgs> for ConnectionSide { +7377 fn from(side: SideArgs) -> Self { +7378 match side { +7379 SideArgs::Client { +7380 token_store, +7381 server_name, +7382 } => Self::Client { +7383 token: token_store.take(&server_name).unwrap_or_default(), +7384 token_store, +7385 server_name, +7386 }, +7387 SideArgs::Server { +7388 server_config, +7389 pref_addr_cid: _, +7390 path_validated: _, +7391 } => Self::Server { server_config }, +7392 } +7393 } +7394} +7395 +7396/// Parameters to `Connection::new` specific to it being client-side or server-side +7397pub(crate) enum SideArgs { +7398 Client { +7399 token_store: Arc<dyn TokenStore>, +7400 server_name: String, +7401 }, +7402 Server { +7403 server_config: Arc<ServerConfig>, +7404 pref_addr_cid: Option<ConnectionId>, +7405 path_validated: bool, +7406 }, +7407} +7408 +7409impl SideArgs { +7410 pub(crate) fn pref_addr_cid(&self) -> Option<ConnectionId> { +7411 match *self { +7412 Self::Client { .. } => None, +7413 Self::Server { pref_addr_cid, .. } => pref_addr_cid, +7414 } +7415 } +7416 +7417 pub(crate) fn path_validated(&self) -> bool { +7418 match *self { +7419 Self::Client { .. } => true, +7420 Self::Server { path_validated, .. } => path_validated, +7421 } +7422 } +7423 +7424 pub(crate) fn side(&self) -> Side { +7425 match *self { +7426 Self::Client { .. } => Side::Client, +7427 Self::Server { .. } => Side::Server, +7428 } +7429 } +7430} +7431 +7432/// Reasons why a connection might be lost +7433#[derive(Debug, Error, Clone, PartialEq, Eq)] +7434pub enum ConnectionError { +7435 /// The peer doesn't implement any supported version +7436 #[error("peer doesn't implement any supported version")] +7437 VersionMismatch, +7438 /// The peer violated the QUIC specification as understood by this implementation +7439 #[error(transparent)] +7440 TransportError(#[from] TransportError), +7441 /// The peer's QUIC stack aborted the connection automatically +7442 #[error("aborted by peer: {0}")] +7443 ConnectionClosed(frame::ConnectionClose), +7444 /// The peer closed the connection +7445 #[error("closed by peer: {0}")] +7446 ApplicationClosed(frame::ApplicationClose), +7447 /// The peer is unable to continue processing this connection, usually due to having restarted +7448 #[error("reset by peer")] +7449 Reset, +7450 /// Communication with the peer has lapsed for longer than the negotiated idle timeout +7451 /// +7452 /// If neither side is sending keep-alives, a connection will time out after a long enough idle +7453 /// period even if the peer is still reachable. See also [`TransportConfig::max_idle_timeout()`] +7454 /// and [`TransportConfig::keep_alive_interval()`]. +7455 #[error("timed out")] +7456 TimedOut, +7457 /// The local application closed the connection +7458 #[error("closed")] +7459 LocallyClosed, +7460 /// The connection could not be created because not enough of the CID space is available +7461 /// +7462 /// Try using longer connection IDs. +7463 #[error("CIDs exhausted")] +7464 CidsExhausted, +7465} +7466 +7467impl From<Close> for ConnectionError { +7468 fn from(x: Close) -> Self { +7469 match x { +7470 Close::Connection(reason) => Self::ConnectionClosed(reason), +7471 Close::Application(reason) => Self::ApplicationClosed(reason), +7472 } +7473 } +7474} +7475 +7476// For compatibility with API consumers +7477impl From<ConnectionError> for io::Error { +7478 fn from(x: ConnectionError) -> Self { +7479 use ConnectionError::*; +7480 let kind = match x { +7481 TimedOut => io::ErrorKind::TimedOut, +7482 Reset => io::ErrorKind::ConnectionReset, +7483 ApplicationClosed(_) | ConnectionClosed(_) => io::ErrorKind::ConnectionAborted, +7484 TransportError(_) | VersionMismatch | LocallyClosed | CidsExhausted => { +7485 io::ErrorKind::Other +7486 } +7487 }; +7488 Self::new(kind, x) +7489 } +7490} +7491 +7492/// Errors that might trigger a path being closed +7493// TODO(@divma): maybe needs to be reworked based on what we want to do with the public API +7494#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)] +7495pub enum PathError { +7496 /// The extension was not negotiated with the peer +7497 #[error("multipath extension not negotiated")] +7498 MultipathNotNegotiated, +7499 /// Paths can only be opened client-side +7500 #[error("the server side may not open a path")] +7501 ServerSideNotAllowed, +7502 /// Current limits do not allow us to open more paths +7503 #[error("maximum number of concurrent paths reached")] +7504 MaxPathIdReached, +7505 /// No remote CIDs available to open a new path +7506 #[error("remoted CIDs exhausted")] +7507 RemoteCidsExhausted, +7508 /// Path could not be validated and will be abandoned +7509 #[error("path validation failed")] +7510 ValidationFailed, +7511 /// The remote address for the path is not supported by the endpoint +7512 #[error("invalid remote address")] +7513 InvalidRemoteAddress(SocketAddr), 7514} 7515 -7516/// Prevents overflow and improves behavior in extreme circumstances. -7517const MAX_BACKOFF_EXPONENT: u32 = 16; -7518 -7519/// The max interval between successive tail-loss probes. -7520/// -7521/// This is the "normal" value we use. -7522const MAX_PTO_INTERVAL: Duration = Duration::from_secs(2); -7523 -7524/// The idle time, below which we use the shorter [`MAX_PTO_FAST_INTERVAL`]. -7525const MIN_IDLE_FOR_FAST_PTO: Duration = Duration::from_secs(25); -7526 -7527/// The max interval between successive tail-loss probes with short idle times. -7528/// -7529/// If the path or connection idle time is less than [`MIN_IDLE_FOR_FAST_PTO`] then we use -7530/// this value to ensure we have plenty of retransmits before we reach the idle time. -7531const MAX_PTO_FAST_INTERVAL: Duration = Duration::from_secs(1); -7532 -7533/// The RTT threshold above which we cap the PTO interval to 1.5 * smoothed_rtt -7534/// -7535/// This is RTT time above which 1.5 * RTT > [`MAX_PTO_INTERVAL`], for these links we want -7536/// to extend the interval between tail-loss probes to not fill the entire pipe with them. -7537const SLOW_RTT_THRESHOLD: Duration = -7538 Duration::from_millis((MAX_PTO_INTERVAL.as_millis() as u64 * 2) / 3); -7539 -7540/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag -7541/// -7542/// This must be at least as large as the header for a well-formed empty packet to be coalesced, -7543/// plus some space for frames. We only care about handshake headers because short header packets -7544/// necessarily have smaller headers, and initial packets are only ever the first packet in a -7545/// datagram (because we coalesce in ascending packet space order and the only reason to split a -7546/// packet is when packet space changes). -7547const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; -7548 -7549/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header -7550/// -7551/// Excludes packet-type-specific fields such as packet number or Initial token -7552// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + -7553// scid len + scid + length + pn -7554const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = -7555 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; -7556 -7557#[derive(Default)] -7558struct SentFrames { -7559 retransmits: ThinRetransmits, -7560 /// The packet number of the largest acknowledged packet for each path -7561 largest_acked: FxHashMap<PathId, u64>, -7562 stream_frames: StreamMetaVec, -7563 /// Whether the packet contains non-retransmittable frames (like datagrams) -7564 non_retransmits: bool, -7565 /// If the datagram containing these frames should be padded to the min MTU -7566 requires_padding: bool, -7567} -7568 -7569impl SentFrames { -7570 /// Returns whether the packet contains only ACKs -7571 fn is_ack_only(&self, streams: &StreamsState) -> bool { -7572 !self.largest_acked.is_empty() -7573 && !self.non_retransmits -7574 && self.stream_frames.is_empty() -7575 && self.retransmits.is_empty(streams) -7576 } -7577 -7578 /// Returns whether the packet contains any frames at all -7579 fn is_empty(&self, streams: &StreamsState) -> bool { -7580 !self.non_retransmits && self.stream_frames.is_empty() && self.retransmits.is_empty(streams) -7581 } +7516/// Errors triggered when abandoning a path +7517#[derive(Debug, Error, Clone, Eq, PartialEq)] +7518pub enum ClosePathError { +7519 /// Multipath is not negotiated +7520 #[error("Multipath extension not negotiated")] +7521 MultipathNotNegotiated, +7522 /// The path is already closed or was never opened +7523 #[error("closed path")] +7524 ClosedPath, +7525 /// Cannot close the last remaining open path via the local API. +7526 /// +7527 /// Use [`Connection::close`] to end the connection instead. +7528 #[error("last open path")] +7529 LastOpenPath, +7530} +7531 +7532/// Error when the multipath extension was not negotiated, but attempted to be used. +7533#[derive(Debug, Error, Clone, Copy)] +7534#[error("Multipath extension not negotiated")] +7535pub struct MultipathNotNegotiated { +7536 _private: (), +7537} +7538 +7539/// Events of interest to the application +7540#[derive(Debug)] +7541pub enum Event { +7542 /// The connection's handshake data is ready +7543 HandshakeDataReady, +7544 /// The connection was successfully established +7545 Connected, +7546 /// The TLS handshake was confirmed +7547 HandshakeConfirmed, +7548 /// The connection was lost +7549 /// +7550 /// Emitted when the connection is closed due to an error, a timeout, or the peer closing it. +7551 /// This is **not** emitted when the local application closes the connection via +7552 /// [`Connection::close()`](crate::Connection::close). In that case, pending operations will +7553 /// fail with [`ConnectionError::LocallyClosed`]. +7554 ConnectionLost { +7555 /// Reason that the connection was closed +7556 reason: ConnectionError, +7557 }, +7558 /// Stream events +7559 Stream(StreamEvent), +7560 /// One or more application datagrams have been received +7561 DatagramReceived, +7562 /// One or more application datagrams have been sent after blocking +7563 DatagramsUnblocked, +7564 /// (Multi)Path events +7565 Path(PathEvent), +7566 /// n0's nat traversal events +7567 NatTraversal(n0_nat_traversal::Event), +7568} +7569 +7570impl From<PathEvent> for Event { +7571 fn from(source: PathEvent) -> Self { +7572 Self::Path(source) +7573 } +7574} +7575 +7576fn get_max_ack_delay(params: &TransportParameters) -> Duration { +7577 Duration::from_micros(params.max_ack_delay.0 * 1000) +7578} +7579 +7580/// Prevents overflow and improves behavior in extreme circumstances. +7581const MAX_BACKOFF_EXPONENT: u32 = 16; 7582 -7583 fn retransmits_mut(&mut self) -> &mut Retransmits { -7584 self.retransmits.get_or_create() -7585 } -7586 -7587 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { -7588 use frame::EncodableFrame::*; -7589 match frame { -7590 PathAck(path_ack_encoder) => { -7591 if let Some(max) = path_ack_encoder.ranges.max() { -7592 self.largest_acked.insert(path_ack_encoder.path_id, max); -7593 } -7594 } -7595 Ack(ack_encoder) => { -7596 if let Some(max) = ack_encoder.ranges.max() { -7597 self.largest_acked.insert(PathId::ZERO, max); -7598 } -7599 } -7600 Close(_) => { /* non retransmittable, but after this we don't really care */ } -7601 PathResponse(_) => self.non_retransmits = true, -7602 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, -7603 ReachOut(frame) => self.retransmits_mut().reach_out.push(frame), -7604 ObservedAddr(_) => self.retransmits_mut().observed_addr = true, -7605 Ping(_) => self.non_retransmits = true, -7606 ImmediateAck(_) => self.non_retransmits = true, -7607 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, -7608 PathChallenge(_) => self.non_retransmits = true, -7609 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), -7610 PathAbandon(path_abandon) => { -7611 self.retransmits_mut() -7612 .path_abandon -7613 .entry(path_abandon.path_id) -7614 .or_insert(path_abandon.error_code); -7615 } -7616 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) -7617 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { -7618 self.retransmits_mut().path_status.insert(path_id); -7619 } -7620 MaxPathId(_) => self.retransmits_mut().max_path_id = true, -7621 PathsBlocked(_) => self.retransmits_mut().paths_blocked = true, -7622 PathCidsBlocked(path_cids_blocked) => { -7623 self.retransmits_mut() -7624 .path_cids_blocked -7625 .insert(path_cids_blocked.path_id); -7626 } -7627 ResetStream(reset) => self -7628 .retransmits_mut() -7629 .reset_stream -7630 .push((reset.id, reset.error_code)), -7631 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), -7632 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), -7633 RetireConnectionId(retire_cid) => self -7634 .retransmits_mut() -7635 .retire_cids -7636 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), -7637 Datagram(_) => self.non_retransmits = true, -7638 NewToken(_) => {} -7639 AddAddress(add_address) => { -7640 self.retransmits_mut().add_address.insert(add_address); -7641 } -7642 RemoveAddress(remove_address) => { -7643 self.retransmits_mut().remove_address.insert(remove_address); -7644 } -7645 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), -7646 MaxData(_) => self.retransmits_mut().max_data = true, -7647 MaxStreamData(max) => { -7648 self.retransmits_mut().max_stream_data.insert(max.id); -7649 } -7650 MaxStreams(max_streams) => { -7651 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true -7652 } -7653 StreamsBlocked(streams_blocked) => { -7654 self.retransmits_mut().streams_blocked[streams_blocked.dir as usize] = true -7655 } -7656 } -7657 } -7658} -7659 -7660/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. -7661/// -7662/// According to the definition of max_idle_timeout, a value of `0` means the timeout is disabled; see <https://www.rfc-editor.org/rfc/rfc9000#section-18.2-4.4.1.> -7663/// -7664/// According to the negotiation procedure, either the minimum of the timeouts or one specified is used as the negotiated value; see <https://www.rfc-editor.org/rfc/rfc9000#section-10.1-2.> -7665/// -7666/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. -7667fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { -7668 match (x, y) { -7669 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, -7670 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), -7671 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), -7672 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), -7673 } -7674} -7675 -7676#[cfg(test)] -7677mod tests { -7678 use super::*; -7679 -7680 #[test] -7681 fn negotiate_max_idle_timeout_commutative() { -7682 let test_params = [ -7683 (None, None, None), -7684 (None, Some(VarInt(0)), None), -7685 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), -7686 (Some(VarInt(0)), Some(VarInt(0)), None), -7687 ( -7688 Some(VarInt(2)), -7689 Some(VarInt(0)), -7690 Some(Duration::from_millis(2)), -7691 ), -7692 ( -7693 Some(VarInt(1)), -7694 Some(VarInt(4)), -7695 Some(Duration::from_millis(1)), -7696 ), -7697 ]; -7698 -7699 for (left, right, result) in test_params { -7700 assert_eq!(negotiate_max_idle_timeout(left, right), result); -7701 assert_eq!(negotiate_max_idle_timeout(right, left), result); -7702 } -7703 } -7704}
\ No newline at end of file +7583/// The max interval between successive tail-loss probes. +7584/// +7585/// This is the "normal" value we use. +7586const MAX_PTO_INTERVAL: Duration = Duration::from_secs(2); +7587 +7588/// The idle time, below which we use the shorter [`MAX_PTO_FAST_INTERVAL`]. +7589const MIN_IDLE_FOR_FAST_PTO: Duration = Duration::from_secs(25); +7590 +7591/// The max interval between successive tail-loss probes with short idle times. +7592/// +7593/// If the path or connection idle time is less than [`MIN_IDLE_FOR_FAST_PTO`] then we use +7594/// this value to ensure we have plenty of retransmits before we reach the idle time. +7595const MAX_PTO_FAST_INTERVAL: Duration = Duration::from_secs(1); +7596 +7597/// The RTT threshold above which we cap the PTO interval to 1.5 * smoothed_rtt +7598/// +7599/// This is RTT time above which 1.5 * RTT > [`MAX_PTO_INTERVAL`], for these links we want +7600/// to extend the interval between tail-loss probes to not fill the entire pipe with them. +7601const SLOW_RTT_THRESHOLD: Duration = +7602 Duration::from_millis((MAX_PTO_INTERVAL.as_millis() as u64 * 2) / 3); +7603 +7604/// Minimal remaining size to allow packet coalescing, excluding cryptographic tag +7605/// +7606/// This must be at least as large as the header for a well-formed empty packet to be coalesced, +7607/// plus some space for frames. We only care about handshake headers because short header packets +7608/// necessarily have smaller headers, and initial packets are only ever the first packet in a +7609/// datagram (because we coalesce in ascending packet space order and the only reason to split a +7610/// packet is when packet space changes). +7611const MIN_PACKET_SPACE: usize = MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE + 32; +7612 +7613/// Largest amount of space that could be occupied by a Handshake or 0-RTT packet's header +7614/// +7615/// Excludes packet-type-specific fields such as packet number or Initial token +7616// https://www.rfc-editor.org/rfc/rfc9000.html#name-0-rtt: flags + version + dcid len + dcid + +7617// scid len + scid + length + pn +7618const MAX_HANDSHAKE_OR_0RTT_HEADER_SIZE: usize = +7619 1 + 4 + 1 + MAX_CID_SIZE + 1 + MAX_CID_SIZE + VarInt::from_u32(u16::MAX as u32).size() + 4; +7620 +7621#[derive(Default)] +7622struct SentFrames { +7623 retransmits: ThinRetransmits, +7624 path_retransmits: PathRetransmits, +7625 /// The packet number of the largest acknowledged packet for each path +7626 largest_acked: FxHashMap<PathId, u64>, +7627 stream_frames: StreamMetaVec, +7628 /// Whether the packet contains non-retransmittable frames (like datagrams) +7629 non_retransmits: bool, +7630 /// If the datagram containing these frames should be padded to the min MTU +7631 requires_padding: bool, +7632} +7633 +7634impl SentFrames { +7635 /// Returns whether the packet contains only ACKs +7636 fn is_ack_only(&self, streams: &StreamsState) -> bool { +7637 !self.largest_acked.is_empty() +7638 && !self.non_retransmits +7639 && self.stream_frames.is_empty() +7640 && self.retransmits.is_empty(streams) +7641 } +7642 +7643 /// Returns whether the packet contains any frames at all +7644 fn is_empty(&self, streams: &StreamsState) -> bool { +7645 !self.non_retransmits && self.stream_frames.is_empty() && self.retransmits.is_empty(streams) +7646 } +7647 +7648 fn retransmits_mut(&mut self) -> &mut Retransmits { +7649 self.retransmits.get_or_create() +7650 } +7651 +7652 fn record_sent_frame(&mut self, frame: frame::EncodableFrame<'_>) { +7653 use frame::EncodableFrame::*; +7654 match frame { +7655 PathAck(path_ack_encoder) => { +7656 if let Some(max) = path_ack_encoder.ranges.max() { +7657 self.largest_acked.insert(path_ack_encoder.path_id, max); +7658 } +7659 } +7660 Ack(ack_encoder) => { +7661 if let Some(max) = ack_encoder.ranges.max() { +7662 self.largest_acked.insert(PathId::ZERO, max); +7663 } +7664 } +7665 Close(_) => { /* non retransmittable, but after this we don't really care */ } +7666 PathResponse(_) => self.non_retransmits = true, +7667 HandshakeDone(_) => self.retransmits_mut().handshake_done = true, +7668 ReachOut(frame) => self.retransmits_mut().reach_out.push(frame), +7669 ObservedAddr(_) => self.path_retransmits.observed_address = true, +7670 Ping(_) => self.non_retransmits = true, +7671 ImmediateAck(_) => self.non_retransmits = true, +7672 AckFrequency(_) => self.retransmits_mut().ack_frequency = true, +7673 PathChallenge(_) => self.non_retransmits = true, +7674 Crypto(crypto) => self.retransmits_mut().crypto.push_back(crypto), +7675 PathAbandon(path_abandon) => { +7676 self.retransmits_mut() +7677 .path_abandon +7678 .entry(path_abandon.path_id) +7679 .or_insert(path_abandon.error_code); +7680 } +7681 PathStatusAvailable(frame::PathStatusAvailable { path_id, .. }) +7682 | PathStatusBackup(frame::PathStatusBackup { path_id, .. }) => { +7683 self.retransmits_mut().path_status.insert(path_id); +7684 } +7685 MaxPathId(_) => self.retransmits_mut().max_path_id = true, +7686 PathsBlocked(frame::PathsBlocked(path_id)) => { +7687 let paths_blocked = &mut self.retransmits_mut().paths_blocked; +7688 *paths_blocked = cmp::max(*paths_blocked, Some(path_id)); +7689 } +7690 PathCidsBlocked(path_cids_blocked) => { +7691 self.retransmits_mut() +7692 .path_cids_blocked +7693 .entry(path_cids_blocked.path_id) +7694 .and_modify(|next_seq| { +7695 *next_seq = cmp::max(*next_seq, path_cids_blocked.next_seq); +7696 }) +7697 .or_insert(path_cids_blocked.next_seq); +7698 } +7699 ResetStream(reset) => self +7700 .retransmits_mut() +7701 .reset_stream +7702 .push((reset.id, reset.error_code)), +7703 StopSending(stop_sending) => self.retransmits_mut().stop_sending.push(stop_sending), +7704 NewConnectionId(new_cid) => self.retransmits_mut().new_cids.push(new_cid.issued()), +7705 RetireConnectionId(retire_cid) => self +7706 .retransmits_mut() +7707 .retire_cids +7708 .push((retire_cid.path_id.unwrap_or_default(), retire_cid.sequence)), +7709 Datagram(_) => self.non_retransmits = true, +7710 NewToken(_) => {} +7711 AddAddress(add_address) => { +7712 self.retransmits_mut().add_address.insert(add_address); +7713 } +7714 RemoveAddress(remove_address) => { +7715 self.retransmits_mut().remove_address.insert(remove_address); +7716 } +7717 StreamMeta(stream_meta_encoder) => self.stream_frames.push(stream_meta_encoder.meta), +7718 MaxData(_) => self.retransmits_mut().max_data = true, +7719 MaxStreamData(max) => { +7720 self.retransmits_mut().max_stream_data.insert(max.id); +7721 } +7722 MaxStreams(max_streams) => { +7723 self.retransmits_mut().max_stream_id[max_streams.dir as usize] = true +7724 } +7725 StreamsBlocked(streams_blocked) => { +7726 self.retransmits_mut().streams_blocked[streams_blocked.dir as usize] = true +7727 } +7728 } +7729 } +7730} +7731 +7732/// Compute the negotiated idle timeout based on local and remote max_idle_timeout transport parameters. +7733/// +7734/// According to the definition of max_idle_timeout, a value of `0` means the timeout is disabled; see <https://www.rfc-editor.org/rfc/rfc9000#section-18.2-4.4.1.> +7735/// +7736/// According to the negotiation procedure, either the minimum of the timeouts or one specified is used as the negotiated value; see <https://www.rfc-editor.org/rfc/rfc9000#section-10.1-2.> +7737/// +7738/// Returns the negotiated idle timeout as a `Duration`, or `None` when both endpoints have opted out of idle timeout. +7739fn negotiate_max_idle_timeout(x: Option<VarInt>, y: Option<VarInt>) -> Option<Duration> { +7740 match (x, y) { +7741 (Some(VarInt(0)) | None, Some(VarInt(0)) | None) => None, +7742 (Some(VarInt(0)) | None, Some(y)) => Some(Duration::from_millis(y.0)), +7743 (Some(x), Some(VarInt(0)) | None) => Some(Duration::from_millis(x.0)), +7744 (Some(x), Some(y)) => Some(Duration::from_millis(cmp::min(x, y).0)), +7745 } +7746} +7747 +7748#[cfg(test)] +7749mod tests { +7750 use super::*; +7751 +7752 #[test] +7753 fn negotiate_max_idle_timeout_commutative() { +7754 let test_params = [ +7755 (None, None, None), +7756 (None, Some(VarInt(0)), None), +7757 (None, Some(VarInt(2)), Some(Duration::from_millis(2))), +7758 (Some(VarInt(0)), Some(VarInt(0)), None), +7759 ( +7760 Some(VarInt(2)), +7761 Some(VarInt(0)), +7762 Some(Duration::from_millis(2)), +7763 ), +7764 ( +7765 Some(VarInt(1)), +7766 Some(VarInt(4)), +7767 Some(Duration::from_millis(1)), +7768 ), +7769 ]; +7770 +7771 for (left, right, result) in test_params { +7772 assert_eq!(negotiate_max_idle_timeout(left, right), result); +7773 assert_eq!(negotiate_max_idle_timeout(right, left), result); +7774 } +7775 } +7776 +7777 #[test] +7778 fn abandoned_paths() { +7779 let mut t = AbandonedPaths::default(); +7780 +7781 t.insert(PathId(0)); +7782 t.insert(PathId(1)); +7783 assert_eq!(t.len(), 2); +7784 assert_eq!(t.0.range_count(), 1); // 2 elements compacted into one range +7785 assert!(t.contains(&PathId(0))); +7786 assert!(t.contains(&PathId(1))); +7787 assert!(!t.contains(&PathId(2))); +7788 assert!(!t.contains(&PathId(3))); +7789 assert_eq!(t.max(), Some(PathId(1))); +7790 +7791 t.insert(PathId(3)); +7792 assert_eq!(t.len(), 3); +7793 assert_eq!(t.0.range_count(), 2); // 3 elements compacted into 2 ranges +7794 assert!(t.contains(&PathId(0))); +7795 assert!(t.contains(&PathId(1))); +7796 assert!(!t.contains(&PathId(2))); +7797 assert!(t.contains(&PathId(3))); +7798 assert_eq!(t.max(), Some(PathId(3))); +7799 +7800 t.insert(PathId(2)); +7801 assert_eq!(t.len(), 4); +7802 assert_eq!(t.0.range_count(), 1); // 4 elements compacted into 1 range +7803 assert!(t.contains(&PathId(0))); +7804 assert!(t.contains(&PathId(1))); +7805 assert!(t.contains(&PathId(2))); +7806 assert!(t.contains(&PathId(3))); +7807 assert_eq!(t.max(), Some(PathId(3))); +7808 } +7809}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/packet_builder.rs.html b/pr/451/docs/src/noq_proto/connection/packet_builder.rs.html index 9247a8803..69211871a 100644 --- a/pr/451/docs/src/noq_proto/connection/packet_builder.rs.html +++ b/pr/451/docs/src/noq_proto/connection/packet_builder.rs.html @@ -293,144 +293,148 @@ 293 size, 294 ack_eliciting, 295 retransmits: sent.retransmits, -296 stream_frames: sent.stream_frames, -297 }; -298 -299 conn.paths.get_mut(&path_id).unwrap().data.sent( -300 packet_number, -301 packet, -302 conn.spaces[space_id].for_path(path_id), -303 ); -304 conn.reset_keep_alive(path_id, now); -305 if size != 0 { -306 if ack_eliciting { -307 conn.spaces[space_id] -308 .for_path(path_id) -309 .time_of_last_ack_eliciting_packet = Some(now); -310 if conn.path_data(path_id).permit_idle_reset { -311 conn.reset_idle_timeout(now, space_id.kind(), path_id); -312 } -313 conn.path_data_mut(path_id).permit_idle_reset = false; -314 } -315 conn.set_loss_detection_timer(now, path_id); -316 conn.path_data_mut(path_id).pacing.on_transmit(size); -317 } -318 } -319 -320 /// Encrypt packet, returning the length of the packet and whether padding was added -321 pub(super) fn finish( -322 mut self, -323 conn: &mut Connection, -324 now: Instant, -325 ) -> (usize, bool, SentFrames) { -326 debug_assert!( -327 self.buf.len() <= self.buf.datagram_max_offset() - self.tag_len, -328 "packet exceeds maximum size" -329 ); +296 path_retransmits: sent.path_retransmits, +297 stream_frames: sent.stream_frames, +298 }; +299 +300 conn.paths.get_mut(&path_id).unwrap().data.sent( +301 packet_number, +302 packet, +303 conn.spaces[space_id].for_path(path_id), +304 ); +305 conn.reset_keep_alive(path_id, now); +306 if size != 0 { +307 if ack_eliciting { +308 conn.spaces[space_id] +309 .for_path(path_id) +310 .time_of_last_ack_eliciting_packet = Some(now); +311 if conn.path_data(path_id).permit_idle_reset { +312 conn.reset_idle_timeout(now, space_id.kind(), path_id); +313 } +314 conn.path_data_mut(path_id).permit_idle_reset = false; +315 conn.path_data_mut(path_id) +316 .congestion +317 .on_packet_sent(now, size, packet_number) +318 } +319 conn.set_loss_detection_timer(now, path_id); +320 conn.path_data_mut(path_id).pacing.on_transmit(size); +321 } +322 } +323 +324 /// Encrypt packet, returning the length of the packet and whether padding was added +325 pub(super) fn finish( +326 mut self, +327 conn: &mut Connection, +328 now: Instant, +329 ) -> (usize, bool, SentFrames) { 330 debug_assert!( -331 !self.sent_frames.is_empty(&conn.streams), -332 "constructed empty packet" +331 self.buf.len() <= self.buf.datagram_max_offset() - self.tag_len, +332 "packet exceeds maximum size" 333 ); -334 let pad = self.buf.len() < self.min_size; -335 if pad { -336 let padding = self.min_size - self.buf.len(); -337 trace!("PADDING * {}", padding); -338 self.buf.put_bytes(0, padding); -339 self.qlog.frame_padding(padding); -340 } -341 -342 let (header_crypto, packet_crypto) = conn -343 .crypto_state -344 .local_crypto(self.level) -345 .expect("tried to send packet without keys"); -346 -347 debug_assert_eq!( -348 packet_crypto.tag_len(), -349 self.tag_len, -350 "Mismatching crypto tag len" -351 ); -352 -353 self.buf.put_bytes(0, packet_crypto.tag_len()); -354 let encode_start = self.partial_encode.start; -355 let packet_buf = &mut self.buf.as_mut_slice()[encode_start..]; -356 // for packet protection, PathId::ZERO and no path are equivalent. -357 self.partial_encode.finish( -358 packet_buf, -359 header_crypto, -360 Some((self.packet_number, self.path, packet_crypto)), -361 ); -362 -363 conn.crypto_state.inc_sent_with_keys(self.level); -364 -365 let packet_len = self.buf.len() - encode_start; -366 trace!(size = %packet_len, "wrote packet"); -367 self.qlog.finalize(packet_len); -368 conn.qlog.emit_packet_sent(self.qlog, now); -369 (packet_len, pad, self.sent_frames) -370 } -371 -372 /// The number of additional bytes the current packet would take up if it was finished now -373 /// -374 /// This will include any padding which is required to make the size large enough to be -375 /// encrypted correctly. -376 pub(super) fn predict_packet_end(&self) -> usize { -377 self.buf.len().max(self.min_size) + self.tag_len - self.buf.len() -378 } -379 -380 /// Returns the remaining space in the packet that can be taken up by QUIC frames -381 /// -382 /// This leaves space in the datagram for the cryptographic tag that needs to be written -383 /// when the packet is finished. -384 pub(super) fn frame_space_remaining(&self) -> usize { -385 let max_offset = self.buf.datagram_max_offset() - self.tag_len; -386 max_offset.saturating_sub(self.buf.len()) -387 } -388 -389 pub(crate) fn require_padding(&mut self) { -390 self.sent_frames.requires_padding = true; +334 debug_assert!( +335 !self.sent_frames.is_empty(&conn.streams), +336 "constructed empty packet" +337 ); +338 let pad = self.buf.len() < self.min_size; +339 if pad { +340 let padding = self.min_size - self.buf.len(); +341 trace!("PADDING * {}", padding); +342 self.buf.put_bytes(0, padding); +343 self.qlog.frame_padding(padding); +344 } +345 +346 let (header_crypto, packet_crypto) = conn +347 .crypto_state +348 .local_crypto(self.level) +349 .expect("tried to send packet without keys"); +350 +351 debug_assert_eq!( +352 packet_crypto.tag_len(), +353 self.tag_len, +354 "Mismatching crypto tag len" +355 ); +356 +357 self.buf.put_bytes(0, packet_crypto.tag_len()); +358 let encode_start = self.partial_encode.start; +359 let packet_buf = &mut self.buf.as_mut_slice()[encode_start..]; +360 // for packet protection, PathId::ZERO and no path are equivalent. +361 self.partial_encode.finish( +362 packet_buf, +363 header_crypto, +364 Some((self.packet_number, self.path, packet_crypto)), +365 ); +366 +367 conn.crypto_state.inc_sent_with_keys(self.level); +368 +369 let packet_len = self.buf.len() - encode_start; +370 trace!(size = %packet_len, "wrote packet"); +371 self.qlog.finalize(packet_len); +372 conn.qlog.emit_packet_sent(self.qlog, now); +373 (packet_len, pad, self.sent_frames) +374 } +375 +376 /// The number of additional bytes the current packet would take up if it was finished now +377 /// +378 /// This will include any padding which is required to make the size large enough to be +379 /// encrypted correctly. +380 pub(super) fn predict_packet_end(&self) -> usize { +381 self.buf.len().max(self.min_size) + self.tag_len - self.buf.len() +382 } +383 +384 /// Returns the remaining space in the packet that can be taken up by QUIC frames +385 /// +386 /// This leaves space in the datagram for the cryptographic tag that needs to be written +387 /// when the packet is finished. +388 pub(super) fn frame_space_remaining(&self) -> usize { +389 let max_offset = self.buf.datagram_max_offset() - self.tag_len; +390 max_offset.saturating_sub(self.buf.len()) 391 } 392 -393 pub(crate) fn retransmits_mut(&mut self) -> &mut Retransmits { -394 self.sent_frames.retransmits_mut() +393 pub(crate) fn require_padding(&mut self) { +394 self.sent_frames.requires_padding = true; 395 } -396} -397 -398#[derive(Debug, Copy, Clone)] -399pub(super) enum PadDatagram { -400 /// Do not pad the datagram -401 No, -402 /// To a specific size -403 ToSize(u16), -404 /// Pad to the current MTU/segment size -405 /// -406 /// For the first datagram in a transmit the MTU is the same as the -407 /// [`TransmitBuf::segment_size`]. -408 ToSegmentSize, -409 /// Pad to [`MIN_INITIAL_SIZE`], the minimal QUIC MTU of 1200 bytes -410 ToMinMtu, -411} -412 -413impl std::ops::BitOrAssign for PadDatagram { -414 fn bitor_assign(&mut self, rhs: Self) { -415 *self = *self | rhs; -416 } -417} -418 -419impl std::ops::BitOr for PadDatagram { -420 type Output = Self; -421 -422 fn bitor(self, rhs: Self) -> Self::Output { -423 match (self, rhs) { -424 (Self::No, rhs) => rhs, -425 (Self::ToSize(size), Self::No) => Self::ToSize(size), -426 (Self::ToSize(a), Self::ToSize(b)) => Self::ToSize(a.max(b)), -427 (Self::ToSize(_), Self::ToSegmentSize) => Self::ToSegmentSize, -428 (Self::ToSize(_), Self::ToMinMtu) => Self::ToMinMtu, -429 (Self::ToSegmentSize, Self::No) => Self::ToSegmentSize, -430 (Self::ToSegmentSize, Self::ToSize(_)) => Self::ToSegmentSize, -431 (Self::ToSegmentSize, Self::ToSegmentSize) => Self::ToSegmentSize, -432 (Self::ToSegmentSize, Self::ToMinMtu) => Self::ToMinMtu, -433 (Self::ToMinMtu, _) => Self::ToMinMtu, -434 } -435 } -436}
\ No newline at end of file +396 +397 pub(crate) fn retransmits_mut(&mut self) -> &mut Retransmits { +398 self.sent_frames.retransmits_mut() +399 } +400} +401 +402#[derive(Debug, Copy, Clone)] +403pub(super) enum PadDatagram { +404 /// Do not pad the datagram +405 No, +406 /// To a specific size +407 ToSize(u16), +408 /// Pad to the current MTU/segment size +409 /// +410 /// For the first datagram in a transmit the MTU is the same as the +411 /// [`TransmitBuf::segment_size`]. +412 ToSegmentSize, +413 /// Pad to [`MIN_INITIAL_SIZE`], the minimal QUIC MTU of 1200 bytes +414 ToMinMtu, +415} +416 +417impl std::ops::BitOrAssign for PadDatagram { +418 fn bitor_assign(&mut self, rhs: Self) { +419 *self = *self | rhs; +420 } +421} +422 +423impl std::ops::BitOr for PadDatagram { +424 type Output = Self; +425 +426 fn bitor(self, rhs: Self) -> Self::Output { +427 match (self, rhs) { +428 (Self::No, rhs) => rhs, +429 (Self::ToSize(size), Self::No) => Self::ToSize(size), +430 (Self::ToSize(a), Self::ToSize(b)) => Self::ToSize(a.max(b)), +431 (Self::ToSize(_), Self::ToSegmentSize) => Self::ToSegmentSize, +432 (Self::ToSize(_), Self::ToMinMtu) => Self::ToMinMtu, +433 (Self::ToSegmentSize, Self::No) => Self::ToSegmentSize, +434 (Self::ToSegmentSize, Self::ToSize(_)) => Self::ToSegmentSize, +435 (Self::ToSegmentSize, Self::ToSegmentSize) => Self::ToSegmentSize, +436 (Self::ToSegmentSize, Self::ToMinMtu) => Self::ToMinMtu, +437 (Self::ToMinMtu, _) => Self::ToMinMtu, +438 } +439 } +440}

\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/paths.rs.html b/pr/451/docs/src/noq_proto/connection/paths.rs.html index f9e16d239..111e2c7af 100644 --- a/pr/451/docs/src/noq_proto/connection/paths.rs.html +++ b/pr/451/docs/src/noq_proto/connection/paths.rs.html @@ -15,1050 +15,1050 @@ 15 TransportErrorCode, VarInt, 16 coding::{self, Decodable, Encodable}, 17 congestion, -18 frame::ObservedAddr, -19}; -20 -21#[cfg(feature = "qlog")] -22use qlog::events::quic::RecoveryMetricsUpdated; -23 -24/// Id representing different paths when using multipath extension -25#[cfg_attr(test, derive(test_strategy::Arbitrary))] -26#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] -27pub struct PathId(pub(crate) u32); -28 -29impl std::hash::Hash for PathId { -30 fn hash<H: std::hash::Hasher>(&self, state: &mut H) { -31 state.write_u32(self.0); -32 } -33} -34 -35impl identity_hash::IdentityHashable for PathId {} -36 -37impl Decodable for PathId { -38 fn decode<B: bytes::Buf>(r: &mut B) -> coding::Result<Self> { -39 let v = VarInt::decode(r)?; -40 let v = u32::try_from(v.0).map_err(|_| coding::UnexpectedEnd)?; -41 Ok(Self(v)) -42 } -43} -44 -45impl Encodable for PathId { -46 fn encode<B: bytes::BufMut>(&self, w: &mut B) { -47 VarInt(self.0.into()).encode(w) -48 } -49} -50 -51impl PathId { -52 /// The maximum path ID allowed. -53 pub const MAX: Self = Self(u32::MAX); -54 -55 /// The 0 path id. -56 pub const ZERO: Self = Self(0); -57 -58 /// The number of bytes this [`PathId`] uses when encoded as a [`VarInt`] -59 pub(crate) const fn size(&self) -> usize { -60 VarInt(self.0 as u64).size() -61 } -62 -63 /// Saturating integer addition. Computes self + rhs, saturating at the numeric bounds instead -64 /// of overflowing. -65 pub fn saturating_add(self, rhs: impl Into<Self>) -> Self { -66 let rhs = rhs.into(); -67 let inner = self.0.saturating_add(rhs.0); -68 Self(inner) -69 } -70 -71 /// Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds -72 /// instead of overflowing. -73 pub fn saturating_sub(self, rhs: impl Into<Self>) -> Self { -74 let rhs = rhs.into(); -75 let inner = self.0.saturating_sub(rhs.0); -76 Self(inner) -77 } -78 -79 /// Get the next [`PathId`] -80 pub(crate) fn next(&self) -> Self { -81 self.saturating_add(Self(1)) -82 } -83 -84 /// Get the underlying u32 -85 pub(crate) fn as_u32(&self) -> u32 { -86 self.0 -87 } -88} -89 -90impl std::fmt::Display for PathId { -91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -92 self.0.fmt(f) -93 } -94} -95 -96impl<T: Into<u32>> From<T> for PathId { -97 fn from(source: T) -> Self { -98 Self(source.into()) -99 } -100} -101 -102/// State needed for a single path ID. -103/// -104/// A single path ID can migrate according to the rules in RFC9000 §9, either voluntary or -105/// involuntary. We need to keep the [`PathData`] of the previously used such path available -106/// in order to defend against migration attacks (see RFC9000 §9.3.1, §9.3.2 and §9.3.3) as -107/// well as to support path probing (RFC9000 §9.1). -108#[derive(Debug)] -109pub(super) struct PathState { -110 pub(super) data: PathData, -111 pub(super) prev: Option<(ConnectionId, PathData)>, -112} -113 -114impl PathState { -115 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned -116 pub(super) fn remove_in_flight(&mut self, packet: &SentPacket) { -117 // Visit known paths from newest to oldest to find the one `pn` was sent on -118 for path_data in [&mut self.data] -119 .into_iter() -120 .chain(self.prev.as_mut().map(|(_, data)| data)) -121 { -122 if path_data.remove_in_flight(packet) { -123 return; -124 } -125 } -126 } -127} -128 -129#[derive(Debug)] -130pub(super) struct SentChallengeInfo { -131 /// When was the challenge sent on the wire. -132 pub(super) sent_instant: Instant, -133 /// The 4-tuple on which this path challenge was sent. -134 pub(super) network_path: FourTuple, -135} -136 -137/// State of particular network path 4-tuple within a [`PacketNumberSpace`]. -138/// -139/// With QUIC-Multipath a path is identified by a [`PathId`] and it is possible to have -140/// multiple paths on the same 4-tuple. Furthermore a single QUIC-Multipath path can migrate -141/// to a different 4-tuple, in a similar manner as an RFC9000 connection can use "path -142/// migration" to move to a different 4-tuple. There are thus two states we keep for paths: -143/// -144/// - [`PacketNumberSpace`]: The state for a single packet number space, i.e. [`PathId`], -145/// which remains in place across path migrations to different 4-tuples. -146/// -147/// This is stored in [`PacketSpace::number_spaces`] indexed on [`PathId`]. -148/// -149/// - [`PathData`]: The state we keep for each unique 4-tuple within a space. Of note is -150/// that a single [`PathData`] can never belong to a different [`PacketNumberSpace`]. -151/// -152/// This is stored in [`Connection::paths`] indexed by the current [`PathId`] for which -153/// space it exists. Either as the primary 4-tuple or as the previous 4-tuple just after a -154/// migration. -155/// -156/// It follows that there might be several [`PathData`] structs for the same 4-tuple if -157/// several spaces are sharing the same 4-tuple. Note that during the handshake, the -158/// Initial, Handshake and Data spaces for [`PathId::ZERO`] all share the same [`PathData`]. -159/// -160/// [`PacketSpace::number_spaces`]: super::spaces::PacketSpace::number_spaces -161/// [`Connection::paths`]: super::Connection::paths -162#[derive(Debug)] -163pub(super) struct PathData { -164 pub(super) network_path: FourTuple, -165 pub(super) rtt: RttEstimator, -166 /// Whether we're enabling ECN on outgoing packets -167 pub(super) sending_ecn: bool, -168 /// Congestion controller state -169 pub(super) congestion: Box<dyn congestion::Controller>, -170 /// Pacing state -171 pub(super) pacing: Pacer, -172 /// Whether the last `poll_transmit_on_path` call yielded no data because there was -173 /// no outgoing application data. -174 /// -175 /// The RFC writes: -176 /// > When bytes in flight is smaller than the congestion window and sending is not pacing limited, -177 /// > the congestion window is underutilized. This can happen due to insufficient application data -178 /// > or flow control limits. When this occurs, the congestion window SHOULD NOT be increased in -179 /// > either slow start or congestion avoidance. -180 /// -181 /// (RFC9002, section 7.8) -182 /// -183 /// I.e. when app_limited is true, the congestion controller doesn't increase the congestion window. -184 pub(super) app_limited: bool, -185 -186 /// Path challenges sent (on the wire, on-path) that we didn't receive a path response for yet -187 on_path_challenges_unconfirmed: IntMap<u64, SentChallengeInfo>, -188 /// Whether to trigger sending another PATH_CHALLENGE in the next poll_transmit. +18 connection::{MAX_BACKOFF_EXPONENT, MAX_PTO_INTERVAL}, +19 frame::ObservedAddr, +20}; +21 +22#[cfg(feature = "qlog")] +23use qlog::events::quic::RecoveryMetricsUpdated; +24 +25/// Id representing different paths when using multipath extension +26#[cfg_attr(test, derive(test_strategy::Arbitrary))] +27#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] +28pub struct PathId(pub(crate) u32); +29 +30impl std::hash::Hash for PathId { +31 fn hash<H: std::hash::Hasher>(&self, state: &mut H) { +32 state.write_u32(self.0); +33 } +34} +35 +36impl Decodable for PathId { +37 fn decode<B: bytes::Buf>(r: &mut B) -> coding::Result<Self> { +38 let v = VarInt::decode(r)?; +39 let v = u32::try_from(v.0).map_err(|_| coding::UnexpectedEnd)?; +40 Ok(Self(v)) +41 } +42} +43 +44impl Encodable for PathId { +45 fn encode<B: bytes::BufMut>(&self, w: &mut B) { +46 VarInt(self.0.into()).encode(w) +47 } +48} +49 +50impl PathId { +51 /// The maximum path ID allowed. +52 pub const MAX: Self = Self(u32::MAX); +53 +54 /// The 0 path id. +55 pub const ZERO: Self = Self(0); +56 +57 /// The number of bytes this [`PathId`] uses when encoded as a [`VarInt`] +58 pub(crate) const fn size(&self) -> usize { +59 VarInt(self.0 as u64).size() +60 } +61 +62 /// Saturating integer addition. Computes self + rhs, saturating at the numeric bounds instead +63 /// of overflowing. +64 pub fn saturating_add(self, rhs: impl Into<Self>) -> Self { +65 let rhs = rhs.into(); +66 let inner = self.0.saturating_add(rhs.0); +67 Self(inner) +68 } +69 +70 /// Saturating integer subtraction. Computes self - rhs, saturating at the numeric bounds +71 /// instead of overflowing. +72 pub fn saturating_sub(self, rhs: impl Into<Self>) -> Self { +73 let rhs = rhs.into(); +74 let inner = self.0.saturating_sub(rhs.0); +75 Self(inner) +76 } +77 +78 /// Get the next [`PathId`] +79 pub(crate) fn next(&self) -> Self { +80 self.saturating_add(Self(1)) +81 } +82 +83 /// Get the underlying u32 +84 pub(crate) fn as_u32(&self) -> u32 { +85 self.0 +86 } +87} +88 +89impl std::fmt::Display for PathId { +90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +91 self.0.fmt(f) +92 } +93} +94 +95impl<T: Into<u32>> From<T> for PathId { +96 fn from(source: T) -> Self { +97 Self(source.into()) +98 } +99} +100 +101/// State needed for a single path ID. +102/// +103/// A single path ID can migrate according to the rules in RFC9000 §9, either voluntary or +104/// involuntary. We need to keep the [`PathData`] of the previously used such path available +105/// in order to defend against migration attacks (see RFC9000 §9.3.1, §9.3.2 and §9.3.3) as +106/// well as to support path probing (RFC9000 §9.1). +107#[derive(Debug)] +108pub(super) struct PathState { +109 pub(super) data: PathData, +110 pub(super) prev: Option<(ConnectionId, PathData)>, +111} +112 +113impl PathState { +114 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned +115 pub(super) fn remove_in_flight(&mut self, packet: &SentPacket) { +116 // Visit known paths from newest to oldest to find the one `pn` was sent on +117 for path_data in [&mut self.data] +118 .into_iter() +119 .chain(self.prev.as_mut().map(|(_, data)| data)) +120 { +121 if path_data.remove_in_flight(packet) { +122 return; +123 } +124 } +125 } +126} +127 +128#[derive(Debug)] +129pub(super) struct SentChallengeInfo { +130 /// When was the challenge sent on the wire. +131 pub(super) sent_instant: Instant, +132 /// The 4-tuple on which this path challenge was sent. +133 pub(super) network_path: FourTuple, +134} +135 +136/// State of particular network path 4-tuple within a [`PacketNumberSpace`]. +137/// +138/// With QUIC-Multipath a path is identified by a [`PathId`] and it is possible to have +139/// multiple paths on the same 4-tuple. Furthermore a single QUIC-Multipath path can migrate +140/// to a different 4-tuple, in a similar manner as an RFC9000 connection can use "path +141/// migration" to move to a different 4-tuple. There are thus two states we keep for paths: +142/// +143/// - [`PacketNumberSpace`]: The state for a single packet number space, i.e. [`PathId`], +144/// which remains in place across path migrations to different 4-tuples. +145/// +146/// This is stored in [`PacketSpace::number_spaces`] indexed on [`PathId`]. +147/// +148/// - [`PathData`]: The state we keep for each unique 4-tuple within a space. Of note is +149/// that a single [`PathData`] can never belong to a different [`PacketNumberSpace`]. +150/// +151/// This is stored in [`Connection::paths`] indexed by the current [`PathId`] for which +152/// space it exists. Either as the primary 4-tuple or as the previous 4-tuple just after a +153/// migration. +154/// +155/// It follows that there might be several [`PathData`] structs for the same 4-tuple if +156/// several spaces are sharing the same 4-tuple. Note that during the handshake, the +157/// Initial, Handshake and Data spaces for [`PathId::ZERO`] all share the same [`PathData`]. +158/// +159/// [`PacketSpace::number_spaces`]: super::spaces::PacketSpace::number_spaces +160/// [`Connection::paths`]: super::Connection::paths +161#[derive(Debug)] +162pub(super) struct PathData { +163 pub(super) network_path: FourTuple, +164 pub(super) rtt: RttEstimator, +165 /// Whether we're enabling ECN on outgoing packets +166 pub(super) sending_ecn: bool, +167 /// Congestion controller state +168 pub(super) congestion: Box<dyn congestion::Controller>, +169 /// Pacing state +170 pub(super) pacing: Pacer, +171 /// Whether the last `poll_transmit_on_path` call yielded no data because there was +172 /// no outgoing application data. +173 /// +174 /// The RFC writes: +175 /// > When bytes in flight is smaller than the congestion window and sending is not pacing limited, +176 /// > the congestion window is underutilized. This can happen due to insufficient application data +177 /// > or flow control limits. When this occurs, the congestion window SHOULD NOT be increased in +178 /// > either slow start or congestion avoidance. +179 /// +180 /// (RFC9002, section 7.8) +181 /// +182 /// I.e. when app_limited is true, the congestion controller doesn't increase the congestion window. +183 pub(super) app_limited: bool, +184 +185 /// Whether to trigger sending another PATH_CHALLENGE in the next poll_transmit. +186 /// +187 /// This is picked up by [`super::Connection::space_can_send`]. These are **not** +188 /// retransmittable, which is why they are not part of the `PathRetransmits`. 189 /// -190 /// This is picked up by [`super::Connection::space_can_send`]. -191 /// -192 /// Only used for RFC9000-style path migration and multipath path validation (for opening). -193 /// -194 /// This is **not used** for n0 nat traversal challenge sending. -195 pub(super) pending_on_path_challenge: bool, -196 /// Pending responses to PATH_CHALLENGE frames -197 pub(super) path_responses: PathResponses, -198 /// Whether we're certain the peer can both send and receive on this address -199 /// -200 /// Initially equal to `use_stateless_retry` for servers, and becomes false again on every -201 /// migration. Always true for clients. -202 pub(super) validated: bool, -203 /// Total size of all UDP datagrams sent on this path -204 pub(super) total_sent: u64, -205 /// Total size of all UDP datagrams received on this path -206 pub(super) total_recvd: u64, -207 /// The state of the MTU discovery process -208 pub(super) mtud: MtuDiscovery, -209 /// Packet number of the first packet sent after an RTT sample was collected on this path -210 /// -211 /// Used in persistent congestion determination. -212 pub(super) first_packet_after_rtt_sample: Option<(SpaceKind, u64)>, -213 /// The in-flight packets and bytes +190 /// Only used for **on-path** challenges, like RFC9000-style path migration and +191 /// multipath path validation (for opening). +192 /// +193 /// This is **not used** for n0 nat traversal challenge sending, which is off-path. +194 pub(super) pending_challenge: bool, +195 /// On-path path challenges sent that we didn't receive a path response for yet. +196 unconfirmed_challenges: IntMap<u64, SentChallengeInfo>, +197 /// How often we've deemed a path challenge to be lost. +198 /// +199 /// Similar to [`Self::pto_count`], but for on-path path challenges. +200 /// Used to calculate exponential backoff for retrying path challenges. +201 pub(super) lost_challenge_count: u32, +202 /// Whether we're certain the peer can both send and receive on this address +203 /// +204 /// Initially equal to `use_stateless_retry` for servers, and becomes false again on every +205 /// migration. Always true for clients. +206 pub(super) validated: bool, +207 /// Total size of all UDP datagrams sent on this path +208 pub(super) total_sent: u64, +209 /// Total size of all UDP datagrams received on this path +210 pub(super) total_recvd: u64, +211 /// The state of the MTU discovery process +212 pub(super) mtud: MtuDiscovery, +213 /// Packet number of the first packet sent after an RTT sample was collected on this path 214 /// -215 /// Note that this is across all spaces on this path -216 pub(super) in_flight: InFlight, -217 /// Whether this path has had it's remote address reported back to the peer. This only happens -218 /// if both peers agree to so based on their transport parameters. -219 pub(super) observed_addr_sent: bool, -220 /// Observed address frame with the largest sequence number received from the peer on this path. -221 pub(super) last_observed_addr_report: Option<ObservedAddr>, -222 /// The QUIC-MULTIPATH path status -223 pub(super) status: PathStatusState, -224 /// Number of the first packet sent on this path -225 /// -226 /// With RFC9000 §9 style migration (i.e. not multipath) the PathId does not change and -227 /// hence packet numbers continue. This is used to determine whether a packet was sent -228 /// on such an earlier path. Insufficient to determine if a packet was sent on a later -229 /// path. -230 first_packet: Option<u64>, -231 /// The number of times a tail-loss probe has been sent without receiving an ack. -232 /// -233 /// This is incremented by one every time the [`LossDetection`] timer fires because a -234 /// tail-loss probe needs to be sent. Once an acknowledgement for a packet is received -235 /// again it is reset to 0. Used to compute the PTO duration. -236 /// -237 /// [`LossDetection`]: super::timer::PathTimer::LossDetection -238 pub(super) pto_count: u32, -239 -240 // -241 // Per-path idle & keep alive -242 // -243 /// Idle timeout for the path -244 /// -245 /// If expired, the path will be abandoned. This is different from the connection-wide -246 /// idle timeout which closes the connection if expired. -247 pub(super) idle_timeout: Option<Duration>, -248 /// Keep alives to send on this path -249 /// -250 /// There is also a connection-level keep alive configured in the -251 /// [`TransportParameters`]. This triggers activity on any path which can keep the -252 /// connection alive. -253 /// -254 /// [`TransportParameters`]: crate::transport_parameters::TransportParameters -255 pub(super) keep_alive: Option<Duration>, -256 /// Whether to reset the idle timer when the next ack-eliciting packet is sent. -257 /// -258 /// Whenever we receive an authenticated packet the connection and path idle timers are -259 /// reset if a maximum idle timeout was negotiated. However on the first ack-eliciting -260 /// packet *sent* after this the idle timer also needs to be reset to avoid the idle -261 /// timer firing while the sent packet is in-fight. See -262 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.1>. -263 pub(super) permit_idle_reset: bool, -264 -265 /// Whether the path has already been considered opened from an application perspective. -266 /// -267 /// This means, for paths other than the original [`PathId::ZERO`], a first path challenge has -268 /// been responded to, regardless of the initial validation status of the path. This state is -269 /// irreversible, since it's not affected by the path being closed. -270 /// -271 /// Sending a PATH_CHALLENGE and receiving a valid response before the application is informed -272 /// of the path, is a way to ensure the path is usable before it is reported. This is not -273 /// required by the spec, and in the future might be changed for simply requiring a first ack'd -274 /// packet. -275 pub(super) open_status: OpenStatus, -276 -277 /// Whether we're currently draining the path after having abandoned it. -278 /// -279 /// This should only be true when a path discard timer is armed, and after the path was -280 /// abandoned (and added to the abandoned_paths set). +215 /// Used in persistent congestion determination. +216 pub(super) first_packet_after_rtt_sample: Option<(SpaceKind, u64)>, +217 /// The in-flight packets and bytes +218 /// +219 /// Note that this is across all spaces on this path +220 pub(super) in_flight: InFlight, +221 /// Queue of data that must be sent over this specific [`PathData::generation`] path. +222 pub(super) pending: PathRetransmits, +223 /// Observed address frame with the largest sequence number received from the peer on this path. +224 pub(super) last_observed_addr_report: Option<ObservedAddr>, +225 /// The QUIC-MULTIPATH path status +226 pub(super) status: PathStatusState, +227 /// Number of the first packet sent on this path +228 /// +229 /// With RFC9000 §9 style migration (i.e. not multipath) the PathId does not change and +230 /// hence packet numbers continue. This is used to determine whether a packet was sent +231 /// on such an earlier path. Insufficient to determine if a packet was sent on a later +232 /// path. +233 first_packet: Option<u64>, +234 /// The number of times a tail-loss probe has been sent without receiving an ack. +235 /// +236 /// This is incremented by one every time the [`LossDetection`] timer fires because a +237 /// tail-loss probe needs to be sent. Once an acknowledgement for a packet is received +238 /// again it is reset to 0. Used to compute the PTO duration. +239 /// +240 /// [`LossDetection`]: super::timer::PathTimer::LossDetection +241 pub(super) pto_count: u32, +242 +243 // +244 // Per-path idle & keep alive +245 // +246 /// Idle timeout for the path +247 /// +248 /// If expired, the path will be abandoned. This is different from the connection-wide +249 /// idle timeout which closes the connection if expired. +250 pub(super) idle_timeout: Option<Duration>, +251 /// Keep alives to send on this path +252 /// +253 /// There is also a connection-level keep alive configured in the +254 /// [`TransportParameters`]. This triggers activity on any path which can keep the +255 /// connection alive. +256 /// +257 /// [`TransportParameters`]: crate::transport_parameters::TransportParameters +258 pub(super) keep_alive: Option<Duration>, +259 /// Whether to reset the idle timer when the next ack-eliciting packet is sent. +260 /// +261 /// Whenever we receive an authenticated packet the connection and path idle timers are +262 /// reset if a maximum idle timeout was negotiated. However on the first ack-eliciting +263 /// packet *sent* after this the idle timer also needs to be reset to avoid the idle +264 /// timer firing while the sent packet is in-fight. See +265 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.1>. +266 pub(super) permit_idle_reset: bool, +267 +268 /// Whether we're currently draining the path after having abandoned it. +269 /// +270 /// This should only be true when a path discard timer is armed, and after the path was +271 /// abandoned (and added to the abandoned_paths set). +272 /// +273 /// This will only ever be set from false to true. +274 pub(super) draining: bool, +275 +276 /// Snapshot of the qlog recovery metrics +277 #[cfg(feature = "qlog")] +278 recovery_metrics: RecoveryMetrics, +279 +280 /// Tag uniquely identifying a path in a connection. 281 /// -282 /// This will only ever be set from false to true. -283 pub(super) draining: bool, -284 -285 /// Snapshot of the qlog recovery metrics -286 #[cfg(feature = "qlog")] -287 recovery_metrics: RecoveryMetrics, -288 -289 /// Tag uniquely identifying a path in a connection. -290 /// -291 /// When a migration happens on the same [`PathId`] we still detect a change in the -292 /// 4-tuple and generate a new [`PathData`] for it. Each such generation has a unique -293 /// value to keep track of which 4-tuple a packet belonged to. -294 generation: u64, -295} -296 -297impl PathData { -298 pub(super) fn new( -299 network_path: FourTuple, -300 allow_mtud: bool, -301 peer_max_udp_payload_size: Option<u16>, -302 generation: u64, -303 now: Instant, -304 config: &TransportConfig, -305 ) -> Self { -306 let congestion = config -307 .congestion_controller_factory -308 .clone() -309 .build(now, config.get_initial_mtu()); -310 Self { -311 network_path, -312 rtt: RttEstimator::new(config.initial_rtt), -313 sending_ecn: true, -314 pacing: Pacer::new( -315 config.initial_rtt, -316 congestion.initial_window(), -317 config.get_initial_mtu(), -318 config.max_outgoing_bytes_per_second, -319 now, -320 ), -321 congestion, -322 app_limited: false, -323 on_path_challenges_unconfirmed: Default::default(), -324 pending_on_path_challenge: false, -325 path_responses: PathResponses::default(), -326 validated: false, -327 total_sent: 0, -328 total_recvd: 0, -329 mtud: config -330 .mtu_discovery_config -331 .as_ref() -332 .filter(|_| allow_mtud) -333 .map_or_else( -334 || MtuDiscovery::disabled(config.get_initial_mtu(), config.min_mtu), -335 |mtud_config| { -336 MtuDiscovery::new( -337 config.get_initial_mtu(), -338 config.min_mtu, -339 peer_max_udp_payload_size, -340 mtud_config.clone(), -341 ) -342 }, -343 ), -344 first_packet_after_rtt_sample: None, -345 in_flight: InFlight::new(), -346 observed_addr_sent: false, -347 last_observed_addr_report: None, -348 status: Default::default(), -349 first_packet: None, -350 pto_count: 0, -351 idle_timeout: config.default_path_max_idle_timeout, -352 keep_alive: config.default_path_keep_alive_interval, -353 permit_idle_reset: true, -354 open_status: OpenStatus::default(), -355 draining: false, -356 #[cfg(feature = "qlog")] -357 recovery_metrics: RecoveryMetrics::default(), -358 generation, -359 } -360 } -361 -362 /// Create a new path from a previous one. -363 /// -364 /// This should only be called when migrating paths. -365 pub(super) fn from_previous( -366 network_path: FourTuple, -367 prev: &Self, -368 generation: u64, -369 now: Instant, -370 ) -> Self { -371 let congestion = prev.congestion.clone_box(); -372 let smoothed_rtt = prev.rtt.get(); -373 Self { -374 network_path, -375 rtt: prev.rtt, -376 pacing: Pacer::new( -377 smoothed_rtt, -378 congestion.window(), -379 prev.current_mtu(), -380 prev.pacing.max_bytes_per_second(), -381 now, -382 ), -383 sending_ecn: true, -384 congestion, -385 app_limited: false, -386 on_path_challenges_unconfirmed: Default::default(), -387 pending_on_path_challenge: false, -388 path_responses: PathResponses::default(), -389 validated: false, -390 total_sent: 0, -391 total_recvd: 0, -392 mtud: prev.mtud.clone(), -393 first_packet_after_rtt_sample: prev.first_packet_after_rtt_sample, -394 in_flight: InFlight::new(), -395 observed_addr_sent: false, -396 last_observed_addr_report: None, -397 status: prev.status.clone(), -398 first_packet: None, -399 pto_count: 0, -400 idle_timeout: prev.idle_timeout, -401 keep_alive: prev.keep_alive, -402 permit_idle_reset: true, -403 open_status: OpenStatus::default(), -404 draining: false, -405 #[cfg(feature = "qlog")] -406 recovery_metrics: prev.recovery_metrics.clone(), -407 generation, -408 } +282 /// When a migration happens on the same [`PathId`] we still detect a change in the +283 /// 4-tuple and generate a new [`PathData`] for it. Each such generation has a unique +284 /// value to keep track of which 4-tuple a packet belonged to. +285 generation: u64, +286} +287 +288impl PathData { +289 pub(super) fn new( +290 network_path: FourTuple, +291 allow_mtud: bool, +292 peer_max_udp_payload_size: Option<u16>, +293 generation: u64, +294 now: Instant, +295 config: &TransportConfig, +296 ) -> Self { +297 let congestion = config +298 .congestion_controller_factory +299 .clone() +300 .build(now, config.get_initial_mtu()); +301 Self { +302 network_path, +303 rtt: RttEstimator::new(config.initial_rtt), +304 sending_ecn: true, +305 pacing: Pacer::new( +306 config.initial_rtt, +307 congestion.initial_window(), +308 config.get_initial_mtu(), +309 config.max_outgoing_bytes_per_second, +310 now, +311 ), +312 congestion, +313 app_limited: false, +314 unconfirmed_challenges: Default::default(), +315 lost_challenge_count: 0, +316 pending_challenge: false, +317 validated: false, +318 total_sent: 0, +319 total_recvd: 0, +320 mtud: config +321 .mtu_discovery_config +322 .as_ref() +323 .filter(|_| allow_mtud) +324 .map_or_else( +325 || MtuDiscovery::disabled(config.get_initial_mtu(), config.min_mtu), +326 |mtud_config| { +327 MtuDiscovery::new( +328 config.get_initial_mtu(), +329 config.min_mtu, +330 peer_max_udp_payload_size, +331 mtud_config.clone(), +332 ) +333 }, +334 ), +335 first_packet_after_rtt_sample: None, +336 in_flight: InFlight::new(), +337 pending: PathRetransmits::default(), +338 last_observed_addr_report: None, +339 status: Default::default(), +340 first_packet: None, +341 pto_count: 0, +342 idle_timeout: config.default_path_max_idle_timeout, +343 keep_alive: config.default_path_keep_alive_interval, +344 permit_idle_reset: true, +345 draining: false, +346 #[cfg(feature = "qlog")] +347 recovery_metrics: RecoveryMetrics::default(), +348 generation, +349 } +350 } +351 +352 /// Create a new path from a previous one. +353 /// +354 /// This should only be called when migrating paths. +355 pub(super) fn from_previous( +356 network_path: FourTuple, +357 prev: &Self, +358 generation: u64, +359 now: Instant, +360 ) -> Self { +361 let congestion = prev.congestion.clone_box(); +362 let smoothed_rtt = prev.rtt.get(); +363 Self { +364 network_path, +365 rtt: prev.rtt, +366 pacing: Pacer::new( +367 smoothed_rtt, +368 congestion.window(), +369 prev.current_mtu(), +370 prev.pacing.max_bytes_per_second(), +371 now, +372 ), +373 sending_ecn: true, +374 congestion, +375 app_limited: false, +376 unconfirmed_challenges: Default::default(), +377 lost_challenge_count: 0, +378 pending_challenge: false, +379 validated: false, +380 total_sent: 0, +381 total_recvd: 0, +382 mtud: prev.mtud.clone(), +383 first_packet_after_rtt_sample: prev.first_packet_after_rtt_sample, +384 in_flight: InFlight::new(), +385 pending: PathRetransmits::default(), +386 last_observed_addr_report: None, +387 status: prev.status.clone(), +388 first_packet: None, +389 pto_count: 0, +390 idle_timeout: prev.idle_timeout, +391 keep_alive: prev.keep_alive, +392 permit_idle_reset: true, +393 draining: false, +394 #[cfg(feature = "qlog")] +395 recovery_metrics: prev.recovery_metrics.clone(), +396 generation, +397 } +398 } +399 +400 /// Whether we're in the process of validating this path with PATH_CHALLENGEs +401 pub(super) fn is_validating_path(&self) -> bool { +402 !self.unconfirmed_challenges.is_empty() || self.pending_challenge +403 } +404 +405 /// Indicates whether we're a server that hasn't validated the peer's address and hasn't +406 /// received enough data from the peer to permit sending `bytes_to_send` additional bytes +407 pub(super) fn anti_amplification_blocked(&self, bytes_to_send: u64) -> bool { +408 !self.validated && self.total_recvd * 3 < self.total_sent + bytes_to_send 409 } 410 -411 /// Whether we're in the process of validating this path with PATH_CHALLENGEs -412 pub(super) fn is_validating_path(&self) -> bool { -413 !self.on_path_challenges_unconfirmed.is_empty() || self.pending_on_path_challenge +411 /// Returns the path's current MTU +412 pub(super) fn current_mtu(&self) -> u16 { +413 self.mtud.current_mtu() 414 } 415 -416 /// Indicates whether we're a server that hasn't validated the peer's address and hasn't -417 /// received enough data from the peer to permit sending `bytes_to_send` additional bytes -418 pub(super) fn anti_amplification_blocked(&self, bytes_to_send: u64) -> bool { -419 !self.validated && self.total_recvd * 3 < self.total_sent + bytes_to_send -420 } -421 -422 /// Returns the path's current MTU -423 pub(super) fn current_mtu(&self) -> u16 { -424 self.mtud.current_mtu() +416 /// Account for transmission of `packet` with number `pn` in `space` +417 pub(super) fn sent(&mut self, pn: u64, packet: SentPacket, space: &mut PacketNumberSpace) { +418 self.in_flight.insert(&packet); +419 if self.first_packet.is_none() { +420 self.first_packet = Some(pn); +421 } +422 if let Some(forgotten) = space.sent(pn, packet) { +423 self.remove_in_flight(&forgotten); +424 } 425 } 426 -427 /// Account for transmission of `packet` with number `pn` in `space` -428 pub(super) fn sent(&mut self, pn: u64, packet: SentPacket, space: &mut PacketNumberSpace) { -429 self.in_flight.insert(&packet); -430 if self.first_packet.is_none() { -431 self.first_packet = Some(pn); -432 } -433 if let Some(forgotten) = space.sent(pn, packet) { -434 self.remove_in_flight(&forgotten); -435 } -436 } -437 -438 pub(super) fn record_path_challenge_sent( -439 &mut self, -440 now: Instant, -441 token: u64, -442 network_path: FourTuple, -443 ) { -444 let info = SentChallengeInfo { -445 sent_instant: now, -446 network_path, -447 }; -448 debug_assert_eq!(network_path, self.network_path); -449 self.on_path_challenges_unconfirmed.insert(token, info); -450 } -451 -452 /// Remove `packet` with number `pn` from this path's congestion control counters, or return -453 /// `false` if `pn` was sent before this path was established. -454 pub(super) fn remove_in_flight(&mut self, packet: &SentPacket) -> bool { -455 if packet.path_generation != self.generation { -456 return false; -457 } -458 self.in_flight.remove(packet); -459 true -460 } -461 -462 /// Increment the total size of sent UDP datagrams -463 pub(super) fn inc_total_sent(&mut self, inc: u64) { -464 self.total_sent = self.total_sent.saturating_add(inc); -465 if !self.validated { -466 trace!( -467 network_path = %self.network_path, -468 anti_amplification_budget = %(self.total_recvd * 3).saturating_sub(self.total_sent), -469 "anti amplification budget decreased" -470 ); -471 } -472 } -473 -474 /// Increment the total size of received UDP datagrams -475 pub(super) fn inc_total_recvd(&mut self, inc: u64) { -476 self.total_recvd = self.total_recvd.saturating_add(inc); -477 if !self.validated { -478 trace!( -479 network_path = %self.network_path, -480 anti_amplification_budget = %(self.total_recvd * 3).saturating_sub(self.total_sent), -481 "anti amplification budget increased" -482 ); -483 } -484 } -485 -486 /// The earliest time at which an on-path challenge we sent is considered lost. -487 pub(super) fn earliest_on_path_expiring_challenge(&self) -> Option<Instant> { -488 if self.on_path_challenges_unconfirmed.is_empty() { -489 return None; -490 } -491 let pto = self.rtt.pto_base(); -492 self.on_path_challenges_unconfirmed -493 .values() -494 .map(|info| info.sent_instant + pto) -495 .min() -496 } -497 -498 /// Handle receiving a PATH_RESPONSE. -499 pub(super) fn on_path_response_received( -500 &mut self, -501 now: Instant, -502 token: u64, -503 network_path: FourTuple, -504 ) -> OnPathResponseReceived { -505 // > § 8.2.3 -506 // > Path validation succeeds when a PATH_RESPONSE frame is received that contains the -507 // > data that was sent in a previous PATH_CHALLENGE frame. A PATH_RESPONSE frame -508 // > received on any network path validates the path on which the PATH_CHALLENGE was -509 // > sent. -510 // -511 // At this point we have three potentially different network paths: -512 // - current network path (`Self::network_path`) -513 // - network path used to send the path challenge (`SentChallengeInfo::network_path`) -514 // - network path over which the response arrived (`network_path`) -515 // -516 // As per the spec, this only validates the network path on which this was *sent*. -517 match self.on_path_challenges_unconfirmed.remove(&token) { -518 // Response to an on-path PathChallenge that validates this path. -519 // The sent path should match the current path. However, it's possible that the -520 // challenge was sent when no local_ip was known. This case is allowed as well. -521 Some(info) if info.network_path.is_probably_same_path(&self.network_path) => { -522 self.network_path.update_local_if_same_remote(&network_path); -523 let sent_instant = info.sent_instant; -524 if !std::mem::replace(&mut self.validated, true) { -525 trace!("new path validated"); -526 } -527 // Clear any other on-path sent challenges and stop sending new ones. -528 self.on_path_challenges_unconfirmed.clear(); -529 self.pending_on_path_challenge = false; -530 -531 // This RTT can only be used for the initial RTT, not as a normal -532 // sample: https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-2. -533 let rtt = now.saturating_duration_since(sent_instant); -534 self.rtt.reset_initial_rtt(rtt); -535 -536 let prev_status = std::mem::replace(&mut self.open_status, OpenStatus::Informed); -537 OnPathResponseReceived::OnPath { -538 was_open: matches!(prev_status, OpenStatus::Informed), -539 } -540 } -541 // Response to an on-path PathChallenge that does not validate this path. -542 Some(info) => { -543 // This is a valid path response, but this validates a 4-tuple we no longer -544 // have in use. Keep only sent challenges for the current path. -545 self.on_path_challenges_unconfirmed -546 .retain(|_token, i| i.network_path == self.network_path); -547 -548 // If there are no challenges for the current path, schedule one -549 if !self.on_path_challenges_unconfirmed.is_empty() { -550 self.pending_on_path_challenge = true; -551 } -552 OnPathResponseReceived::Ignored { -553 sent_on: info.network_path, -554 current_path: self.network_path, -555 } -556 } -557 None => { -558 // Response to an unknown PathChallenge. Does not indicate failure. -559 OnPathResponseReceived::Unknown -560 } -561 } -562 } -563 -564 /// Removes all on-path challenges we remember and cancels sending new on-path challenges. -565 pub(super) fn reset_on_path_challenges(&mut self) { -566 self.on_path_challenges_unconfirmed.clear(); -567 self.pending_on_path_challenge = false; -568 } -569 -570 #[cfg(feature = "qlog")] -571 pub(super) fn qlog_recovery_metrics( -572 &mut self, -573 path_id: PathId, -574 ) -> Option<RecoveryMetricsUpdated> { -575 let controller_metrics = self.congestion.metrics(); -576 -577 let metrics = RecoveryMetrics { -578 min_rtt: Some(self.rtt.min), -579 smoothed_rtt: Some(self.rtt.get()), -580 latest_rtt: Some(self.rtt.latest), -581 rtt_variance: Some(self.rtt.var), -582 pto_count: Some(self.pto_count), -583 bytes_in_flight: Some(self.in_flight.bytes), -584 packets_in_flight: Some(self.in_flight.ack_eliciting), -585 -586 congestion_window: Some(controller_metrics.congestion_window), -587 ssthresh: controller_metrics.ssthresh, -588 pacing_rate: controller_metrics.pacing_rate, -589 }; -590 -591 let event = metrics.to_qlog_event(path_id, &self.recovery_metrics); -592 self.recovery_metrics = metrics; -593 event -594 } -595 -596 /// Return how long we need to wait before sending `bytes_to_send` -597 /// -598 /// See [`Pacer::delay`]. -599 pub(super) fn pacing_delay(&mut self, bytes_to_send: u64, now: Instant) -> Option<Duration> { -600 let smoothed_rtt = self.rtt.get(); -601 let metrics = self.congestion.metrics(); -602 self.pacing.delay( -603 smoothed_rtt, -604 bytes_to_send, -605 self.current_mtu(), -606 metrics.congestion_window, -607 now, -608 metrics.send_quantum, -609 metrics.pacing_rate, -610 ) -611 } -612 -613 /// Updates the last observed address report received on this path. -614 /// -615 /// If the address was updated, it's returned to be informed to the application. -616 #[must_use = "updated observed address must be reported to the application"] -617 pub(super) fn update_observed_addr_report( -618 &mut self, -619 observed: ObservedAddr, -620 ) -> Option<SocketAddr> { -621 match self.last_observed_addr_report.as_mut() { -622 Some(prev) => { -623 if prev.seq_no >= observed.seq_no { -624 // frames that do not increase the sequence number on this path are ignored -625 None -626 } else if prev.ip == observed.ip && prev.port == observed.port { -627 // keep track of the last seq_no but do not report the address as updated -628 prev.seq_no = observed.seq_no; -629 None -630 } else { -631 let addr = observed.socket_addr(); -632 self.last_observed_addr_report = Some(observed); -633 Some(addr) -634 } -635 } -636 None => { -637 let addr = observed.socket_addr(); -638 self.last_observed_addr_report = Some(observed); -639 Some(addr) -640 } -641 } +427 pub(super) fn record_path_challenge_sent( +428 &mut self, +429 now: Instant, +430 token: u64, +431 network_path: FourTuple, +432 ) { +433 let info = SentChallengeInfo { +434 sent_instant: now, +435 network_path, +436 }; +437 debug_assert_eq!(network_path, self.network_path); +438 self.unconfirmed_challenges.insert(token, info); +439 } +440 +441 /// Remove `packet` with number `pn` from this path's congestion control counters, or return +442 /// `false` if `pn` was sent before this path was established. +443 pub(super) fn remove_in_flight(&mut self, packet: &SentPacket) -> bool { +444 if packet.path_generation != self.generation { +445 return false; +446 } +447 self.in_flight.remove(packet); +448 true +449 } +450 +451 /// Increment the total size of sent UDP datagrams +452 pub(super) fn inc_total_sent(&mut self, inc: u64) { +453 self.total_sent = self.total_sent.saturating_add(inc); +454 if !self.validated { +455 trace!( +456 network_path = %self.network_path, +457 anti_amplification_budget = %(self.total_recvd * 3).saturating_sub(self.total_sent), +458 "anti amplification budget decreased" +459 ); +460 } +461 } +462 +463 /// Increment the total size of received UDP datagrams +464 pub(super) fn inc_total_recvd(&mut self, inc: u64) { +465 self.total_recvd = self.total_recvd.saturating_add(inc); +466 if !self.validated { +467 trace!( +468 network_path = %self.network_path, +469 anti_amplification_budget = %(self.total_recvd * 3).saturating_sub(self.total_sent), +470 "anti amplification budget increased" +471 ); +472 } +473 } +474 +475 /// The earliest time at which an on-path challenge we sent is considered lost. +476 pub(super) fn earliest_on_path_expiring_challenge(&self) -> Option<Instant> { +477 if self.unconfirmed_challenges.is_empty() { +478 return None; +479 } +480 let duration = self.on_path_challenge_expiry(); +481 self.unconfirmed_challenges +482 .values() +483 .map(|info| info.sent_instant + duration) +484 .min() +485 } +486 +487 pub(super) fn on_path_challenge_expiry(&self) -> Duration { +488 let backoff = 2u32.pow(self.lost_challenge_count.min(MAX_BACKOFF_EXPONENT)); +489 let duration = self.rtt.pto_base() * backoff; +490 duration.min(MAX_PTO_INTERVAL) +491 } +492 +493 /// Handle receiving a PATH_RESPONSE. +494 pub(super) fn on_path_response_received( +495 &mut self, +496 now: Instant, +497 token: u64, +498 ) -> OnPathResponseReceived { +499 // > § 8.2.3 +500 // > Path validation succeeds when a PATH_RESPONSE frame is received that contains the +501 // > data that was sent in a previous PATH_CHALLENGE frame. A PATH_RESPONSE frame +502 // > received on any network path validates the path on which the PATH_CHALLENGE was +503 // > sent. +504 // +505 // At this point we have three potentially different network paths: +506 // - current network path (`Self::network_path`) +507 // - network path used to send the path challenge (`SentChallengeInfo::network_path`) +508 // - network path over which the response arrived (not needed) +509 // +510 // As per above spec quote, this only validates the network path on which this was +511 // *sent*, regardless of the path on which it was received in order to protect +512 // against off-path packet forwarding attacks. +513 match self.unconfirmed_challenges.remove(&token) { +514 // Response to an on-path PathChallenge that validates this path. +515 // The sent path should match the current path. However, it's possible that the +516 // challenge was sent when no local_ip was known. This case is allowed as well. +517 Some(info) if info.network_path.is_probably_same_path(&self.network_path) => { +518 // Do not update or set the self.network_path.local_ip: +519 // Connection::process_payload handles this later when required. We can mark +520 // the path as validated though, because for a change in local_ip only we do +521 // not need to re-validate the path. +522 let sent_instant = info.sent_instant; +523 if !std::mem::replace(&mut self.validated, true) { +524 trace!("new path validated"); +525 } +526 // Clear any other on-path sent challenges and stop sending new ones. +527 self.reset_on_path_challenges(); +528 +529 // This RTT can only be used for the initial RTT, not as a normal +530 // sample: https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-2. +531 let rtt = now.saturating_duration_since(sent_instant); +532 self.rtt.reset_initial_rtt(rtt); +533 +534 OnPathResponseReceived::OnPath +535 } +536 // Response to an on-path PathChallenge that does not validate this path. +537 Some(info) => { +538 // This is a valid path response, but this validates a 4-tuple we no longer +539 // have in use. Keep only sent challenges for the current path. +540 self.unconfirmed_challenges +541 .retain(|_token, i| i.network_path == self.network_path); +542 +543 // If there are no challenges for the current path, schedule one +544 if !self.unconfirmed_challenges.is_empty() { +545 self.pending_challenge = true; +546 } +547 OnPathResponseReceived::Ignored { +548 sent_on: info.network_path, +549 current_path: self.network_path, +550 } +551 } +552 None => { +553 // Response to an unknown PathChallenge. Does not indicate failure. +554 OnPathResponseReceived::Unknown +555 } +556 } +557 } +558 +559 /// Removes all on-path challenges we remember and cancels sending new on-path challenges. +560 pub(super) fn reset_on_path_challenges(&mut self) { +561 self.unconfirmed_challenges.clear(); +562 self.pending_challenge = false; +563 self.lost_challenge_count = 0; +564 } +565 +566 #[cfg(feature = "qlog")] +567 pub(super) fn qlog_recovery_metrics( +568 &mut self, +569 path_id: PathId, +570 ) -> Option<RecoveryMetricsUpdated> { +571 let controller_metrics = self.congestion.metrics(); +572 +573 let metrics = RecoveryMetrics { +574 min_rtt: Some(self.rtt.min), +575 smoothed_rtt: Some(self.rtt.get()), +576 latest_rtt: Some(self.rtt.latest), +577 rtt_variance: Some(self.rtt.var), +578 pto_count: Some(self.pto_count), +579 bytes_in_flight: Some(self.in_flight.bytes), +580 packets_in_flight: Some(self.in_flight.ack_eliciting), +581 +582 congestion_window: Some(controller_metrics.congestion_window), +583 ssthresh: controller_metrics.ssthresh, +584 pacing_rate: controller_metrics.pacing_rate, +585 }; +586 +587 let event = metrics.to_qlog_event(path_id, &self.recovery_metrics); +588 self.recovery_metrics = metrics; +589 event +590 } +591 +592 /// Return how long we need to wait before sending `bytes_to_send` +593 /// +594 /// See [`Pacer::delay`]. +595 pub(super) fn pacing_delay(&mut self, bytes_to_send: u64, now: Instant) -> Option<Duration> { +596 let smoothed_rtt = self.rtt.get(); +597 let metrics = self.congestion.metrics(); +598 self.pacing.delay( +599 smoothed_rtt, +600 bytes_to_send, +601 self.current_mtu(), +602 metrics.congestion_window, +603 now, +604 metrics.send_quantum, +605 metrics.pacing_rate, +606 ) +607 } +608 +609 /// Updates the last observed address report received on this path. +610 /// +611 /// If the address was updated, it's returned to be informed to the application. +612 #[must_use = "updated observed address must be reported to the application"] +613 pub(super) fn update_observed_addr_report( +614 &mut self, +615 observed: ObservedAddr, +616 ) -> Option<SocketAddr> { +617 match self.last_observed_addr_report.as_mut() { +618 Some(prev) => { +619 if prev.seq_no >= observed.seq_no { +620 // frames that do not increase the sequence number on this path are ignored +621 None +622 } else if prev.ip == observed.ip && prev.port == observed.port { +623 // keep track of the last seq_no but do not report the address as updated +624 prev.seq_no = observed.seq_no; +625 None +626 } else { +627 let addr = observed.socket_addr(); +628 self.last_observed_addr_report = Some(observed); +629 Some(addr) +630 } +631 } +632 None => { +633 let addr = observed.socket_addr(); +634 self.last_observed_addr_report = Some(observed); +635 Some(addr) +636 } +637 } +638 } +639 +640 pub(crate) fn remote_status(&self) -> Option<PathStatus> { +641 self.status.remote_status.map(|(_seq, status)| status) 642 } 643 -644 pub(crate) fn remote_status(&self) -> Option<PathStatus> { -645 self.status.remote_status.map(|(_seq, status)| status) +644 pub(crate) fn local_status(&self) -> PathStatus { +645 self.status.local_status 646 } 647 -648 pub(crate) fn local_status(&self) -> PathStatus { -649 self.status.local_status -650 } -651 -652 /// Tag uniquely identifying a path in a connection. -653 /// -654 /// When a migration happens on the same [`PathId`] we still detect a change in the -655 /// 4-tuple and generate a new [`PathData`] for it. Each such generation has a unique -656 /// value to keep track of which 4-tuple a packet belonged to. -657 pub(super) fn generation(&self) -> u64 { -658 self.generation -659 } -660} -661 -662pub(super) enum OnPathResponseReceived { -663 /// This response validates the path on its current remote address. -664 OnPath { was_open: bool }, -665 /// The received token is unknown. -666 Unknown, -667 /// The response is valid but it's not usable for path validation. -668 Ignored { -669 sent_on: FourTuple, -670 current_path: FourTuple, -671 }, -672} -673 -674#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -675pub(super) enum OpenStatus { -676 /// A first packet has not been sent using this [`PathId`]. -677 #[default] -678 Pending, -679 /// The first packet has been sent using this [`PathId`]. However, it is not yet deemed good -680 /// enough to be reported to the application. -681 Sent, -682 /// The application has been informed of this path. -683 Informed, -684} -685 -686/// Congestion metrics as described in [`recovery_metrics_updated`]. -687/// -688/// [`recovery_metrics_updated`]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html#name-recovery_metrics_updated -689#[cfg(feature = "qlog")] -690#[derive(Default, Clone, PartialEq, Debug)] -691#[non_exhaustive] -692struct RecoveryMetrics { -693 pub min_rtt: Option<Duration>, -694 pub smoothed_rtt: Option<Duration>, -695 pub latest_rtt: Option<Duration>, -696 pub rtt_variance: Option<Duration>, -697 pub pto_count: Option<u32>, -698 pub bytes_in_flight: Option<u64>, -699 pub packets_in_flight: Option<u64>, -700 pub congestion_window: Option<u64>, -701 pub ssthresh: Option<u64>, -702 pub pacing_rate: Option<u64>, -703} -704 -705#[cfg(feature = "qlog")] -706impl RecoveryMetrics { -707 /// Retain only values that have been updated since the last snapshot. -708 fn retain_updated(&self, previous: &Self) -> Self { -709 macro_rules! keep_if_changed { -710 ($name:ident) => { -711 if previous.$name == self.$name { -712 None -713 } else { -714 self.$name -715 } -716 }; -717 } -718 -719 Self { -720 min_rtt: keep_if_changed!(min_rtt), -721 smoothed_rtt: keep_if_changed!(smoothed_rtt), -722 latest_rtt: keep_if_changed!(latest_rtt), -723 rtt_variance: keep_if_changed!(rtt_variance), -724 pto_count: keep_if_changed!(pto_count), -725 bytes_in_flight: keep_if_changed!(bytes_in_flight), -726 packets_in_flight: keep_if_changed!(packets_in_flight), -727 congestion_window: keep_if_changed!(congestion_window), -728 ssthresh: keep_if_changed!(ssthresh), -729 pacing_rate: keep_if_changed!(pacing_rate), -730 } -731 } -732 -733 /// Emit a `MetricsUpdated` event containing only updated values -734 fn to_qlog_event(&self, path_id: PathId, previous: &Self) -> Option<RecoveryMetricsUpdated> { -735 let updated = self.retain_updated(previous); -736 -737 if updated == Self::default() { -738 return None; -739 } -740 -741 Some(RecoveryMetricsUpdated { -742 min_rtt: updated.min_rtt.map(|rtt| rtt.as_micros() as f32 / 1000.0), -743 smoothed_rtt: updated -744 .smoothed_rtt -745 .map(|rtt| rtt.as_micros() as f32 / 1000.0), -746 latest_rtt: updated -747 .latest_rtt -748 .map(|rtt| rtt.as_micros() as f32 / 1000.0), -749 rtt_variance: updated -750 .rtt_variance -751 .map(|rtt| rtt.as_micros() as f32 / 1000.0), -752 pto_count: updated -753 .pto_count -754 .map(|count| count.try_into().unwrap_or(u16::MAX)), -755 bytes_in_flight: updated.bytes_in_flight, -756 packets_in_flight: updated.packets_in_flight, -757 congestion_window: updated.congestion_window, -758 ssthresh: updated.ssthresh, -759 pacing_rate: updated.pacing_rate, -760 path_id: Some(path_id.as_u32() as u64), -761 }) -762 } -763} -764 -765/// RTT estimation for a particular network path -766#[derive(Copy, Clone, Debug)] -767pub struct RttEstimator { -768 /// The most recent RTT measurement made when receiving an ack for a previously unacked packet -769 latest: Duration, -770 /// The smoothed RTT of the connection, computed as described in RFC6298 -771 smoothed: Option<Duration>, -772 /// The RTT variance, computed as described in RFC6298 -773 var: Duration, -774 /// The minimum RTT seen in the connection, ignoring ack delay. -775 min: Duration, -776} -777 -778impl RttEstimator { -779 pub(super) fn new(initial_rtt: Duration) -> Self { -780 Self { -781 latest: initial_rtt, -782 smoothed: None, -783 var: initial_rtt / 2, -784 min: initial_rtt, -785 } -786 } -787 -788 /// Resets the estimator using a new initial_rtt value. -789 /// -790 /// This only resets the initial_rtt **if** no samples have been recorded yet. If there -791 /// are any recorded samples the initial estimate can not be adjusted after the fact. -792 /// -793 /// This is useful when you receive a PATH_RESPONSE in the first packet received on a -794 /// new path. In this case you can use the delay of the PATH_CHALLENGE-PATH_RESPONSE as -795 /// the initial RTT to get a better expected estimation. -796 /// -797 /// A PATH_CHALLENGE-PATH_RESPONSE pair later in the connection should not be used -798 /// explicitly as an estimation since PATH_CHALLENGE is an ACK-eliciting packet itself -799 /// already. -800 pub(crate) fn reset_initial_rtt(&mut self, initial_rtt: Duration) { -801 if self.smoothed.is_none() { -802 self.latest = initial_rtt; -803 self.var = initial_rtt / 2; -804 self.min = initial_rtt; -805 } -806 } -807 -808 /// The current best RTT estimation. -809 pub fn get(&self) -> Duration { -810 self.smoothed.unwrap_or(self.latest) -811 } -812 -813 /// Conservative estimate of RTT -814 /// -815 /// Takes the maximum of smoothed and latest RTT, as recommended -816 /// in 6.1.2 of the recovery spec (draft 29). -817 pub fn conservative(&self) -> Duration { -818 self.get().max(self.latest) -819 } -820 -821 /// Minimum RTT registered so far for this estimator. -822 pub fn min(&self) -> Duration { -823 self.min -824 } -825 -826 /// PTO computed as described in RFC9002#6.2.1. -827 pub(crate) fn pto_base(&self) -> Duration { -828 self.get() + cmp::max(4 * self.var, TIMER_GRANULARITY) -829 } -830 -831 /// Records an RTT sample. -832 pub(crate) fn update(&mut self, ack_delay: Duration, rtt: Duration) { -833 self.latest = rtt; -834 // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-3: -835 // min_rtt does not adjust for ack_delay to avoid underestimating. -836 self.min = cmp::min(self.min, self.latest); -837 // Based on RFC6298. -838 if let Some(smoothed) = self.smoothed { -839 let adjusted_rtt = if self.min + ack_delay <= self.latest { -840 self.latest - ack_delay -841 } else { -842 self.latest -843 }; -844 let var_sample = smoothed.abs_diff(adjusted_rtt); -845 self.var = (3 * self.var + var_sample) / 4; -846 self.smoothed = Some((7 * smoothed + adjusted_rtt) / 8); -847 } else { -848 self.smoothed = Some(self.latest); -849 self.var = self.latest / 2; -850 self.min = self.latest; -851 } -852 } -853} -854 -855#[derive(Default, Debug)] -856pub(crate) struct PathResponses { -857 pending: Vec<PathResponse>, -858} -859 -860impl PathResponses { -861 pub(crate) fn push(&mut self, packet: u64, token: u64, network_path: FourTuple) { -862 /// An arbitrary permissive limit to prevent abuse. -863 /// -864 /// If we've negotiated the n0 NAT Traversal extension, and one user might have a lot -865 /// of addresses, e.g. because of having lots of interfaces (we've seen >25 interfaces -866 /// on Macs with docker and other things), then we need to be able to process at least -867 /// as many PATH_CHALLENGE frames as there are interfaces. -868 /// On top of that, there are retries, which make it possible that we need to process -869 /// even more. -870 /// -871 /// Considering that there can be up to 2 `PathData`s per active `PathId`, and -872 /// reasonable default values for maximum concurrent multipath paths are ~8 and each -873 /// `PathResponse` struct takes up 72 bytes at the moment this, means an attacker can -874 /// cause us to keep `32 * 2 * 8 * 72 = ~37KB` of data around. -875 const MAX_PATH_RESPONSES: usize = 32; -876 let response = PathResponse { -877 packet, -878 token, -879 network_path, -880 }; -881 let existing = self -882 .pending -883 .iter_mut() -884 .find(|x| x.network_path.remote == network_path.remote); -885 if let Some(existing) = existing { -886 // Update a queued response -887 if existing.packet <= packet { -888 *existing = response; -889 } -890 return; -891 } -892 if self.pending.len() < MAX_PATH_RESPONSES { -893 self.pending.push(response); -894 } else { -895 // We don't expect to ever hit this with well-behaved peers, so we don't bother dropping -896 // older challenges. -897 trace!("ignoring excessive PATH_CHALLENGE"); -898 } +648 /// Tag uniquely identifying a path in a connection. +649 /// +650 /// When a migration happens on the same [`PathId`] we still detect a change in the +651 /// 4-tuple and generate a new [`PathData`] for it. Each such generation has a unique +652 /// value to keep track of which 4-tuple a packet belonged to. +653 pub(super) fn generation(&self) -> u64 { +654 self.generation +655 } +656} +657 +658pub(super) enum OnPathResponseReceived { +659 /// This response validates the path on its current remote address. +660 OnPath, +661 /// The received token is unknown. +662 Unknown, +663 /// The response is valid but it's not usable for path validation. +664 Ignored { +665 sent_on: FourTuple, +666 current_path: FourTuple, +667 }, +668} +669 +670/// Congestion metrics as described in [`recovery_metrics_updated`]. +671/// +672/// [`recovery_metrics_updated`]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html#name-recovery_metrics_updated +673#[cfg(feature = "qlog")] +674#[derive(Default, Clone, PartialEq, Debug)] +675#[non_exhaustive] +676struct RecoveryMetrics { +677 pub min_rtt: Option<Duration>, +678 pub smoothed_rtt: Option<Duration>, +679 pub latest_rtt: Option<Duration>, +680 pub rtt_variance: Option<Duration>, +681 pub pto_count: Option<u32>, +682 pub bytes_in_flight: Option<u64>, +683 pub packets_in_flight: Option<u64>, +684 pub congestion_window: Option<u64>, +685 pub ssthresh: Option<u64>, +686 pub pacing_rate: Option<u64>, +687} +688 +689#[cfg(feature = "qlog")] +690impl RecoveryMetrics { +691 /// Retain only values that have been updated since the last snapshot. +692 fn retain_updated(&self, previous: &Self) -> Self { +693 macro_rules! keep_if_changed { +694 ($name:ident) => { +695 if previous.$name == self.$name { +696 None +697 } else { +698 self.$name +699 } +700 }; +701 } +702 +703 Self { +704 min_rtt: keep_if_changed!(min_rtt), +705 smoothed_rtt: keep_if_changed!(smoothed_rtt), +706 latest_rtt: keep_if_changed!(latest_rtt), +707 rtt_variance: keep_if_changed!(rtt_variance), +708 pto_count: keep_if_changed!(pto_count), +709 bytes_in_flight: keep_if_changed!(bytes_in_flight), +710 packets_in_flight: keep_if_changed!(packets_in_flight), +711 congestion_window: keep_if_changed!(congestion_window), +712 ssthresh: keep_if_changed!(ssthresh), +713 pacing_rate: keep_if_changed!(pacing_rate), +714 } +715 } +716 +717 /// Emit a `MetricsUpdated` event containing only updated values +718 fn to_qlog_event(&self, path_id: PathId, previous: &Self) -> Option<RecoveryMetricsUpdated> { +719 let updated = self.retain_updated(previous); +720 +721 if updated == Self::default() { +722 return None; +723 } +724 +725 Some(RecoveryMetricsUpdated { +726 min_rtt: updated.min_rtt.map(|rtt| rtt.as_micros() as f32 / 1000.0), +727 smoothed_rtt: updated +728 .smoothed_rtt +729 .map(|rtt| rtt.as_micros() as f32 / 1000.0), +730 latest_rtt: updated +731 .latest_rtt +732 .map(|rtt| rtt.as_micros() as f32 / 1000.0), +733 rtt_variance: updated +734 .rtt_variance +735 .map(|rtt| rtt.as_micros() as f32 / 1000.0), +736 pto_count: updated +737 .pto_count +738 .map(|count| count.try_into().unwrap_or(u16::MAX)), +739 bytes_in_flight: updated.bytes_in_flight, +740 packets_in_flight: updated.packets_in_flight, +741 congestion_window: updated.congestion_window, +742 ssthresh: updated.ssthresh, +743 pacing_rate: updated.pacing_rate, +744 path_id: Some(path_id.as_u32() as u64), +745 ex_data: Default::default(), +746 }) +747 } +748} +749 +750/// RTT estimation for a particular network path +751#[derive(Copy, Clone, Debug)] +752pub struct RttEstimator { +753 /// The most recent RTT measurement made when receiving an ack for a previously unacked packet +754 latest: Duration, +755 /// The smoothed RTT of the connection, computed as described in RFC6298 +756 smoothed: Option<Duration>, +757 /// The RTT variance, computed as described in RFC6298 +758 var: Duration, +759 /// The minimum RTT seen in the connection, ignoring ack delay. +760 min: Duration, +761} +762 +763impl RttEstimator { +764 pub(super) fn new(initial_rtt: Duration) -> Self { +765 Self { +766 latest: initial_rtt, +767 smoothed: None, +768 var: initial_rtt / 2, +769 min: initial_rtt, +770 } +771 } +772 +773 /// Resets the estimator using a new initial_rtt value. +774 /// +775 /// This only resets the initial_rtt **if** no samples have been recorded yet. If there +776 /// are any recorded samples the initial estimate can not be adjusted after the fact. +777 /// +778 /// This is useful when you receive a PATH_RESPONSE in the first packet received on a +779 /// new path. In this case you can use the delay of the PATH_CHALLENGE-PATH_RESPONSE as +780 /// the initial RTT to get a better expected estimation. +781 /// +782 /// A PATH_CHALLENGE-PATH_RESPONSE pair later in the connection should not be used +783 /// explicitly as an estimation since PATH_CHALLENGE is an ACK-eliciting packet itself +784 /// already. +785 pub(crate) fn reset_initial_rtt(&mut self, initial_rtt: Duration) { +786 if self.smoothed.is_none() { +787 self.latest = initial_rtt; +788 self.var = initial_rtt / 2; +789 self.min = initial_rtt; +790 } +791 } +792 +793 /// The current best RTT estimation. +794 pub fn get(&self) -> Duration { +795 self.smoothed.unwrap_or(self.latest) +796 } +797 +798 /// Conservative estimate of RTT +799 /// +800 /// Takes the maximum of smoothed and latest RTT, as recommended +801 /// in 6.1.2 of the recovery spec (draft 29). +802 pub fn conservative(&self) -> Duration { +803 self.get().max(self.latest) +804 } +805 +806 /// Minimum RTT registered so far for this estimator. +807 pub fn min(&self) -> Duration { +808 self.min +809 } +810 +811 /// PTO computed as described in RFC9002#6.2.1. +812 pub(crate) fn pto_base(&self) -> Duration { +813 self.get() + cmp::max(4 * self.var, TIMER_GRANULARITY) +814 } +815 +816 /// Records an RTT sample. +817 pub(crate) fn update(&mut self, ack_delay: Duration, rtt: Duration) { +818 self.latest = rtt; +819 // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-3: +820 // min_rtt does not adjust for ack_delay to avoid underestimating. +821 self.min = cmp::min(self.min, self.latest); +822 // Based on RFC6298. +823 if let Some(smoothed) = self.smoothed { +824 let adjusted_rtt = if self.min + ack_delay <= self.latest { +825 self.latest - ack_delay +826 } else { +827 self.latest +828 }; +829 let var_sample = smoothed.abs_diff(adjusted_rtt); +830 self.var = (3 * self.var + var_sample) / 4; +831 self.smoothed = Some((7 * smoothed + adjusted_rtt) / 8); +832 } else { +833 self.smoothed = Some(self.latest); +834 self.var = self.latest / 2; +835 self.min = self.latest; +836 } +837 } +838} +839 +840#[derive(Default, Debug)] +841pub(crate) struct PathResponses { +842 pending: Vec<PathResponse>, +843} +844 +845impl PathResponses { +846 pub(crate) fn push(&mut self, packet: u64, token: u64, network_path: FourTuple) { +847 /// An arbitrary permissive limit to prevent abuse. +848 /// +849 /// If we've negotiated the n0 NAT Traversal extension, and one user might have a lot +850 /// of addresses, e.g. because of having lots of interfaces (we've seen >25 interfaces +851 /// on Macs with docker and other things), then we need to be able to process at least +852 /// as many PATH_CHALLENGE frames as there are interfaces. +853 /// On top of that, there are retries, which make it possible that we need to process +854 /// even more. +855 /// +856 /// Considering that there can be up to 2 `PathData`s per active `PathId`, and +857 /// reasonable default values for maximum concurrent multipath paths are ~8 and each +858 /// `PathResponse` struct takes up 72 bytes at the moment this, means an attacker can +859 /// cause us to keep `32 * 2 * 8 * 72 = ~37KB` of data around. +860 const MAX_PATH_RESPONSES: usize = 32; +861 let response = PathResponse { +862 packet, +863 token, +864 network_path, +865 }; +866 let existing = self +867 .pending +868 .iter_mut() +869 .find(|x| x.network_path.remote == network_path.remote); +870 if let Some(existing) = existing { +871 // Update a queued response +872 if existing.packet <= packet { +873 *existing = response; +874 } +875 return; +876 } +877 if self.pending.len() < MAX_PATH_RESPONSES { +878 self.pending.push(response); +879 } else { +880 // We don't expect to ever hit this with well-behaved peers, so we don't bother dropping +881 // older challenges. +882 trace!("ignoring excessive PATH_CHALLENGE"); +883 } +884 } +885 +886 pub(crate) fn pop_off_path(&mut self, network_path: FourTuple) -> Option<(u64, FourTuple)> { +887 let response = *self.pending.last()?; +888 // We use an exact comparison here, because once we've received for the first time, +889 // we really should either already have a local_ip, or we will never get one +890 // (because our OS doesn't support it). And even if we get it wrong we are only +891 // slightly less efficient and would not include other on-path data in the packet. +892 if response.network_path == network_path { +893 // We don't bother searching further because we expect that the on-path response will +894 // get drained in the immediate future by a call to `pop_on_path` +895 return None; +896 } +897 self.pending.pop(); +898 Some((response.token, response.network_path)) 899 } 900 -901 pub(crate) fn pop_off_path(&mut self, network_path: FourTuple) -> Option<(u64, FourTuple)> { +901 pub(crate) fn pop_on_path(&mut self, network_path: FourTuple) -> Option<u64> { 902 let response = *self.pending.last()?; -903 // We use an exact comparison here, because once we've received for the first time, -904 // we really should either already have a local_ip, or we will never get one -905 // (because our OS doesn't support it). -906 if response.network_path == network_path { -907 // We don't bother searching further because we expect that the on-path response will -908 // get drained in the immediate future by a call to `pop_on_path` -909 return None; -910 } -911 self.pending.pop(); -912 Some((response.token, response.network_path)) -913 } -914 -915 pub(crate) fn pop_on_path(&mut self, network_path: FourTuple) -> Option<u64> { -916 let response = *self.pending.last()?; -917 // Using an exact comparison. See explanation in `pop_off_path`. -918 if response.network_path != network_path { -919 // We don't bother searching further because we expect that the off-path response will -920 // get drained in the immediate future by a call to `pop_off_path` -921 return None; -922 } -923 self.pending.pop(); -924 Some(response.token) -925 } -926 -927 pub(crate) fn is_empty(&self) -> bool { -928 self.pending.is_empty() -929 } -930} -931 -932#[derive(Copy, Clone, Debug)] -933struct PathResponse { -934 /// The packet number the corresponding PATH_CHALLENGE was received in -935 packet: u64, -936 /// The token of the PATH_CHALLENGE -937 token: u64, -938 /// The path the corresponding PATH_CHALLENGE was received from -939 network_path: FourTuple, -940} -941 -942/// Summary statistics of packets that have been sent on a particular path, but which have not yet -943/// been acked or deemed lost -944#[derive(Debug)] -945pub(super) struct InFlight { -946 /// Sum of the sizes of all sent packets considered "in flight" by congestion control -947 /// -948 /// The size does not include IP or UDP overhead. Packets only containing ACK frames do not -949 /// count towards this to ensure congestion control does not impede congestion feedback. -950 pub(super) bytes: u64, -951 /// Number of packets in flight containing frames other than ACK and PADDING -952 /// -953 /// This can be 0 even when bytes is not 0 because PADDING frames cause a packet to be -954 /// considered "in flight" by congestion control. However, if this is nonzero, bytes will always -955 /// also be nonzero. -956 pub(super) ack_eliciting: u64, -957} -958 -959impl InFlight { -960 fn new() -> Self { -961 Self { -962 bytes: 0, -963 ack_eliciting: 0, -964 } -965 } -966 -967 fn insert(&mut self, packet: &SentPacket) { -968 self.bytes += u64::from(packet.size); -969 self.ack_eliciting += u64::from(packet.ack_eliciting); -970 } +903 // Using an exact comparison. See explanation in `pop_off_path`. +904 if response.network_path != network_path { +905 // We don't bother searching further because we expect that the off-path response will +906 // get drained in the immediate future by a call to `pop_off_path` +907 return None; +908 } +909 self.pending.pop(); +910 Some(response.token) +911 } +912 +913 /// Whether the next [`Self::pop_on_path`] will return something to send. +914 pub(crate) fn has_pending_on_path(&self, network_path: FourTuple) -> bool { +915 self.pending +916 .last() +917 .is_some_and(|response| response.network_path == network_path) +918 } +919 +920 pub(crate) fn is_empty(&self) -> bool { +921 self.pending.is_empty() +922 } +923} +924 +925#[derive(Copy, Clone, Debug)] +926struct PathResponse { +927 /// The packet number the corresponding PATH_CHALLENGE was received in +928 packet: u64, +929 /// The token of the PATH_CHALLENGE +930 token: u64, +931 /// The path the corresponding PATH_CHALLENGE was received from +932 network_path: FourTuple, +933} +934 +935/// Summary statistics of packets that have been sent on a particular path, but which have not yet +936/// been acked or deemed lost +937#[derive(Debug)] +938pub(super) struct InFlight { +939 /// Sum of the sizes of all sent packets considered "in flight" by congestion control +940 /// +941 /// The size does not include IP or UDP overhead. Packets only containing ACK frames do not +942 /// count towards this to ensure congestion control does not impede congestion feedback. +943 pub(super) bytes: u64, +944 /// Number of packets in flight containing frames other than ACK and PADDING +945 /// +946 /// This can be 0 even when bytes is not 0 because PADDING frames cause a packet to be +947 /// considered "in flight" by congestion control. However, if this is nonzero, bytes will always +948 /// also be nonzero. +949 pub(super) ack_eliciting: u64, +950} +951 +952impl InFlight { +953 fn new() -> Self { +954 Self { +955 bytes: 0, +956 ack_eliciting: 0, +957 } +958 } +959 +960 fn insert(&mut self, packet: &SentPacket) { +961 self.bytes += u64::from(packet.size); +962 self.ack_eliciting += u64::from(packet.ack_eliciting); +963 } +964 +965 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned +966 fn remove(&mut self, packet: &SentPacket) { +967 self.bytes -= u64::from(packet.size); +968 self.ack_eliciting -= u64::from(packet.ack_eliciting); +969 } +970} 971 -972 /// Update counters to account for a packet becoming acknowledged, lost, or abandoned -973 fn remove(&mut self, packet: &SentPacket) { -974 self.bytes -= u64::from(packet.size); -975 self.ack_eliciting -= u64::from(packet.ack_eliciting); -976 } -977} -978 -979/// State for QUIC-MULTIPATH PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP frames -980#[derive(Debug, Clone, Default)] -981pub(super) struct PathStatusState { -982 /// The local status -983 local_status: PathStatus, -984 /// Local sequence number, for both PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP -985 /// -986 /// This is the number of the *next* path status frame to be sent. -987 local_seq: VarInt, -988 /// The status set by the remote -989 remote_status: Option<(VarInt, PathStatus)>, -990} +972/// State for QUIC-MULTIPATH PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP frames +973#[derive(Debug, Clone, Default)] +974pub(super) struct PathStatusState { +975 /// The local status +976 local_status: PathStatus, +977 /// Local sequence number, for both PATH_STATUS_AVAILABLE and PATH_STATUS_BACKUP +978 /// +979 /// This is the number of the *next* path status frame to be sent. +980 local_seq: VarInt, +981 /// The status set by the remote +982 remote_status: Option<(VarInt, PathStatus)>, +983} +984 +985impl PathStatusState { +986 /// To be called on received PATH_STATUS_AVAILABLE/PATH_STATUS_BACKUP frames +987 pub(super) fn remote_update(&mut self, status: PathStatus, seq: VarInt) { +988 if self.remote_status.is_some_and(|(curr, _)| curr >= seq) { +989 return trace!(%seq, "ignoring path status update"); +990 } 991 -992impl PathStatusState { -993 /// To be called on received PATH_STATUS_AVAILABLE/PATH_STATUS_BACKUP frames -994 pub(super) fn remote_update(&mut self, status: PathStatus, seq: VarInt) { -995 if self.remote_status.is_some_and(|(curr, _)| curr >= seq) { -996 return trace!(%seq, "ignoring path status update"); -997 } -998 -999 let prev = self.remote_status.replace((seq, status)).map(|(_, s)| s); -1000 if prev != Some(status) { -1001 debug!(?status, ?seq, "remote changed path status"); -1002 } -1003 } -1004 -1005 /// Updates the local status -1006 /// -1007 /// If the local status changed, the previous value is returned -1008 pub(super) fn local_update(&mut self, status: PathStatus) -> Option<PathStatus> { -1009 if self.local_status == status { -1010 return None; -1011 } -1012 -1013 self.local_seq = self.local_seq.saturating_add(1u8); -1014 Some(std::mem::replace(&mut self.local_status, status)) -1015 } -1016 -1017 pub(crate) fn seq(&self) -> VarInt { -1018 self.local_seq -1019 } -1020} -1021 -1022/// The QUIC-MULTIPATH path status -1023/// -1024/// See section "3.3 Path Status Management": -1025/// <https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management> -1026#[cfg_attr(test, derive(test_strategy::Arbitrary))] -1027#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] -1028pub enum PathStatus { -1029 /// Paths marked with as available will be used when scheduling packets -1030 /// -1031 /// If multiple paths are available, packets will be scheduled on whichever has -1032 /// capacity. -1033 #[default] -1034 Available, -1035 /// Paths marked as backup will only be used if there are no available paths -1036 /// -1037 /// If the max_idle_timeout is specified the path will be kept alive so that it does not -1038 /// expire. -1039 Backup, -1040} -1041 -1042/// Application events about paths -1043#[derive(Debug, Clone, PartialEq, Eq)] -1044pub enum PathEvent { -1045 /// A new path has established connection with the peer. -1046 Established { -1047 /// The path which can now be used for application data. -1048 id: PathId, -1049 }, -1050 /// A path was abandoned and is no longer usable. -1051 /// -1052 /// This event will always be followed by [`Self::Discarded`] after some time. -1053 Abandoned { -1054 /// With path was abandoned. -1055 id: PathId, -1056 /// Reason why this path was abandoned. -1057 reason: PathAbandonReason, -1058 }, -1059 /// A path was discarded and all remaining state for it has been removed. -1060 /// -1061 /// This event is the last event for a path, and is always emitted after [`Self::Abandoned`]. +992 let prev = self.remote_status.replace((seq, status)).map(|(_, s)| s); +993 if prev != Some(status) { +994 debug!(?status, ?seq, "remote changed path status"); +995 } +996 } +997 +998 /// Updates the local status +999 /// +1000 /// If the local status changed, the previous value is returned +1001 pub(super) fn local_update(&mut self, status: PathStatus) -> Option<PathStatus> { +1002 if self.local_status == status { +1003 return None; +1004 } +1005 +1006 self.local_seq = self.local_seq.saturating_add(1u8); +1007 Some(std::mem::replace(&mut self.local_status, status)) +1008 } +1009 +1010 pub(crate) fn seq(&self) -> VarInt { +1011 self.local_seq +1012 } +1013} +1014 +1015/// The QUIC-MULTIPATH path status +1016/// +1017/// See section "3.3 Path Status Management": +1018/// <https://quicwg.org/multipath/draft-ietf-quic-multipath.html#name-path-status-management> +1019#[cfg_attr(test, derive(test_strategy::Arbitrary))] +1020#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +1021pub enum PathStatus { +1022 /// Paths marked with as available will be used when scheduling packets +1023 /// +1024 /// If multiple paths are available, packets will be scheduled on whichever has +1025 /// capacity. +1026 #[default] +1027 Available, +1028 /// Paths marked as backup will only be used if there are no available paths +1029 /// +1030 /// If the max_idle_timeout is specified the path will be kept alive so that it does not +1031 /// expire. +1032 Backup, +1033} +1034 +1035/// Application events about paths +1036#[derive(Debug, Clone, PartialEq, Eq)] +1037#[non_exhaustive] +1038pub enum PathEvent { +1039 /// A new path has established connection with the peer. +1040 #[non_exhaustive] +1041 Established { +1042 /// The path which can now be used for application data. +1043 id: PathId, +1044 }, +1045 /// A path was abandoned and is no longer usable. +1046 /// +1047 /// Note that this may be the first event for a path: If a path is abandoned +1048 /// before having been established, no [`Self::Established`] event is emitted. +1049 /// +1050 /// This event will always be followed by [`Self::Discarded`] after some time. +1051 #[non_exhaustive] +1052 Abandoned { +1053 /// The path that was abandoned. +1054 id: PathId, +1055 /// Reason why this path was abandoned. +1056 reason: PathAbandonReason, +1057 }, +1058 /// A path was discarded and all remaining state for it has been removed. +1059 /// +1060 /// This event is the last event for a path, and is always emitted after [`Self::Abandoned`]. +1061 #[non_exhaustive] 1062 Discarded { 1063 /// Which path had its state dropped 1064 id: PathId, @@ -1072,38 +1072,38 @@ 1072 /// The local status is not changed because of this event. It is up to the application 1073 /// to update the local status, which is used for packet scheduling, when the remote 1074 /// changes the status. -1075 RemoteStatus { -1076 /// Path which has changed status -1077 id: PathId, -1078 /// The new status set by the remote -1079 status: PathStatus, -1080 }, -1081 /// Received an observation of our external address from the peer. -1082 ObservedAddr { -1083 /// Path over which the observed address was reported, [`PathId::ZERO`] when multipath is -1084 /// not negotiated -1085 id: PathId, -1086 /// The address observed by the remote over this path -1087 addr: SocketAddr, -1088 }, -1089} -1090 -1091/// Reason for why a path was abandoned. -1092#[derive(Debug, Clone, Eq, PartialEq)] -1093pub enum PathAbandonReason { -1094 /// The path was closed locally by the application. -1095 ApplicationClosed { -1096 /// The error code to be sent with the abandon frame. -1097 error_code: VarInt, -1098 }, -1099 /// We didn't receive a path response in time after opening this path. -1100 ValidationFailed, -1101 /// We didn't receive any data from the remote within the path's idle timeout. -1102 TimedOut, -1103 /// The path became unusable after a local network change. -1104 UnusableAfterNetworkChange, -1105 /// The path was opened in a NAT traversal round which was terminated. -1106 NatTraversalRoundEnded, +1075 #[non_exhaustive] +1076 RemoteStatus { +1077 /// Path which has changed status +1078 id: PathId, +1079 /// The new status set by the remote +1080 status: PathStatus, +1081 }, +1082 /// Received an observation of our external address from the peer. +1083 #[non_exhaustive] +1084 ObservedAddr { +1085 /// Path over which the observed address was reported, [`PathId::ZERO`] when multipath is +1086 /// not negotiated +1087 id: PathId, +1088 /// The address observed by the remote over this path +1089 addr: SocketAddr, +1090 }, +1091} +1092 +1093/// Reason for why a path was abandoned. +1094#[derive(Debug, Clone, Eq, PartialEq)] +1095pub enum PathAbandonReason { +1096 /// The path was closed locally by the application. +1097 ApplicationClosed { +1098 /// The error code to be sent with the abandon frame. +1099 error_code: VarInt, +1100 }, +1101 /// We didn't receive a path response in time after opening this path. +1102 ValidationFailed, +1103 /// We didn't receive any data from the remote within the path's idle timeout. +1104 TimedOut, +1105 /// The path became unusable after a local network change. +1106 UnusableAfterNetworkChange, 1107 /// The remote closed the path. 1108 RemoteAbandoned { 1109 /// The error that was sent with the abandon frame. @@ -1121,45 +1121,67 @@ 1121 pub(crate) fn error_code(&self) -> TransportErrorCode { 1122 match self { 1123 Self::ApplicationClosed { error_code } => (*error_code).into(), -1124 Self::NatTraversalRoundEnded => TransportErrorCode::APPLICATION_ABANDON_PATH, -1125 Self::ValidationFailed | Self::TimedOut | Self::UnusableAfterNetworkChange => { -1126 TransportErrorCode::PATH_UNSTABLE_OR_POOR -1127 } -1128 Self::RemoteAbandoned { error_code } => (*error_code).into(), -1129 } -1130 } -1131} -1132 -1133/// Error from setting path status -1134#[derive(Debug, Error, Clone, PartialEq, Eq)] -1135pub enum SetPathStatusError { -1136 /// Error indicating that a path has not been opened or has already been abandoned -1137 #[error("closed path")] -1138 ClosedPath, -1139 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been -1140 #[error("multipath not negotiated")] -1141 MultipathNotNegotiated, -1142} -1143 -1144/// Error indicating that a path has not been opened or has already been abandoned -1145#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] -1146#[error("closed path")] -1147pub struct ClosedPath { -1148 pub(super) _private: (), -1149} -1150 -1151#[cfg(test)] -1152mod tests { -1153 use super::*; -1154 -1155 #[test] -1156 fn test_path_id_saturating_add() { -1157 // add within range behaves normally -1158 let large: PathId = u16::MAX.into(); -1159 let next = u32::from(u16::MAX) + 1; -1160 assert_eq!(large.saturating_add(1u8), PathId::from(next)); -1161 -1162 // outside range saturates -1163 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) -1164 } -1165}

\ No newline at end of file +1124 Self::ValidationFailed | Self::TimedOut | Self::UnusableAfterNetworkChange => { +1125 TransportErrorCode::PATH_UNSTABLE_OR_POOR +1126 } +1127 Self::RemoteAbandoned { error_code } => (*error_code).into(), +1128 } +1129 } +1130} +1131 +1132/// Error from setting path status +1133#[derive(Debug, Error, Clone, PartialEq, Eq)] +1134pub enum SetPathStatusError { +1135 /// Error indicating that a path has not been opened or has already been abandoned +1136 #[error("closed path")] +1137 ClosedPath, +1138 /// Error indicating that this operation requires multipath to be negotiated whereas it hasn't been +1139 #[error("multipath not negotiated")] +1140 MultipathNotNegotiated, +1141} +1142 +1143/// Error indicating that a path has not been opened or has already been abandoned +1144#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] +1145#[error("closed path")] +1146pub struct ClosedPath { +1147 pub(super) _private: (), +1148} +1149 +1150/// Retransmittable data specific to a [`PathData::generation`]. +1151#[derive(Debug, Default, Clone)] +1152pub(super) struct PathRetransmits { +1153 /// Whether this path needs to report its remote address back to the peer. +1154 /// +1155 /// This only happens if both peers agree to do so based on their transport parameters. +1156 pub(super) observed_address: bool, +1157} +1158 +1159impl PathRetransmits { +1160 pub(super) fn is_empty(&self) -> bool { +1161 let Self { observed_address } = self; +1162 !observed_address +1163 } +1164} +1165 +1166impl std::ops::BitOrAssign for PathRetransmits { +1167 fn bitor_assign(&mut self, rhs: Self) { +1168 let Self { observed_address } = rhs; +1169 self.observed_address |= observed_address; +1170 } +1171} +1172 +1173#[cfg(test)] +1174mod tests { +1175 use super::*; +1176 +1177 #[test] +1178 fn test_path_id_saturating_add() { +1179 // add within range behaves normally +1180 let large: PathId = u16::MAX.into(); +1181 let next = u32::from(u16::MAX) + 1; +1182 assert_eq!(large.saturating_add(1u8), PathId::from(next)); +1183 +1184 // outside range saturates +1185 assert_eq!(PathId::MAX.saturating_add(1u8), PathId::MAX) +1186 } +1187}

\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/qlog.rs.html b/pr/451/docs/src/noq_proto/connection/qlog.rs.html index a4f47b22b..2c2b8d7f1 100644 --- a/pr/451/docs/src/noq_proto/connection/qlog.rs.html +++ b/pr/451/docs/src/noq_proto/connection/qlog.rs.html @@ -25,7 +25,7 @@ 25 events::{ 26 ApplicationError, ConnectionClosedFrameError, Event, EventData, RawInfo, TupleEndpointInfo, 27 quic::{ -28 self, AckedRanges, AddressDiscoveryRole, ConnectionStarted, ErrorSpace, PacketHeader, +28 self, AckRange, AddressDiscoveryRole, ConnectionStarted, ErrorSpace, PacketHeader, 29 PacketLost, PacketLostTrigger, PacketReceived, PacketSent, PacketType, 30 ParametersRestored, ParametersSet, PreferredAddress, QlogTimerType, QuicFrame, 31 StreamType, TimerEventType, TimerType, TimerUpdated, TransportInitiator, TupleAssigned, @@ -93,1205 +93,1208 @@ 93 start_time, 94 trace, 95 qlog::events::EventImportance::Extra, -96 config.writer, -97 ); -98 -99 streamer.start_log()?; -100 Ok(Self(Arc::new(Mutex::new(streamer)))) -101 } -102 -103 fn emit_event(&self, event: EventData, now: Instant) { -104 self.emit_event_with_tuple_id(event, now, None); -105 } -106 -107 fn emit_event_with_tuple_id( -108 &self, -109 event: EventData, -110 now: Instant, -111 network_path: Option<String>, -112 ) { -113 // Time will be overwritten by `add_event_with_instant` -114 let mut event = Event::with_time(0.0, event); -115 event.tuple = network_path; -116 let mut qlog_streamer = self.0.lock().unwrap(); -117 if let Err(e) = qlog_streamer.add_event_with_instant(event, now) { -118 warn!("could not emit qlog event: {e}"); -119 } -120 } -121} -122 -123/// A [`QlogStream`] that may be either dynamically disabled or compiled out entirely -124#[derive(Clone, Default)] -125pub(crate) struct QlogSink { -126 #[cfg(feature = "qlog")] -127 stream: Option<QlogStream>, -128} -129 -130impl QlogSink { -131 #[cfg(feature = "qlog")] -132 pub(crate) fn new( -133 config: QlogConfig, -134 initial_dst_cid: ConnectionId, -135 side: Side, -136 now: Instant, -137 ) -> Self { -138 let stream = QlogStream::new(config, initial_dst_cid, side, now) -139 .inspect_err(|err| warn!("failed to initialize qlog streamer: {err}")) -140 .ok(); -141 Self { stream } -142 } -143 -144 pub(crate) fn emit_connection_started( -145 &self, -146 now: Instant, -147 local_cid: ConnectionId, -148 remote_cid: ConnectionId, -149 remote: SocketAddr, -150 local_ip: Option<IpAddr>, -151 transport_params: &TransportParameters, -152 ) { -153 #[cfg(feature = "qlog")] -154 { -155 let Some(stream) = self.stream.as_ref() else { -156 return; -157 }; -158 stream.emit_event( -159 EventData::ConnectionStarted(ConnectionStarted { -160 local: tuple_endpoint_info(local_ip, None, Some(local_cid)), -161 remote: tuple_endpoint_info( -162 Some(remote.ip()), -163 Some(remote.port()), -164 Some(remote_cid), -165 ), -166 }), -167 now, -168 ); -169 -170 let params = transport_params.to_qlog(TransportInitiator::Local); -171 let event = EventData::ParametersSet(params); -172 stream.emit_event(event, now); -173 } -174 } -175 -176 pub(super) fn emit_recovery_metrics(&self, path_id: PathId, path: &mut PathData, now: Instant) { -177 #[cfg(feature = "qlog")] -178 { -179 let Some(stream) = self.stream.as_ref() else { -180 return; -181 }; -182 -183 let Some(metrics) = path.qlog_recovery_metrics(path_id) else { -184 return; -185 }; -186 -187 stream.emit_event(EventData::MetricsUpdated(metrics), now); -188 } -189 } -190 -191 pub(super) fn emit_packet_lost( -192 &self, -193 pn: u64, -194 info: &SentPacket, -195 loss_delay: Duration, -196 space: SpaceKind, -197 now: Instant, -198 ) { -199 #[cfg(feature = "qlog")] -200 { -201 let Some(stream) = self.stream.as_ref() else { -202 return; -203 }; -204 -205 let event = PacketLost { -206 header: Some(PacketHeader { -207 packet_number: Some(pn), -208 packet_type: packet_type(space, false), -209 length: Some(info.size), -210 ..Default::default() -211 }), -212 frames: None, -213 trigger: Some( -214 match info.time_sent.saturating_duration_since(now) >= loss_delay { -215 true => PacketLostTrigger::TimeThreshold, -216 false => PacketLostTrigger::ReorderingThreshold, -217 }, -218 ), -219 is_mtu_probe_packet: None, -220 }; -221 -222 stream.emit_event(EventData::PacketLost(event), now); -223 } -224 } -225 -226 pub(super) fn emit_peer_transport_params_restored(&self, conn: &Connection, now: Instant) { -227 #[cfg(feature = "qlog")] -228 { -229 let Some(stream) = self.stream.as_ref() else { -230 return; -231 }; -232 let params = conn.peer_params.to_qlog_restored(); -233 let event = EventData::ParametersRestored(params); -234 stream.emit_event(event, now); -235 } -236 } -237 -238 pub(super) fn emit_peer_transport_params_received(&self, conn: &Connection, now: Instant) { -239 #[cfg(feature = "qlog")] -240 { -241 let Some(stream) = self.stream.as_ref() else { -242 return; -243 }; -244 let params = conn.peer_params.to_qlog(TransportInitiator::Remote); -245 let event = EventData::ParametersSet(params); -246 stream.emit_event(event, now); -247 } -248 } -249 -250 pub(super) fn emit_tuple_assigned(&self, path_id: PathId, tuple: FourTuple, now: Instant) { -251 #[cfg(feature = "qlog")] -252 { -253 let Some(stream) = self.stream.as_ref() else { -254 return; -255 }; -256 let tuple_id = fmt_tuple_id(path_id.as_u32() as u64); -257 let event = TupleAssigned { -258 tuple_id, -259 tuple_local: tuple -260 .local_ip -261 .map(|local_ip| tuple_endpoint_info(Some(local_ip), None, None)), -262 tuple_remote: Some(tuple_endpoint_info( -263 Some(tuple.remote.ip()), -264 Some(tuple.remote.port()), -265 None, -266 )), -267 }; -268 -269 stream.emit_event(EventData::TupleAssigned(event), now); -270 } -271 } -272 -273 pub(super) fn emit_packet_sent(&self, packet: QlogSentPacket, now: Instant) { -274 #[cfg(feature = "qlog")] -275 { -276 let Some(stream) = self.stream.as_ref() else { -277 return; -278 }; -279 let tuple_id = packet.inner.header.path_id.map(fmt_tuple_id); -280 stream.emit_event_with_tuple_id(EventData::PacketSent(packet.inner), now, tuple_id); -281 } -282 } -283 -284 pub(super) fn emit_packet_received(&self, packet: QlogRecvPacket, now: Instant) { -285 #[cfg(feature = "qlog")] -286 { -287 let Some(stream) = self.stream.as_ref() else { -288 return; -289 }; -290 let mut packet = packet; -291 packet.emit_padding(); -292 let tuple_id = packet.inner.header.path_id.map(fmt_tuple_id); -293 let event = packet.inner; -294 stream.emit_event_with_tuple_id(EventData::PacketReceived(event), now, tuple_id); -295 } -296 } -297 -298 /// Emits a timer event. -299 /// -300 /// This function is not public: Instead, create a [`QlogSinkWithTime`] via [`Self::with_time`] and use -301 /// its `emit_timer_` methods. -302 #[cfg(feature = "qlog")] -303 fn emit_timer(&self, timer: Timer, op: TimerOp, now: Instant) { -304 let Some(stream) = self.stream.as_ref() else { -305 return; -306 }; -307 -308 let timer_type: Option<TimerType> = match timer { -309 Timer::Conn(conn_timer) => match conn_timer { -310 ConnTimer::Idle => Some(QlogTimerType::IdleTimeout.into()), -311 ConnTimer::Close => Some(TimerType::custom("close")), -312 ConnTimer::KeyDiscard => Some(TimerType::custom("key_discard")), -313 ConnTimer::KeepAlive => Some(TimerType::custom("keep_alive")), -314 ConnTimer::PushNewCid => Some(TimerType::custom("push_new_cid")), -315 ConnTimer::NoAvailablePath => Some(TimerType::custom("no_available_path")), -316 ConnTimer::NatTraversalProbeRetry => { -317 Some(TimerType::custom("nat_traversal_probe_retry")) -318 } -319 }, -320 Timer::PerPath(_, path_timer) => match path_timer { -321 PathTimer::LossDetection => Some(QlogTimerType::LossTimeout.into()), -322 PathTimer::PathIdle => Some(TimerType::custom("path_idle")), -323 PathTimer::PathValidationFailed => Some(QlogTimerType::PathValidation.into()), -324 PathTimer::PathChallengeLost => Some(TimerType::custom("path_challenge_lost")), -325 PathTimer::AbandonFromValidation => { -326 Some(TimerType::custom("abandon_from_validation")) -327 } -328 PathTimer::PathKeepAlive => Some(TimerType::custom("path_keep_alive")), -329 PathTimer::Pacing => Some(TimerType::custom("pacing")), -330 PathTimer::MaxAckDelay => Some(QlogTimerType::Ack.into()), -331 PathTimer::PathDrained => Some(TimerType::custom("discard_path")), -332 }, -333 }; -334 -335 let Some(timer_type) = timer_type else { -336 return; -337 }; -338 -339 let delta = match op { -340 TimerOp::Set(instant) => instant -341 .checked_duration_since(now) -342 .map(|dur| dur.as_secs_f32() * 1000.), -343 _ => None, -344 }; -345 let path_id = match timer { -346 Timer::Conn(_) => None, -347 Timer::PerPath(path_id, _) => Some(path_id.as_u32() as u64), -348 }; -349 -350 let event_type = match op { -351 TimerOp::Set(_) => TimerEventType::Set, -352 TimerOp::Expire => TimerEventType::Expired, -353 TimerOp::Cancelled => TimerEventType::Cancelled, -354 }; -355 -356 let event = TimerUpdated { -357 path_id, -358 timer_type: Some(timer_type), -359 timer_id: None, -360 packet_number_space: None, -361 event_type, -362 delta, -363 }; -364 stream.emit_event(EventData::TimerUpdated(event), now); -365 } -366 -367 /// Returns a [`QlogSinkWithTime`] that passes along a `now` timestamp. -368 /// -369 /// This may be used if you want to pass a [`QlogSink`] downwards together with the current -370 /// `now` timestamp, to not have to pass the latter separately as an additional argument just -371 /// for qlog support. -372 pub(super) fn with_time(&self, now: Instant) -> QlogSinkWithTime<'_> { -373 #[cfg(feature = "qlog")] -374 let s = QlogSinkWithTime { sink: self, now }; -375 #[cfg(not(feature = "qlog"))] -376 let s = QlogSinkWithTime { -377 _phantom: PhantomData, -378 }; -379 s -380 } -381} -382 -383/// A [`QlogSink`] with a `now` timestamp. -384#[derive(Clone)] -385pub(super) struct QlogSinkWithTime<'a> { -386 #[cfg(feature = "qlog")] -387 sink: &'a QlogSink, -388 #[cfg(feature = "qlog")] -389 now: Instant, -390 #[cfg(not(feature = "qlog"))] -391 _phantom: PhantomData<&'a ()>, -392} -393 -394impl<'a> QlogSinkWithTime<'a> { -395 pub(super) fn emit_timer_stop(&self, timer: Timer) { -396 #[cfg(feature = "qlog")] -397 self.sink.emit_timer(timer, TimerOp::Cancelled, self.now) -398 } -399 -400 pub(super) fn emit_timer_set(&self, timer: Timer, expire_at: Instant) { -401 #[cfg(feature = "qlog")] -402 self.sink -403 .emit_timer(timer, TimerOp::Set(expire_at), self.now) -404 } -405 -406 pub(super) fn emit_timer_expire(&self, timer: Timer) { -407 #[cfg(feature = "qlog")] -408 self.sink.emit_timer(timer, TimerOp::Expire, self.now) -409 } -410} -411 -412#[cfg(feature = "qlog")] -413enum TimerOp { -414 Set(Instant), -415 Expire, -416 Cancelled, -417} -418 -419/// Info about a sent packet. Zero-sized struct if `qlog` feature is not enabled. -420#[derive(Default)] -421pub(crate) struct QlogSentPacket { -422 #[cfg(feature = "qlog")] -423 inner: PacketSent, -424} -425 -426impl QlogSentPacket { -427 /// Sets data from the packet header. -428 pub(crate) fn header( -429 &mut self, -430 header: &Header, -431 pn: Option<u64>, -432 encryption_level: EncryptionLevel, -433 path_id: PathId, -434 ) { -435 #[cfg(feature = "qlog")] -436 { -437 self.inner.header.scid = header.src_cid().map(stringify_cid); -438 self.inner.header.dcid = Some(stringify_cid(header.dst_cid())); -439 self.inner.header.packet_number = pn; -440 self.inner.header.packet_type = encryption_level.into(); -441 self.inner.header.path_id = Some(path_id.as_u32() as u64); -442 } -443 } -444 -445 /// Adds a PADDING frame. -446 /// -447 /// This is a no-op if the `qlog` feature is not enabled. -448 pub(crate) fn frame_padding(&mut self, count: usize) { -449 #[cfg(feature = "qlog")] -450 self.frame_raw(QuicFrame::Padding { -451 raw: Some(RawInfo { -452 length: Some(count as u64), -453 payload_length: Some(count as u64), -454 data: None, -455 }), -456 }); -457 } -458 -459 /// Adds a frame by pushing a [`QuicFrame`]. -460 /// -461 /// This function is only available if the `qlog` feature is enabled, because constructing a [`QuicFrame`] may involve -462 /// calculations which shouldn't be performed if the `qlog` feature is disabled. -463 #[cfg(feature = "qlog")] -464 fn frame_raw(&mut self, frame: QuicFrame) { -465 self.inner.frames.get_or_insert_default().push(frame); -466 } -467 -468 /// Finalizes the packet by setting the final packet length (after encryption). -469 pub(super) fn finalize(&mut self, len: usize) { -470 #[cfg(feature = "qlog")] -471 { -472 self.inner.header.length = Some(len as u16); -473 } -474 } -475 -476 pub(crate) fn record<'a>(&mut self, frame: &EncodableFrame<'a>) { -477 #[cfg(feature = "qlog")] -478 self.frame_raw(frame.to_qlog()); -479 } -480} -481 -482/// Info about a received packet. Zero-sized struct if `qlog` feature is not enabled. -483pub(crate) struct QlogRecvPacket { -484 #[cfg(feature = "qlog")] -485 inner: PacketReceived, -486 #[cfg(feature = "qlog")] -487 padding: usize, -488} -489 -490impl QlogRecvPacket { -491 /// Creates a new [`QlogRecvPacket`]. Noop if `qlog` feature is not enabled. -492 /// -493 /// `len` is the packet's full length (before decryption). -494 pub(crate) fn new(len: usize) -> Self { -495 #[cfg(not(feature = "qlog"))] -496 let this = Self {}; -497 -498 #[cfg(feature = "qlog")] -499 let this = { -500 let mut this = Self { -501 inner: Default::default(), -502 padding: 0, -503 }; -504 this.inner.header.length = Some(len as u16); -505 this -506 }; -507 -508 this -509 } -510 -511 /// Adds info from the packet header. -512 pub(crate) fn header(&mut self, header: &Header, pn: Option<u64>, path_id: PathId) { -513 #[cfg(feature = "qlog")] -514 { -515 let is_0rtt = !header.is_1rtt(); -516 self.inner.header.scid = header.src_cid().map(stringify_cid); -517 self.inner.header.dcid = Some(stringify_cid(header.dst_cid())); -518 self.inner.header.packet_number = pn; -519 self.inner.header.packet_type = packet_type(header.space(), is_0rtt); -520 self.inner.header.path_id = Some(path_id.as_u32() as u64); -521 } -522 } -523 -524 /// Adds a frame. -525 pub(crate) fn frame(&mut self, frame: &Frame) { -526 #[cfg(feature = "qlog")] -527 { -528 if matches!(frame, crate::Frame::Padding) { -529 self.padding += 1; -530 } else { -531 self.emit_padding(); -532 self.inner -533 .frames -534 .get_or_insert_default() -535 .push(frame.to_qlog()) -536 } -537 } -538 } -539 -540 #[cfg(feature = "qlog")] -541 fn emit_padding(&mut self) { -542 if self.padding > 0 { -543 self.inner -544 .frames -545 .get_or_insert_default() -546 .push(QuicFrame::Padding { -547 raw: Some(RawInfo { -548 length: Some(self.padding as u64), -549 payload_length: Some(self.padding as u64), -550 data: None, -551 }), -552 }); -553 self.padding = 0; -554 } -555 } -556} -557 -558/* Frame conversions to qlog */ -559#[cfg(feature = "qlog")] -560pub(crate) trait ToQlog { -561 fn to_qlog(&self) -> QuicFrame; -562} -563 -564#[cfg(feature = "qlog")] -565impl<'a> ToQlog for frame::AckEncoder<'a> { -566 fn to_qlog(&self) -> QuicFrame { -567 QuicFrame::Ack { -568 ack_delay: Some(self.delay as f32), -569 acked_ranges: Some(AckedRanges::Double( -570 self.ranges -571 .iter() -572 .map(|range| (range.start, range.end)) -573 .collect(), -574 )), -575 ect1: self.ecn.map(|e| e.ect1), -576 ect0: self.ecn.map(|e| e.ect0), -577 ce: self.ecn.map(|e| e.ce), -578 raw: None, -579 } -580 } -581} -582 -583#[cfg(feature = "qlog")] -584impl ToQlog for frame::AckFrequency { -585 fn to_qlog(&self) -> QuicFrame { -586 QuicFrame::AckFrequency { -587 sequence_number: self.sequence.into_inner(), -588 ack_eliciting_threshold: self.ack_eliciting_threshold.into_inner(), -589 requested_max_ack_delay: self.request_max_ack_delay.into_inner(), -590 reordering_threshold: self.reordering_threshold.into_inner(), -591 raw: None, -592 } -593 } -594} -595 -596#[cfg(feature = "qlog")] -597impl ToQlog for frame::AddAddress { -598 fn to_qlog(&self) -> QuicFrame { -599 QuicFrame::AddAddress { -600 sequence_number: self.seq_no.into_inner(), -601 ip_v4: match self.ip { -602 IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()), -603 IpAddr::V6(ipv6_addr) => None, -604 }, -605 ip_v6: match self.ip { -606 IpAddr::V4(ipv4_addr) => None, -607 IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()), -608 }, -609 port: self.port, -610 } -611 } -612} -613 -614#[cfg(feature = "qlog")] -615impl ToQlog for frame::CloseEncoder<'_> { -616 fn to_qlog(&self) -> QuicFrame { -617 self.close.to_qlog() -618 } -619} -620 -621#[cfg(feature = "qlog")] -622impl ToQlog for frame::Close { -623 fn to_qlog(&self) -> QuicFrame { -624 match self { -625 Self::Connection(f) => { -626 let (error, error_code) = transport_error(f.error_code); -627 let error = error.map(|transport_error| { -628 ConnectionClosedFrameError::TransportError(transport_error) -629 }); -630 QuicFrame::ConnectionClose { -631 error_space: Some(ErrorSpace::TransportError), -632 error, -633 error_code, -634 reason: String::from_utf8(f.reason.to_vec()).ok(), -635 reason_bytes: None, -636 trigger_frame_type: None, -637 } -638 } -639 Self::Application(f) => QuicFrame::ConnectionClose { -640 error_space: Some(ErrorSpace::ApplicationError), -641 error: None, -642 error_code: Some(f.error_code.into_inner()), -643 reason: String::from_utf8(f.reason.to_vec()).ok(), -644 reason_bytes: None, -645 trigger_frame_type: None, -646 }, -647 } -648 } -649} -650 -651#[cfg(feature = "qlog")] -652impl ToQlog for frame::Crypto { -653 fn to_qlog(&self) -> QuicFrame { -654 QuicFrame::Crypto { -655 offset: self.offset, -656 raw: Some(RawInfo { -657 length: Some(self.data.len() as u64), -658 ..Default::default() -659 }), -660 } -661 } -662} -663 -664#[cfg(feature = "qlog")] -665impl ToQlog for frame::Datagram { -666 fn to_qlog(&self) -> QuicFrame { -667 QuicFrame::Datagram { -668 raw: Some(RawInfo { -669 length: Some(self.data.len() as u64), -670 ..Default::default() -671 }), -672 } -673 } -674} -675 -676#[cfg(feature = "qlog")] -677impl ToQlog for frame::HandshakeDone { -678 fn to_qlog(&self) -> QuicFrame { -679 QuicFrame::HandshakeDone { raw: None } -680 } -681} -682 -683#[cfg(feature = "qlog")] -684impl ToQlog for frame::ImmediateAck { -685 fn to_qlog(&self) -> QuicFrame { -686 QuicFrame::ImmediateAck { raw: None } -687 } -688} -689 -690#[cfg(feature = "qlog")] -691impl ToQlog for frame::MaxData { -692 fn to_qlog(&self) -> QuicFrame { -693 QuicFrame::MaxData { -694 maximum: self.0.into(), -695 raw: None, -696 } -697 } -698} -699 -700#[cfg(feature = "qlog")] -701impl ToQlog for frame::MaxPathId { -702 fn to_qlog(&self) -> QuicFrame { -703 QuicFrame::MaxPathId { -704 maximum_path_id: self.0.as_u32().into(), -705 raw: None, -706 } -707 } -708} -709 -710#[cfg(feature = "qlog")] -711impl ToQlog for frame::MaxStreamData { -712 fn to_qlog(&self) -> QuicFrame { -713 QuicFrame::MaxStreamData { -714 stream_id: self.id.into(), -715 maximum: self.offset, -716 raw: None, -717 } -718 } -719} -720 -721#[cfg(feature = "qlog")] -722impl ToQlog for frame::MaxStreams { -723 fn to_qlog(&self) -> QuicFrame { -724 QuicFrame::MaxStreams { -725 maximum: self.count, -726 stream_type: self.dir.into(), -727 raw: None, -728 } -729 } -730} -731 -732#[cfg(feature = "qlog")] -733impl ToQlog for frame::StreamsBlocked { -734 fn to_qlog(&self) -> QuicFrame { -735 QuicFrame::StreamsBlocked { -736 stream_type: self.dir.into(), -737 limit: self.limit, -738 raw: None, -739 } -740 } -741} -742 -743#[cfg(feature = "qlog")] -744impl ToQlog for frame::NewConnectionId { -745 fn to_qlog(&self) -> QuicFrame { -746 match self.path_id { -747 None => QuicFrame::NewConnectionId { -748 sequence_number: self.sequence, -749 retire_prior_to: self.retire_prior_to, -750 connection_id_length: Some(self.id.len() as u8), -751 connection_id: self.id.to_string(), -752 stateless_reset_token: Some(self.reset_token.to_string()), -753 raw: None, -754 }, -755 Some(path_id) => QuicFrame::PathNewConnectionId { -756 path_id: path_id.0 as u64, -757 sequence_number: self.sequence, -758 retire_prior_to: self.retire_prior_to, -759 connection_id_length: Some(self.id.len() as u8), -760 connection_id: self.id.to_string(), -761 stateless_reset_token: Some(self.reset_token.to_string()), -762 raw: None, -763 }, -764 } -765 } -766} -767 -768#[cfg(feature = "qlog")] -769impl ToQlog for frame::NewToken { -770 fn to_qlog(&self) -> QuicFrame { -771 QuicFrame::NewToken { -772 token: qlog::Token { -773 ty: Some(TokenType::Retry), -774 raw: Some(RawInfo { -775 data: HexSlice::maybe_string(Some(&self.token)), -776 length: Some(self.token.len() as u64), -777 payload_length: None, -778 }), -779 details: None, -780 }, -781 raw: None, -782 } -783 } -784} -785 -786#[cfg(feature = "qlog")] -787impl ToQlog for frame::ObservedAddr { -788 fn to_qlog(&self) -> QuicFrame { -789 QuicFrame::ObservedAddress { -790 sequence_number: self.seq_no.into_inner(), -791 ip_v4: match self.ip { -792 IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()), -793 IpAddr::V6(ipv6_addr) => None, -794 }, -795 ip_v6: match self.ip { -796 IpAddr::V4(ipv4_addr) => None, -797 IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()), -798 }, -799 port: self.port, -800 raw: None, -801 } -802 } -803} -804 -805#[cfg(feature = "qlog")] -806impl ToQlog for frame::PathAbandon { -807 fn to_qlog(&self) -> QuicFrame { -808 QuicFrame::PathAbandon { -809 path_id: self.path_id.as_u32().into(), -810 error_code: self.error_code.into(), -811 raw: None, -812 } -813 } -814} -815 -816#[cfg(feature = "qlog")] -817impl ToQlog for frame::PathAckEncoder<'_> { -818 fn to_qlog(&self) -> QuicFrame { -819 QuicFrame::PathAck { -820 path_id: self.path_id.as_u32() as u64, -821 ack_delay: Some(self.delay as f32), -822 acked_ranges: Some(AckedRanges::Double( -823 self.ranges -824 .iter() -825 .map(|range| (range.start, range.end)) -826 .collect(), -827 )), -828 ect1: self.ecn.map(|e| e.ect1), -829 ect0: self.ecn.map(|e| e.ect0), -830 ce: self.ecn.map(|e| e.ce), -831 raw: None, -832 } -833 } -834} -835 -836#[cfg(feature = "qlog")] -837impl ToQlog for frame::PathChallenge { -838 #[cfg(feature = "qlog")] -839 fn to_qlog(&self) -> QuicFrame { -840 QuicFrame::PathChallenge { -841 data: Some(self.to_string()), -842 raw: None, -843 } -844 } -845} -846 -847#[cfg(feature = "qlog")] -848impl ToQlog for frame::PathCidsBlocked { -849 fn to_qlog(&self) -> QuicFrame { -850 QuicFrame::PathCidsBlocked { -851 path_id: self.path_id.as_u32().into(), -852 next_sequence_number: self.next_seq.into(), -853 raw: None, -854 } -855 } -856} -857 -858#[cfg(feature = "qlog")] -859impl ToQlog for frame::PathResponse { -860 fn to_qlog(&self) -> QuicFrame { -861 QuicFrame::PathResponse { -862 data: Some(self.to_string()), -863 raw: None, -864 } -865 } -866} -867 -868#[cfg(feature = "qlog")] -869impl ToQlog for frame::ReachOut { -870 fn to_qlog(&self) -> QuicFrame { -871 QuicFrame::ReachOut { -872 round: self.round.into_inner(), -873 ip_v4: match self.ip { -874 IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()), -875 IpAddr::V6(ipv6_addr) => None, -876 }, -877 ip_v6: match self.ip { -878 IpAddr::V4(ipv4_addr) => None, -879 IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()), -880 }, -881 port: self.port, -882 } -883 } -884} -885 -886#[cfg(feature = "qlog")] -887impl ToQlog for frame::Ping { -888 fn to_qlog(&self) -> QuicFrame { -889 QuicFrame::Ping { raw: None } -890 } -891} -892 -893#[cfg(feature = "qlog")] -894impl ToQlog for frame::PathStatusAvailable { -895 fn to_qlog(&self) -> QuicFrame { -896 QuicFrame::PathStatusAvailable { -897 path_id: self.path_id.as_u32().into(), -898 path_status_sequence_number: self.status_seq_no.into(), -899 raw: None, -900 } -901 } -902} -903 -904#[cfg(feature = "qlog")] -905impl ToQlog for frame::PathStatusBackup { -906 fn to_qlog(&self) -> QuicFrame { -907 QuicFrame::PathStatusBackup { -908 path_id: self.path_id.as_u32().into(), -909 path_status_sequence_number: self.status_seq_no.into(), -910 raw: None, -911 } -912 } -913} -914 -915#[cfg(feature = "qlog")] -916impl ToQlog for frame::PathsBlocked { -917 fn to_qlog(&self) -> QuicFrame { -918 QuicFrame::PathsBlocked { -919 maximum_path_id: self.0.as_u32().into(), -920 raw: None, -921 } -922 } -923} -924 -925#[cfg(feature = "qlog")] -926impl ToQlog for frame::ResetStream { -927 fn to_qlog(&self) -> QuicFrame { -928 QuicFrame::ResetStream { -929 stream_id: self.id.into(), -930 error_code: Some(self.error_code.into_inner()), -931 final_size: self.final_offset.into(), -932 error: ApplicationError::Unknown, -933 raw: None, -934 } -935 } -936} -937 -938#[cfg(feature = "qlog")] -939impl ToQlog for frame::StopSending { -940 fn to_qlog(&self) -> QuicFrame { -941 QuicFrame::StopSending { -942 stream_id: self.id.into(), -943 error_code: Some(self.error_code.into_inner()), -944 error: ApplicationError::Unknown, -945 raw: None, -946 } -947 } -948} -949 -950#[cfg(feature = "qlog")] -951impl ToQlog for frame::RetireConnectionId { -952 fn to_qlog(&self) -> QuicFrame { -953 match self.path_id { -954 None => QuicFrame::RetireConnectionId { -955 sequence_number: self.sequence, -956 raw: None, -957 }, -958 Some(path_id) => QuicFrame::PathRetireConnectionId { -959 path_id: path_id.0 as u64, -960 sequence_number: self.sequence, -961 raw: None, -962 }, -963 } -964 } -965} -966 -967#[cfg(feature = "qlog")] -968impl ToQlog for frame::RemoveAddress { -969 fn to_qlog(&self) -> QuicFrame { -970 QuicFrame::RemoveAddress { -971 sequence_number: self.seq_no.into_inner(), -972 } -973 } -974} -975 -976#[cfg(feature = "qlog")] -977impl ToQlog for frame::StreamMetaEncoder { -978 fn to_qlog(&self) -> QuicFrame { -979 let meta = &self.meta; -980 QuicFrame::Stream { -981 stream_id: meta.id.into(), -982 offset: Some(meta.offsets.start), -983 fin: Some(meta.fin), -984 raw: Some(RawInfo { -985 length: Some(meta.offsets.end - meta.offsets.start), -986 ..Default::default() -987 }), -988 } -989 } -990} -991 -992#[cfg(feature = "qlog")] -993impl Frame { -994 /// Converts a [`crate::Frame`] into a [`QuicFrame`]. -995 pub(crate) fn to_qlog(&self) -> QuicFrame { -996 match self { -997 Self::Padding => QuicFrame::Padding { -998 raw: Some(RawInfo { -999 length: None, -1000 payload_length: Some(1), -1001 data: None, -1002 }), -1003 }, -1004 Self::Ping => frame::Ping.to_qlog(), -1005 Self::Ack(f) => QuicFrame::Ack { -1006 ack_delay: Some(f.delay as f32), -1007 acked_ranges: Some(AckedRanges::Double( -1008 f.iter().map(|range| (range.start, range.end)).collect(), -1009 )), -1010 ect1: f.ecn.as_ref().map(|e| e.ect1), -1011 ect0: f.ecn.as_ref().map(|e| e.ect0), -1012 ce: f.ecn.as_ref().map(|e| e.ce), -1013 raw: None, -1014 }, -1015 Self::ResetStream(f) => f.to_qlog(), -1016 Self::StopSending(f) => f.to_qlog(), -1017 Self::Crypto(f) => f.to_qlog(), -1018 Self::NewToken(f) => f.to_qlog(), -1019 Self::Stream(s) => QuicFrame::Stream { -1020 stream_id: s.id.into(), -1021 offset: Some(s.offset), -1022 fin: Some(s.fin), -1023 raw: Some(RawInfo { -1024 length: Some(s.data.len() as u64), -1025 ..Default::default() -1026 }), -1027 }, -1028 Self::MaxData(v) => v.to_qlog(), -1029 Self::MaxStreamData(f) => f.to_qlog(), -1030 Self::MaxStreams(f) => f.to_qlog(), -1031 Self::DataBlocked(DataBlocked(offset)) => QuicFrame::DataBlocked { -1032 limit: *offset, -1033 raw: None, -1034 }, -1035 Self::StreamDataBlocked(StreamDataBlocked { id, offset }) => { -1036 QuicFrame::StreamDataBlocked { -1037 stream_id: (*id).into(), -1038 limit: *offset, -1039 raw: None, -1040 } -1041 } -1042 Self::StreamsBlocked(StreamsBlocked { dir, limit }) => QuicFrame::StreamsBlocked { -1043 stream_type: (*dir).into(), -1044 limit: *limit, -1045 raw: None, -1046 }, -1047 Self::NewConnectionId(f) => f.to_qlog(), -1048 Self::RetireConnectionId(f) => f.to_qlog(), -1049 Self::PathChallenge(f) => f.to_qlog(), -1050 Self::PathResponse(f) => f.to_qlog(), -1051 Self::Close(close) => close.to_qlog(), -1052 Self::Datagram(d) => d.to_qlog(), -1053 Self::HandshakeDone => frame::HandshakeDone.to_qlog(), -1054 Self::PathAck(ack) => QuicFrame::PathAck { -1055 path_id: ack.path_id.as_u32().into(), -1056 ack_delay: Some(ack.delay as f32), -1057 ect1: ack.ecn.as_ref().map(|e| e.ect1), -1058 ect0: ack.ecn.as_ref().map(|e| e.ect0), -1059 ce: ack.ecn.as_ref().map(|e| e.ce), -1060 raw: None, -1061 acked_ranges: Some(AckedRanges::Double( -1062 ack.ranges -1063 .iter() -1064 .map(|range| (range.start, range.end)) -1065 .collect(), -1066 )), -1067 }, -1068 Self::PathAbandon(frame) => frame.to_qlog(), -1069 Self::PathStatusAvailable(frame) => frame.to_qlog(), -1070 Self::PathStatusBackup(frame) => frame.to_qlog(), -1071 Self::PathsBlocked(frame) => frame.to_qlog(), -1072 Self::PathCidsBlocked(frame) => frame.to_qlog(), -1073 Self::MaxPathId(f) => f.to_qlog(), -1074 Self::AckFrequency(f) => f.to_qlog(), -1075 Self::ImmediateAck => frame::ImmediateAck.to_qlog(), -1076 Self::ObservedAddr(f) => f.to_qlog(), -1077 Self::AddAddress(f) => f.to_qlog(), -1078 Self::ReachOut(f) => f.to_qlog(), -1079 Self::RemoveAddress(f) => f.to_qlog(), -1080 } -1081 } -1082} -1083 -1084#[cfg(feature = "qlog")] -1085impl From<crate::Dir> for StreamType { -1086 fn from(value: crate::Dir) -> Self { -1087 match value { -1088 crate::Dir::Bi => Self::Bidirectional, -1089 crate::Dir::Uni => Self::Unidirectional, -1090 } -1091 } -1092} -1093 -1094#[cfg(feature = "qlog")] -1095fn packet_type(space: SpaceKind, is_0rtt: bool) -> PacketType { -1096 match space { -1097 SpaceKind::Initial => PacketType::Initial, -1098 SpaceKind::Handshake => PacketType::Handshake, -1099 SpaceKind::Data if is_0rtt => PacketType::ZeroRtt, -1100 SpaceKind::Data => PacketType::OneRtt, -1101 } -1102} -1103 -1104#[cfg(feature = "qlog")] -1105impl From<EncryptionLevel> for PacketType { -1106 fn from(encryption_level: EncryptionLevel) -> Self { -1107 match encryption_level { -1108 EncryptionLevel::Initial => Self::Initial, -1109 EncryptionLevel::Handshake => Self::Handshake, -1110 EncryptionLevel::ZeroRtt => Self::ZeroRtt, -1111 EncryptionLevel::OneRtt => Self::OneRtt, -1112 } -1113 } -1114} -1115 -1116#[cfg(feature = "qlog")] -1117fn stringify_cid(cid: ConnectionId) -> String { -1118 format!("{cid}") -1119} -1120 -1121#[cfg(feature = "qlog")] -1122fn tuple_endpoint_info( -1123 ip: Option<IpAddr>, -1124 port: Option<u16>, -1125 cid: Option<ConnectionId>, -1126) -> TupleEndpointInfo { -1127 let (ip_v4, port_v4, ip_v6, port_v6) = match ip { -1128 Some(addr) => match addr { -1129 IpAddr::V4(ipv4_addr) => (Some(ipv4_addr.to_string()), port, None, None), -1130 IpAddr::V6(ipv6_addr) => (None, None, Some(ipv6_addr.to_string()), port), -1131 }, -1132 None => (None, None, None, None), -1133 }; -1134 TupleEndpointInfo { -1135 ip_v4, -1136 port_v4, -1137 ip_v6, -1138 port_v6, -1139 connection_ids: cid.map(|cid| vec![cid.to_string()]), -1140 } -1141} -1142 -1143#[cfg(feature = "qlog")] -1144fn transport_error(code: TransportErrorCode) -> (Option<quic::TransportError>, Option<u64>) { -1145 let transport_error = match code { -1146 TransportErrorCode::NO_ERROR => Some(quic::TransportError::NoError), -1147 TransportErrorCode::INTERNAL_ERROR => Some(quic::TransportError::InternalError), -1148 TransportErrorCode::CONNECTION_REFUSED => Some(quic::TransportError::ConnectionRefused), -1149 TransportErrorCode::FLOW_CONTROL_ERROR => Some(quic::TransportError::FlowControlError), -1150 TransportErrorCode::STREAM_LIMIT_ERROR => Some(quic::TransportError::StreamLimitError), -1151 TransportErrorCode::STREAM_STATE_ERROR => Some(quic::TransportError::StreamStateError), -1152 TransportErrorCode::FINAL_SIZE_ERROR => Some(quic::TransportError::FinalSizeError), -1153 TransportErrorCode::FRAME_ENCODING_ERROR => Some(quic::TransportError::FrameEncodingError), -1154 TransportErrorCode::TRANSPORT_PARAMETER_ERROR => { -1155 Some(quic::TransportError::TransportParameterError) -1156 } -1157 TransportErrorCode::CONNECTION_ID_LIMIT_ERROR => { -1158 Some(quic::TransportError::ConnectionIdLimitError) +96 qlog::streamer::EventTimePrecision::MicroSeconds, +97 config.writer, +98 ); +99 +100 streamer.start_log()?; +101 Ok(Self(Arc::new(Mutex::new(streamer)))) +102 } +103 +104 fn emit_event(&self, event: EventData, now: Instant) { +105 self.emit_event_with_tuple_id(event, now, None); +106 } +107 +108 fn emit_event_with_tuple_id( +109 &self, +110 event: EventData, +111 now: Instant, +112 network_path: Option<String>, +113 ) { +114 // Time will be overwritten by `add_event_with_instant` +115 let mut event = Event::with_time(0.0, event); +116 event.tuple = network_path; +117 let mut qlog_streamer = self.0.lock().unwrap(); +118 if let Err(e) = qlog_streamer.add_event_with_instant(event, now) { +119 warn!("could not emit qlog event: {e}"); +120 } +121 } +122} +123 +124/// A [`QlogStream`] that may be either dynamically disabled or compiled out entirely +125#[derive(Clone, Default)] +126pub(crate) struct QlogSink { +127 #[cfg(feature = "qlog")] +128 stream: Option<QlogStream>, +129} +130 +131impl QlogSink { +132 #[cfg(feature = "qlog")] +133 pub(crate) fn new( +134 config: QlogConfig, +135 initial_dst_cid: ConnectionId, +136 side: Side, +137 now: Instant, +138 ) -> Self { +139 let stream = QlogStream::new(config, initial_dst_cid, side, now) +140 .inspect_err(|err| warn!("failed to initialize qlog streamer: {err}")) +141 .ok(); +142 Self { stream } +143 } +144 +145 pub(crate) fn emit_connection_started( +146 &self, +147 now: Instant, +148 local_cid: ConnectionId, +149 remote_cid: ConnectionId, +150 remote: SocketAddr, +151 local_ip: Option<IpAddr>, +152 transport_params: &TransportParameters, +153 ) { +154 #[cfg(feature = "qlog")] +155 { +156 let Some(stream) = self.stream.as_ref() else { +157 return; +158 }; +159 stream.emit_event( +160 EventData::QuicConnectionStarted(ConnectionStarted { +161 local: tuple_endpoint_info(local_ip, None, Some(local_cid)), +162 remote: tuple_endpoint_info( +163 Some(remote.ip()), +164 Some(remote.port()), +165 Some(remote_cid), +166 ), +167 }), +168 now, +169 ); +170 +171 let params = transport_params.to_qlog(TransportInitiator::Local); +172 let event = EventData::QuicParametersSet(Box::new(params)); +173 stream.emit_event(event, now); +174 } +175 } +176 +177 pub(super) fn emit_recovery_metrics(&self, path_id: PathId, path: &mut PathData, now: Instant) { +178 #[cfg(feature = "qlog")] +179 { +180 let Some(stream) = self.stream.as_ref() else { +181 return; +182 }; +183 +184 let Some(metrics) = path.qlog_recovery_metrics(path_id) else { +185 return; +186 }; +187 +188 stream.emit_event(EventData::QuicMetricsUpdated(metrics), now); +189 } +190 } +191 +192 pub(super) fn emit_packet_lost( +193 &self, +194 pn: u64, +195 info: &SentPacket, +196 loss_delay: Duration, +197 space: SpaceKind, +198 now: Instant, +199 ) { +200 #[cfg(feature = "qlog")] +201 { +202 let Some(stream) = self.stream.as_ref() else { +203 return; +204 }; +205 +206 let event = PacketLost { +207 header: Some(PacketHeader { +208 packet_number: Some(pn), +209 packet_type: packet_type(space, false), +210 length: Some(info.size), +211 ..Default::default() +212 }), +213 frames: None, +214 trigger: Some( +215 match info.time_sent.saturating_duration_since(now) >= loss_delay { +216 true => PacketLostTrigger::TimeThreshold, +217 false => PacketLostTrigger::ReorderingThreshold, +218 }, +219 ), +220 is_mtu_probe_packet: None, +221 }; +222 +223 stream.emit_event(EventData::QuicPacketLost(event), now); +224 } +225 } +226 +227 pub(super) fn emit_peer_transport_params_restored(&self, conn: &Connection, now: Instant) { +228 #[cfg(feature = "qlog")] +229 { +230 let Some(stream) = self.stream.as_ref() else { +231 return; +232 }; +233 let params = conn.peer_params.to_qlog_restored(); +234 let event = EventData::QuicParametersRestored(params); +235 stream.emit_event(event, now); +236 } +237 } +238 +239 pub(super) fn emit_peer_transport_params_received(&self, conn: &Connection, now: Instant) { +240 #[cfg(feature = "qlog")] +241 { +242 let Some(stream) = self.stream.as_ref() else { +243 return; +244 }; +245 let params = conn.peer_params.to_qlog(TransportInitiator::Remote); +246 let event = EventData::QuicParametersSet(Box::new(params)); +247 stream.emit_event(event, now); +248 } +249 } +250 +251 pub(super) fn emit_tuple_assigned(&self, path_id: PathId, tuple: FourTuple, now: Instant) { +252 #[cfg(feature = "qlog")] +253 { +254 let Some(stream) = self.stream.as_ref() else { +255 return; +256 }; +257 let tuple_id = fmt_tuple_id(path_id.as_u32() as u64); +258 let event = TupleAssigned { +259 tuple_id, +260 tuple_local: tuple +261 .local_ip +262 .map(|local_ip| tuple_endpoint_info(Some(local_ip), None, None)), +263 tuple_remote: Some(tuple_endpoint_info( +264 Some(tuple.remote.ip()), +265 Some(tuple.remote.port()), +266 None, +267 )), +268 }; +269 +270 stream.emit_event(EventData::QuicTupleAssigned(event), now); +271 } +272 } +273 +274 pub(super) fn emit_packet_sent(&self, packet: QlogSentPacket, now: Instant) { +275 #[cfg(feature = "qlog")] +276 { +277 let Some(stream) = self.stream.as_ref() else { +278 return; +279 }; +280 let tuple_id = packet.inner.header.path_id.map(fmt_tuple_id); +281 stream.emit_event_with_tuple_id(EventData::QuicPacketSent(packet.inner), now, tuple_id); +282 } +283 } +284 +285 pub(super) fn emit_packet_received(&self, packet: QlogRecvPacket, now: Instant) { +286 #[cfg(feature = "qlog")] +287 { +288 let Some(stream) = self.stream.as_ref() else { +289 return; +290 }; +291 let mut packet = packet; +292 packet.emit_padding(); +293 let tuple_id = packet.inner.header.path_id.map(fmt_tuple_id); +294 let event = packet.inner; +295 stream.emit_event_with_tuple_id(EventData::QuicPacketReceived(event), now, tuple_id); +296 } +297 } +298 +299 /// Emits a timer event. +300 /// +301 /// This function is not public: Instead, create a [`QlogSinkWithTime`] via [`Self::with_time`] and use +302 /// its `emit_timer_` methods. +303 #[cfg(feature = "qlog")] +304 fn emit_timer(&self, timer: Timer, op: TimerOp, now: Instant) { +305 let Some(stream) = self.stream.as_ref() else { +306 return; +307 }; +308 +309 let timer_type: Option<TimerType> = match timer { +310 Timer::Conn(conn_timer) => match conn_timer { +311 ConnTimer::Idle => Some(QlogTimerType::IdleTimeout.into()), +312 ConnTimer::Close => Some(TimerType::custom("close")), +313 ConnTimer::KeyDiscard => Some(TimerType::custom("key_discard")), +314 ConnTimer::KeepAlive => Some(TimerType::custom("keep_alive")), +315 ConnTimer::PushNewCid => Some(TimerType::custom("push_new_cid")), +316 ConnTimer::NoAvailablePath => Some(TimerType::custom("no_available_path")), +317 ConnTimer::NatTraversalProbeRetry => { +318 Some(TimerType::custom("nat_traversal_probe_retry")) +319 } +320 }, +321 Timer::PerPath(_, path_timer) => match path_timer { +322 PathTimer::LossDetection => Some(QlogTimerType::LossTimeout.into()), +323 PathTimer::PathIdle => Some(TimerType::custom("path_idle")), +324 PathTimer::PathValidationFailed => Some(QlogTimerType::PathValidation.into()), +325 PathTimer::PathChallengeLost => Some(TimerType::custom("path_challenge_lost")), +326 PathTimer::AbandonFromValidation => { +327 Some(TimerType::custom("abandon_from_validation")) +328 } +329 PathTimer::PathKeepAlive => Some(TimerType::custom("path_keep_alive")), +330 PathTimer::Pacing => Some(TimerType::custom("pacing")), +331 PathTimer::MaxAckDelay => Some(QlogTimerType::Ack.into()), +332 PathTimer::PathDrained => Some(TimerType::custom("discard_path")), +333 }, +334 }; +335 +336 let Some(timer_type) = timer_type else { +337 return; +338 }; +339 +340 let delta = match op { +341 TimerOp::Set(instant) => instant +342 .checked_duration_since(now) +343 .map(|dur| dur.as_secs_f32() * 1000.), +344 _ => None, +345 }; +346 let path_id = match timer { +347 Timer::Conn(_) => None, +348 Timer::PerPath(path_id, _) => Some(path_id.as_u32() as u64), +349 }; +350 +351 let event_type = match op { +352 TimerOp::Set(_) => TimerEventType::Set, +353 TimerOp::Expire => TimerEventType::Expired, +354 TimerOp::Cancelled => TimerEventType::Cancelled, +355 }; +356 +357 let event = TimerUpdated { +358 path_id, +359 timer_type: Some(timer_type), +360 timer_id: None, +361 packet_number_space: None, +362 event_type, +363 delta, +364 }; +365 stream.emit_event(EventData::QuicTimerUpdated(event), now); +366 } +367 +368 /// Returns a [`QlogSinkWithTime`] that passes along a `now` timestamp. +369 /// +370 /// This may be used if you want to pass a [`QlogSink`] downwards together with the current +371 /// `now` timestamp, to not have to pass the latter separately as an additional argument just +372 /// for qlog support. +373 pub(super) fn with_time(&self, now: Instant) -> QlogSinkWithTime<'_> { +374 #[cfg(feature = "qlog")] +375 let s = QlogSinkWithTime { sink: self, now }; +376 #[cfg(not(feature = "qlog"))] +377 let s = QlogSinkWithTime { +378 _phantom: PhantomData, +379 }; +380 s +381 } +382} +383 +384/// A [`QlogSink`] with a `now` timestamp. +385#[derive(Clone)] +386pub(super) struct QlogSinkWithTime<'a> { +387 #[cfg(feature = "qlog")] +388 sink: &'a QlogSink, +389 #[cfg(feature = "qlog")] +390 now: Instant, +391 #[cfg(not(feature = "qlog"))] +392 _phantom: PhantomData<&'a ()>, +393} +394 +395impl<'a> QlogSinkWithTime<'a> { +396 pub(super) fn emit_timer_stop(&self, timer: Timer) { +397 #[cfg(feature = "qlog")] +398 self.sink.emit_timer(timer, TimerOp::Cancelled, self.now) +399 } +400 +401 pub(super) fn emit_timer_set(&self, timer: Timer, expire_at: Instant) { +402 #[cfg(feature = "qlog")] +403 self.sink +404 .emit_timer(timer, TimerOp::Set(expire_at), self.now) +405 } +406 +407 pub(super) fn emit_timer_expire(&self, timer: Timer) { +408 #[cfg(feature = "qlog")] +409 self.sink.emit_timer(timer, TimerOp::Expire, self.now) +410 } +411} +412 +413#[cfg(feature = "qlog")] +414enum TimerOp { +415 Set(Instant), +416 Expire, +417 Cancelled, +418} +419 +420/// Info about a sent packet. Zero-sized struct if `qlog` feature is not enabled. +421#[derive(Default)] +422pub(crate) struct QlogSentPacket { +423 #[cfg(feature = "qlog")] +424 inner: PacketSent, +425} +426 +427impl QlogSentPacket { +428 /// Sets data from the packet header. +429 pub(crate) fn header( +430 &mut self, +431 header: &Header, +432 pn: Option<u64>, +433 encryption_level: EncryptionLevel, +434 path_id: PathId, +435 ) { +436 #[cfg(feature = "qlog")] +437 { +438 self.inner.header.scid = header.src_cid().map(stringify_cid); +439 self.inner.header.dcid = Some(stringify_cid(header.dst_cid())); +440 self.inner.header.packet_number = pn; +441 self.inner.header.packet_type = encryption_level.into(); +442 self.inner.header.path_id = Some(path_id.as_u32() as u64); +443 } +444 } +445 +446 /// Adds a PADDING frame. +447 /// +448 /// This is a no-op if the `qlog` feature is not enabled. +449 pub(crate) fn frame_padding(&mut self, count: usize) { +450 #[cfg(feature = "qlog")] +451 self.frame_raw(QuicFrame::Padding { +452 raw: Some(Box::new(RawInfo { +453 length: Some(count as u64), +454 payload_length: Some(count as u64), +455 data: None, +456 })), +457 }); +458 } +459 +460 /// Adds a frame by pushing a [`QuicFrame`]. +461 /// +462 /// This function is only available if the `qlog` feature is enabled, because constructing a [`QuicFrame`] may involve +463 /// calculations which shouldn't be performed if the `qlog` feature is disabled. +464 #[cfg(feature = "qlog")] +465 fn frame_raw(&mut self, frame: QuicFrame) { +466 self.inner.frames.get_or_insert_default().push(frame); +467 } +468 +469 /// Finalizes the packet by setting the final packet length (after encryption). +470 pub(super) fn finalize(&mut self, len: usize) { +471 #[cfg(feature = "qlog")] +472 { +473 self.inner.header.length = Some(len as u16); +474 } +475 } +476 +477 pub(crate) fn record<'a>(&mut self, frame: &EncodableFrame<'a>) { +478 #[cfg(feature = "qlog")] +479 self.frame_raw(frame.to_qlog()); +480 } +481} +482 +483/// Info about a received packet. Zero-sized struct if `qlog` feature is not enabled. +484pub(crate) struct QlogRecvPacket { +485 #[cfg(feature = "qlog")] +486 inner: PacketReceived, +487 #[cfg(feature = "qlog")] +488 padding: usize, +489} +490 +491impl QlogRecvPacket { +492 /// Creates a new [`QlogRecvPacket`]. Noop if `qlog` feature is not enabled. +493 /// +494 /// `len` is the packet's full length (before decryption). +495 pub(crate) fn new(len: usize) -> Self { +496 #[cfg(not(feature = "qlog"))] +497 let this = Self {}; +498 +499 #[cfg(feature = "qlog")] +500 let this = { +501 let mut this = Self { +502 inner: Default::default(), +503 padding: 0, +504 }; +505 this.inner.header.length = Some(len as u16); +506 this +507 }; +508 +509 this +510 } +511 +512 /// Adds info from the packet header. +513 pub(crate) fn header(&mut self, header: &Header, pn: Option<u64>, path_id: PathId) { +514 #[cfg(feature = "qlog")] +515 { +516 let is_0rtt = !header.is_1rtt(); +517 self.inner.header.scid = header.src_cid().map(stringify_cid); +518 self.inner.header.dcid = Some(stringify_cid(header.dst_cid())); +519 self.inner.header.packet_number = pn; +520 self.inner.header.packet_type = packet_type(header.space(), is_0rtt); +521 self.inner.header.path_id = Some(path_id.as_u32() as u64); +522 } +523 } +524 +525 /// Adds a frame. +526 pub(crate) fn frame(&mut self, frame: &Frame) { +527 #[cfg(feature = "qlog")] +528 { +529 if matches!(frame, crate::Frame::Padding) { +530 self.padding += 1; +531 } else { +532 self.emit_padding(); +533 self.inner +534 .frames +535 .get_or_insert_default() +536 .push(frame.to_qlog()) +537 } +538 } +539 } +540 +541 #[cfg(feature = "qlog")] +542 fn emit_padding(&mut self) { +543 if self.padding > 0 { +544 self.inner +545 .frames +546 .get_or_insert_default() +547 .push(QuicFrame::Padding { +548 raw: Some(Box::new(RawInfo { +549 length: Some(self.padding as u64), +550 payload_length: Some(self.padding as u64), +551 data: None, +552 })), +553 }); +554 self.padding = 0; +555 } +556 } +557} +558 +559/* Frame conversions to qlog */ +560#[cfg(feature = "qlog")] +561pub(crate) trait ToQlog { +562 fn to_qlog(&self) -> QuicFrame; +563} +564 +565#[cfg(feature = "qlog")] +566impl<'a> ToQlog for frame::AckEncoder<'a> { +567 fn to_qlog(&self) -> QuicFrame { +568 QuicFrame::Ack { +569 ack_delay: Some(self.delay as f32), +570 acked_ranges: Some( +571 self.ranges +572 .iter() +573 .map(|range| AckRange::new(range.start, range.end)) +574 .collect(), +575 ), +576 ect1: self.ecn.map(|e| e.ect1), +577 ect0: self.ecn.map(|e| e.ect0), +578 ce: self.ecn.map(|e| e.ce), +579 raw: None, +580 } +581 } +582} +583 +584#[cfg(feature = "qlog")] +585impl ToQlog for frame::AckFrequency { +586 fn to_qlog(&self) -> QuicFrame { +587 QuicFrame::AckFrequency { +588 sequence_number: self.sequence.into_inner(), +589 ack_eliciting_threshold: self.ack_eliciting_threshold.into_inner(), +590 requested_max_ack_delay: self.request_max_ack_delay.into_inner(), +591 reordering_threshold: self.reordering_threshold.into_inner(), +592 raw: None, +593 } +594 } +595} +596 +597#[cfg(feature = "qlog")] +598impl ToQlog for frame::AddAddress { +599 fn to_qlog(&self) -> QuicFrame { +600 QuicFrame::AddAddress { +601 sequence_number: self.seq_no.into_inner(), +602 ip_v4: match self.ip { +603 IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()), +604 IpAddr::V6(ipv6_addr) => None, +605 }, +606 ip_v6: match self.ip { +607 IpAddr::V4(ipv4_addr) => None, +608 IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()), +609 }, +610 port: self.port, +611 } +612 } +613} +614 +615#[cfg(feature = "qlog")] +616impl ToQlog for frame::CloseEncoder<'_> { +617 fn to_qlog(&self) -> QuicFrame { +618 self.close.to_qlog() +619 } +620} +621 +622#[cfg(feature = "qlog")] +623impl ToQlog for frame::Close { +624 fn to_qlog(&self) -> QuicFrame { +625 match self { +626 Self::Connection(f) => { +627 let (error, error_code) = transport_error(f.error_code); +628 let error = error.map(|transport_error| { +629 ConnectionClosedFrameError::TransportError(transport_error) +630 }); +631 QuicFrame::ConnectionClose { +632 error_space: Some(ErrorSpace::Transport), +633 error, +634 error_code, +635 reason: String::from_utf8(f.reason.to_vec()).ok(), +636 reason_bytes: None, +637 trigger_frame_type: None, +638 } +639 } +640 Self::Application(f) => QuicFrame::ConnectionClose { +641 error_space: Some(ErrorSpace::Application), +642 error: None, +643 error_code: Some(f.error_code.into_inner()), +644 reason: String::from_utf8(f.reason.to_vec()).ok(), +645 reason_bytes: None, +646 trigger_frame_type: None, +647 }, +648 } +649 } +650} +651 +652#[cfg(feature = "qlog")] +653impl ToQlog for frame::Crypto { +654 fn to_qlog(&self) -> QuicFrame { +655 QuicFrame::Crypto { +656 offset: self.offset, +657 raw: Some(Box::new(RawInfo { +658 length: Some(self.data.len() as u64), +659 ..Default::default() +660 })), +661 } +662 } +663} +664 +665#[cfg(feature = "qlog")] +666impl ToQlog for frame::Datagram { +667 fn to_qlog(&self) -> QuicFrame { +668 QuicFrame::Datagram { +669 raw: Some(Box::new(RawInfo { +670 length: Some(self.data.len() as u64), +671 ..Default::default() +672 })), +673 } +674 } +675} +676 +677#[cfg(feature = "qlog")] +678impl ToQlog for frame::HandshakeDone { +679 fn to_qlog(&self) -> QuicFrame { +680 QuicFrame::HandshakeDone { raw: None } +681 } +682} +683 +684#[cfg(feature = "qlog")] +685impl ToQlog for frame::ImmediateAck { +686 fn to_qlog(&self) -> QuicFrame { +687 QuicFrame::ImmediateAck { raw: None } +688 } +689} +690 +691#[cfg(feature = "qlog")] +692impl ToQlog for frame::MaxData { +693 fn to_qlog(&self) -> QuicFrame { +694 QuicFrame::MaxData { +695 maximum: self.0.into(), +696 raw: None, +697 } +698 } +699} +700 +701#[cfg(feature = "qlog")] +702impl ToQlog for frame::MaxPathId { +703 fn to_qlog(&self) -> QuicFrame { +704 QuicFrame::MaxPathId { +705 maximum_path_id: self.0.as_u32().into(), +706 raw: None, +707 } +708 } +709} +710 +711#[cfg(feature = "qlog")] +712impl ToQlog for frame::MaxStreamData { +713 fn to_qlog(&self) -> QuicFrame { +714 QuicFrame::MaxStreamData { +715 stream_id: self.id.into(), +716 maximum: self.offset, +717 raw: None, +718 } +719 } +720} +721 +722#[cfg(feature = "qlog")] +723impl ToQlog for frame::MaxStreams { +724 fn to_qlog(&self) -> QuicFrame { +725 QuicFrame::MaxStreams { +726 maximum: self.count, +727 stream_type: self.dir.into(), +728 raw: None, +729 } +730 } +731} +732 +733#[cfg(feature = "qlog")] +734impl ToQlog for frame::StreamsBlocked { +735 fn to_qlog(&self) -> QuicFrame { +736 QuicFrame::StreamsBlocked { +737 stream_type: self.dir.into(), +738 limit: self.limit, +739 raw: None, +740 } +741 } +742} +743 +744#[cfg(feature = "qlog")] +745impl ToQlog for frame::NewConnectionId { +746 fn to_qlog(&self) -> QuicFrame { +747 match self.path_id { +748 None => QuicFrame::NewConnectionId { +749 sequence_number: self.sequence, +750 retire_prior_to: self.retire_prior_to, +751 connection_id_length: Some(self.id.len() as u8), +752 connection_id: self.id.to_string(), +753 stateless_reset_token: Some(self.reset_token.to_string()), +754 raw: None, +755 }, +756 Some(path_id) => QuicFrame::PathNewConnectionId { +757 path_id: path_id.0 as u64, +758 sequence_number: self.sequence, +759 retire_prior_to: self.retire_prior_to, +760 connection_id_length: Some(self.id.len() as u8), +761 connection_id: self.id.to_string(), +762 stateless_reset_token: Some(self.reset_token.to_string()), +763 raw: None, +764 }, +765 } +766 } +767} +768 +769#[cfg(feature = "qlog")] +770impl ToQlog for frame::NewToken { +771 fn to_qlog(&self) -> QuicFrame { +772 QuicFrame::NewToken { +773 token: qlog::Token { +774 ty: Some(TokenType::Retry), +775 raw: Some(RawInfo { +776 data: HexSlice::maybe_string(Some(&self.token)).map(Box::new), +777 length: Some(self.token.len() as u64), +778 payload_length: None, +779 }), +780 details: None, +781 }, +782 raw: None, +783 } +784 } +785} +786 +787#[cfg(feature = "qlog")] +788impl ToQlog for frame::ObservedAddr { +789 fn to_qlog(&self) -> QuicFrame { +790 QuicFrame::ObservedAddress { +791 sequence_number: self.seq_no.into_inner(), +792 ip_v4: match self.ip { +793 IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()), +794 IpAddr::V6(ipv6_addr) => None, +795 }, +796 ip_v6: match self.ip { +797 IpAddr::V4(ipv4_addr) => None, +798 IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()), +799 }, +800 port: self.port, +801 raw: None, +802 } +803 } +804} +805 +806#[cfg(feature = "qlog")] +807impl ToQlog for frame::PathAbandon { +808 fn to_qlog(&self) -> QuicFrame { +809 QuicFrame::PathAbandon { +810 path_id: self.path_id.as_u32().into(), +811 error_code: self.error_code.into(), +812 raw: None, +813 } +814 } +815} +816 +817#[cfg(feature = "qlog")] +818impl ToQlog for frame::PathAckEncoder<'_> { +819 fn to_qlog(&self) -> QuicFrame { +820 QuicFrame::PathAck { +821 path_id: self.path_id.as_u32() as u64, +822 ack_delay: Some(self.delay as f32), +823 acked_ranges: Some( +824 self.ranges +825 .iter() +826 .map(|range| AckRange::new(range.start, range.end)) +827 .collect(), +828 ), +829 ect1: self.ecn.map(|e| e.ect1), +830 ect0: self.ecn.map(|e| e.ect0), +831 ce: self.ecn.map(|e| e.ce), +832 raw: None, +833 } +834 } +835} +836 +837#[cfg(feature = "qlog")] +838impl ToQlog for frame::PathChallenge { +839 #[cfg(feature = "qlog")] +840 fn to_qlog(&self) -> QuicFrame { +841 QuicFrame::PathChallenge { +842 data: Some(self.to_string()), +843 raw: None, +844 } +845 } +846} +847 +848#[cfg(feature = "qlog")] +849impl ToQlog for frame::PathCidsBlocked { +850 fn to_qlog(&self) -> QuicFrame { +851 QuicFrame::PathCidsBlocked { +852 path_id: self.path_id.as_u32().into(), +853 next_sequence_number: self.next_seq.into(), +854 raw: None, +855 } +856 } +857} +858 +859#[cfg(feature = "qlog")] +860impl ToQlog for frame::PathResponse { +861 fn to_qlog(&self) -> QuicFrame { +862 QuicFrame::PathResponse { +863 data: Some(self.to_string()), +864 raw: None, +865 } +866 } +867} +868 +869#[cfg(feature = "qlog")] +870impl ToQlog for frame::ReachOut { +871 fn to_qlog(&self) -> QuicFrame { +872 QuicFrame::ReachOut { +873 round: self.round.into_inner(), +874 ip_v4: match self.ip { +875 IpAddr::V4(ipv4_addr) => Some(ipv4_addr.to_string()), +876 IpAddr::V6(ipv6_addr) => None, +877 }, +878 ip_v6: match self.ip { +879 IpAddr::V4(ipv4_addr) => None, +880 IpAddr::V6(ipv6_addr) => Some(ipv6_addr.to_string()), +881 }, +882 port: self.port, +883 } +884 } +885} +886 +887#[cfg(feature = "qlog")] +888impl ToQlog for frame::Ping { +889 fn to_qlog(&self) -> QuicFrame { +890 QuicFrame::Ping { raw: None } +891 } +892} +893 +894#[cfg(feature = "qlog")] +895impl ToQlog for frame::PathStatusAvailable { +896 fn to_qlog(&self) -> QuicFrame { +897 QuicFrame::PathStatusAvailable { +898 path_id: self.path_id.as_u32().into(), +899 path_status_sequence_number: self.status_seq_no.into(), +900 raw: None, +901 } +902 } +903} +904 +905#[cfg(feature = "qlog")] +906impl ToQlog for frame::PathStatusBackup { +907 fn to_qlog(&self) -> QuicFrame { +908 QuicFrame::PathStatusBackup { +909 path_id: self.path_id.as_u32().into(), +910 path_status_sequence_number: self.status_seq_no.into(), +911 raw: None, +912 } +913 } +914} +915 +916#[cfg(feature = "qlog")] +917impl ToQlog for frame::PathsBlocked { +918 fn to_qlog(&self) -> QuicFrame { +919 QuicFrame::PathsBlocked { +920 maximum_path_id: self.0.as_u32().into(), +921 raw: None, +922 } +923 } +924} +925 +926#[cfg(feature = "qlog")] +927impl ToQlog for frame::ResetStream { +928 fn to_qlog(&self) -> QuicFrame { +929 QuicFrame::ResetStream { +930 stream_id: self.id.into(), +931 error_code: Some(self.error_code.into_inner()), +932 final_size: self.final_offset.into(), +933 error: ApplicationError::Unknown, +934 raw: None, +935 } +936 } +937} +938 +939#[cfg(feature = "qlog")] +940impl ToQlog for frame::StopSending { +941 fn to_qlog(&self) -> QuicFrame { +942 QuicFrame::StopSending { +943 stream_id: self.id.into(), +944 error_code: Some(self.error_code.into_inner()), +945 error: ApplicationError::Unknown, +946 raw: None, +947 } +948 } +949} +950 +951#[cfg(feature = "qlog")] +952impl ToQlog for frame::RetireConnectionId { +953 fn to_qlog(&self) -> QuicFrame { +954 match self.path_id { +955 None => QuicFrame::RetireConnectionId { +956 sequence_number: self.sequence, +957 raw: None, +958 }, +959 Some(path_id) => QuicFrame::PathRetireConnectionId { +960 path_id: path_id.0 as u64, +961 sequence_number: self.sequence, +962 raw: None, +963 }, +964 } +965 } +966} +967 +968#[cfg(feature = "qlog")] +969impl ToQlog for frame::RemoveAddress { +970 fn to_qlog(&self) -> QuicFrame { +971 QuicFrame::RemoveAddress { +972 sequence_number: self.seq_no.into_inner(), +973 } +974 } +975} +976 +977#[cfg(feature = "qlog")] +978impl ToQlog for frame::StreamMetaEncoder { +979 fn to_qlog(&self) -> QuicFrame { +980 let meta = &self.meta; +981 QuicFrame::Stream { +982 stream_id: meta.id.into(), +983 offset: Some(meta.offsets.start), +984 fin: Some(meta.fin), +985 raw: Some(Box::new(RawInfo { +986 length: Some(meta.offsets.end - meta.offsets.start), +987 ..Default::default() +988 })), +989 } +990 } +991} +992 +993#[cfg(feature = "qlog")] +994impl Frame { +995 /// Converts a [`crate::Frame`] into a [`QuicFrame`]. +996 pub(crate) fn to_qlog(&self) -> QuicFrame { +997 match self { +998 Self::Padding => QuicFrame::Padding { +999 raw: Some(Box::new(RawInfo { +1000 length: None, +1001 payload_length: Some(1), +1002 data: None, +1003 })), +1004 }, +1005 Self::Ping => frame::Ping.to_qlog(), +1006 Self::Ack(f) => QuicFrame::Ack { +1007 ack_delay: Some(f.delay as f32), +1008 acked_ranges: Some( +1009 f.iter() +1010 .map(|range| AckRange::new(range.start, range.end)) +1011 .collect(), +1012 ), +1013 ect1: f.ecn.as_ref().map(|e| e.ect1), +1014 ect0: f.ecn.as_ref().map(|e| e.ect0), +1015 ce: f.ecn.as_ref().map(|e| e.ce), +1016 raw: None, +1017 }, +1018 Self::ResetStream(f) => f.to_qlog(), +1019 Self::StopSending(f) => f.to_qlog(), +1020 Self::Crypto(f) => f.to_qlog(), +1021 Self::NewToken(f) => f.to_qlog(), +1022 Self::Stream(s) => QuicFrame::Stream { +1023 stream_id: s.id.into(), +1024 offset: Some(s.offset), +1025 fin: Some(s.fin), +1026 raw: Some(Box::new(RawInfo { +1027 length: Some(s.data.len() as u64), +1028 ..Default::default() +1029 })), +1030 }, +1031 Self::MaxData(v) => v.to_qlog(), +1032 Self::MaxStreamData(f) => f.to_qlog(), +1033 Self::MaxStreams(f) => f.to_qlog(), +1034 Self::DataBlocked(DataBlocked(offset)) => QuicFrame::DataBlocked { +1035 limit: *offset, +1036 raw: None, +1037 }, +1038 Self::StreamDataBlocked(StreamDataBlocked { id, offset }) => { +1039 QuicFrame::StreamDataBlocked { +1040 stream_id: (*id).into(), +1041 limit: *offset, +1042 raw: None, +1043 } +1044 } +1045 Self::StreamsBlocked(StreamsBlocked { dir, limit }) => QuicFrame::StreamsBlocked { +1046 stream_type: (*dir).into(), +1047 limit: *limit, +1048 raw: None, +1049 }, +1050 Self::NewConnectionId(f) => f.to_qlog(), +1051 Self::RetireConnectionId(f) => f.to_qlog(), +1052 Self::PathChallenge(f) => f.to_qlog(), +1053 Self::PathResponse(f) => f.to_qlog(), +1054 Self::Close(close) => close.to_qlog(), +1055 Self::Datagram(d) => d.to_qlog(), +1056 Self::HandshakeDone => frame::HandshakeDone.to_qlog(), +1057 Self::PathAck(ack) => QuicFrame::PathAck { +1058 path_id: ack.path_id.as_u32().into(), +1059 ack_delay: Some(ack.delay as f32), +1060 ect1: ack.ecn.as_ref().map(|e| e.ect1), +1061 ect0: ack.ecn.as_ref().map(|e| e.ect0), +1062 ce: ack.ecn.as_ref().map(|e| e.ce), +1063 raw: None, +1064 acked_ranges: Some( +1065 ack.ranges +1066 .iter() +1067 .map(|range| AckRange::new(range.start, range.end)) +1068 .collect(), +1069 ), +1070 }, +1071 Self::PathAbandon(frame) => frame.to_qlog(), +1072 Self::PathStatusAvailable(frame) => frame.to_qlog(), +1073 Self::PathStatusBackup(frame) => frame.to_qlog(), +1074 Self::PathsBlocked(frame) => frame.to_qlog(), +1075 Self::PathCidsBlocked(frame) => frame.to_qlog(), +1076 Self::MaxPathId(f) => f.to_qlog(), +1077 Self::AckFrequency(f) => f.to_qlog(), +1078 Self::ImmediateAck => frame::ImmediateAck.to_qlog(), +1079 Self::ObservedAddr(f) => f.to_qlog(), +1080 Self::AddAddress(f) => f.to_qlog(), +1081 Self::ReachOut(f) => f.to_qlog(), +1082 Self::RemoveAddress(f) => f.to_qlog(), +1083 } +1084 } +1085} +1086 +1087#[cfg(feature = "qlog")] +1088impl From<crate::Dir> for StreamType { +1089 fn from(value: crate::Dir) -> Self { +1090 match value { +1091 crate::Dir::Bi => Self::Bidirectional, +1092 crate::Dir::Uni => Self::Unidirectional, +1093 } +1094 } +1095} +1096 +1097#[cfg(feature = "qlog")] +1098fn packet_type(space: SpaceKind, is_0rtt: bool) -> PacketType { +1099 match space { +1100 SpaceKind::Initial => PacketType::Initial, +1101 SpaceKind::Handshake => PacketType::Handshake, +1102 SpaceKind::Data if is_0rtt => PacketType::ZeroRtt, +1103 SpaceKind::Data => PacketType::OneRtt, +1104 } +1105} +1106 +1107#[cfg(feature = "qlog")] +1108impl From<EncryptionLevel> for PacketType { +1109 fn from(encryption_level: EncryptionLevel) -> Self { +1110 match encryption_level { +1111 EncryptionLevel::Initial => Self::Initial, +1112 EncryptionLevel::Handshake => Self::Handshake, +1113 EncryptionLevel::ZeroRtt => Self::ZeroRtt, +1114 EncryptionLevel::OneRtt => Self::OneRtt, +1115 } +1116 } +1117} +1118 +1119#[cfg(feature = "qlog")] +1120fn stringify_cid(cid: ConnectionId) -> String { +1121 format!("{cid}") +1122} +1123 +1124#[cfg(feature = "qlog")] +1125fn tuple_endpoint_info( +1126 ip: Option<IpAddr>, +1127 port: Option<u16>, +1128 cid: Option<ConnectionId>, +1129) -> TupleEndpointInfo { +1130 let (ip_v4, port_v4, ip_v6, port_v6) = match ip { +1131 Some(addr) => match addr { +1132 IpAddr::V4(ipv4_addr) => (Some(ipv4_addr.to_string()), port, None, None), +1133 IpAddr::V6(ipv6_addr) => (None, None, Some(ipv6_addr.to_string()), port), +1134 }, +1135 None => (None, None, None, None), +1136 }; +1137 TupleEndpointInfo { +1138 ip_v4, +1139 port_v4, +1140 ip_v6, +1141 port_v6, +1142 connection_ids: cid.map(|cid| vec![cid.to_string()]), +1143 } +1144} +1145 +1146#[cfg(feature = "qlog")] +1147fn transport_error(code: TransportErrorCode) -> (Option<quic::TransportError>, Option<u64>) { +1148 let transport_error = match code { +1149 TransportErrorCode::NO_ERROR => Some(quic::TransportError::NoError), +1150 TransportErrorCode::INTERNAL_ERROR => Some(quic::TransportError::InternalError), +1151 TransportErrorCode::CONNECTION_REFUSED => Some(quic::TransportError::ConnectionRefused), +1152 TransportErrorCode::FLOW_CONTROL_ERROR => Some(quic::TransportError::FlowControlError), +1153 TransportErrorCode::STREAM_LIMIT_ERROR => Some(quic::TransportError::StreamLimitError), +1154 TransportErrorCode::STREAM_STATE_ERROR => Some(quic::TransportError::StreamStateError), +1155 TransportErrorCode::FINAL_SIZE_ERROR => Some(quic::TransportError::FinalSizeError), +1156 TransportErrorCode::FRAME_ENCODING_ERROR => Some(quic::TransportError::FrameEncodingError), +1157 TransportErrorCode::TRANSPORT_PARAMETER_ERROR => { +1158 Some(quic::TransportError::TransportParameterError) 1159 } -1160 TransportErrorCode::PROTOCOL_VIOLATION => Some(quic::TransportError::ProtocolViolation), -1161 TransportErrorCode::INVALID_TOKEN => Some(quic::TransportError::InvalidToken), -1162 TransportErrorCode::APPLICATION_ERROR => Some(quic::TransportError::ApplicationError), -1163 TransportErrorCode::CRYPTO_BUFFER_EXCEEDED => { -1164 Some(quic::TransportError::CryptoBufferExceeded) -1165 } -1166 TransportErrorCode::KEY_UPDATE_ERROR => Some(quic::TransportError::KeyUpdateError), -1167 TransportErrorCode::AEAD_LIMIT_REACHED => Some(quic::TransportError::AeadLimitReached), -1168 TransportErrorCode::NO_VIABLE_PATH => Some(quic::TransportError::NoViablePath), -1169 // multipath -1170 TransportErrorCode::APPLICATION_ABANDON_PATH => { -1171 Some(quic::TransportError::ApplicationAbandonPath) -1172 } -1173 TransportErrorCode::PATH_RESOURCE_LIMIT_REACHED => { -1174 Some(quic::TransportError::PathResourceLimitReached) +1160 TransportErrorCode::CONNECTION_ID_LIMIT_ERROR => { +1161 Some(quic::TransportError::ConnectionIdLimitError) +1162 } +1163 TransportErrorCode::PROTOCOL_VIOLATION => Some(quic::TransportError::ProtocolViolation), +1164 TransportErrorCode::INVALID_TOKEN => Some(quic::TransportError::InvalidToken), +1165 TransportErrorCode::APPLICATION_ERROR => Some(quic::TransportError::ApplicationError), +1166 TransportErrorCode::CRYPTO_BUFFER_EXCEEDED => { +1167 Some(quic::TransportError::CryptoBufferExceeded) +1168 } +1169 TransportErrorCode::KEY_UPDATE_ERROR => Some(quic::TransportError::KeyUpdateError), +1170 TransportErrorCode::AEAD_LIMIT_REACHED => Some(quic::TransportError::AeadLimitReached), +1171 TransportErrorCode::NO_VIABLE_PATH => Some(quic::TransportError::NoViablePath), +1172 // multipath +1173 TransportErrorCode::APPLICATION_ABANDON_PATH => { +1174 Some(quic::TransportError::ApplicationAbandonPath) 1175 } -1176 TransportErrorCode::PATH_UNSTABLE_OR_POOR => Some(quic::TransportError::PathUnstableOrPoor), -1177 TransportErrorCode::NO_CID_AVAILABLE_FOR_PATH => { -1178 Some(quic::TransportError::NoCidsAvailableForPath) -1179 } -1180 _ => None, -1181 }; -1182 let code = match transport_error { -1183 None => Some(code.into()), -1184 Some(_) => None, -1185 }; -1186 (transport_error, code) -1187} -1188 -1189#[cfg(feature = "qlog")] -1190fn fmt_tuple_id(path_id: u64) -> String { -1191 format!("p{path_id}") -1192} -1193 -1194#[cfg(feature = "qlog")] -1195impl TransportParameters { -1196 fn to_qlog(self, initiator: TransportInitiator) -> ParametersSet { -1197 ParametersSet { -1198 initiator: Some(initiator), -1199 resumption_allowed: None, -1200 early_data_enabled: None, -1201 tls_cipher: None, -1202 original_destination_connection_id: self -1203 .original_dst_cid -1204 .as_ref() -1205 .map(ToString::to_string), -1206 initial_source_connection_id: self.initial_src_cid.as_ref().map(ToString::to_string), -1207 retry_source_connection_id: self.retry_src_cid.as_ref().map(ToString::to_string), -1208 stateless_reset_token: self.stateless_reset_token.as_ref().map(ToString::to_string), -1209 disable_active_migration: Some(self.disable_active_migration), -1210 max_idle_timeout: Some(self.max_idle_timeout.into()), -1211 max_udp_payload_size: Some(self.max_udp_payload_size.into()), -1212 ack_delay_exponent: Some(self.ack_delay_exponent.into()), -1213 max_ack_delay: Some(self.max_ack_delay.into()), -1214 active_connection_id_limit: Some(self.active_connection_id_limit.into()), -1215 initial_max_data: Some(self.initial_max_data.into()), -1216 initial_max_stream_data_bidi_local: Some( -1217 self.initial_max_stream_data_bidi_local.into(), -1218 ), -1219 initial_max_stream_data_bidi_remote: Some( -1220 self.initial_max_stream_data_bidi_remote.into(), +1176 TransportErrorCode::PATH_RESOURCE_LIMIT_REACHED => { +1177 Some(quic::TransportError::PathResourceLimitReached) +1178 } +1179 TransportErrorCode::PATH_UNSTABLE_OR_POOR => Some(quic::TransportError::PathUnstableOrPoor), +1180 TransportErrorCode::NO_CID_AVAILABLE_FOR_PATH => { +1181 Some(quic::TransportError::NoCidsAvailableForPath) +1182 } +1183 _ => None, +1184 }; +1185 let code = match transport_error { +1186 None => Some(code.into()), +1187 Some(_) => None, +1188 }; +1189 (transport_error, code) +1190} +1191 +1192#[cfg(feature = "qlog")] +1193fn fmt_tuple_id(path_id: u64) -> String { +1194 format!("p{path_id}") +1195} +1196 +1197#[cfg(feature = "qlog")] +1198impl TransportParameters { +1199 fn to_qlog(self, initiator: TransportInitiator) -> ParametersSet { +1200 ParametersSet { +1201 initiator: Some(initiator), +1202 resumption_allowed: None, +1203 early_data_enabled: None, +1204 tls_cipher: None, +1205 original_destination_connection_id: self +1206 .original_dst_cid +1207 .as_ref() +1208 .map(ToString::to_string), +1209 initial_source_connection_id: self.initial_src_cid.as_ref().map(ToString::to_string), +1210 retry_source_connection_id: self.retry_src_cid.as_ref().map(ToString::to_string), +1211 stateless_reset_token: self.stateless_reset_token.as_ref().map(ToString::to_string), +1212 disable_active_migration: Some(self.disable_active_migration), +1213 max_idle_timeout: Some(self.max_idle_timeout.into()), +1214 max_udp_payload_size: Some(self.max_udp_payload_size.into()), +1215 ack_delay_exponent: Some(self.ack_delay_exponent.into()), +1216 max_ack_delay: Some(self.max_ack_delay.into()), +1217 active_connection_id_limit: Some(self.active_connection_id_limit.into()), +1218 initial_max_data: Some(self.initial_max_data.into()), +1219 initial_max_stream_data_bidi_local: Some( +1220 self.initial_max_stream_data_bidi_local.into(), 1221 ), -1222 initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()), -1223 initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()), -1224 initial_max_streams_uni: Some(self.initial_max_streams_uni.into()), -1225 preferred_address: self.preferred_address.as_ref().map(Into::into), -1226 min_ack_delay: self.min_ack_delay.map(Into::into), -1227 address_discovery: self.address_discovery_role.to_qlog(), -1228 initial_max_path_id: self.initial_max_path_id.map(|p| p.as_u32() as u64), -1229 max_remote_nat_traversal_addresses: self -1230 .max_remote_nat_traversal_addresses -1231 .map(|v| u64::from(v.get())), -1232 max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into), -1233 grease_quic_bit: Some(self.grease_quic_bit), -1234 unknown_parameters: Default::default(), -1235 } -1236 } -1237 -1238 fn to_qlog_restored(self) -> ParametersRestored { -1239 ParametersRestored { -1240 disable_active_migration: Some(self.disable_active_migration), -1241 max_idle_timeout: Some(self.max_idle_timeout.into()), -1242 max_udp_payload_size: Some(self.max_udp_payload_size.into()), -1243 active_connection_id_limit: Some(self.active_connection_id_limit.into()), -1244 initial_max_data: Some(self.initial_max_data.into()), -1245 initial_max_stream_data_bidi_local: Some( -1246 self.initial_max_stream_data_bidi_local.into(), -1247 ), -1248 initial_max_stream_data_bidi_remote: Some( -1249 self.initial_max_stream_data_bidi_remote.into(), +1222 initial_max_stream_data_bidi_remote: Some( +1223 self.initial_max_stream_data_bidi_remote.into(), +1224 ), +1225 initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()), +1226 initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()), +1227 initial_max_streams_uni: Some(self.initial_max_streams_uni.into()), +1228 preferred_address: self.preferred_address.as_ref().map(Into::into), +1229 min_ack_delay: self.min_ack_delay.map(Into::into), +1230 address_discovery: self.address_discovery_role.to_qlog(), +1231 initial_max_path_id: self.initial_max_path_id.map(|p| p.as_u32() as u64), +1232 max_remote_nat_traversal_addresses: self +1233 .max_remote_nat_traversal_addresses +1234 .map(|v| u64::from(v.get())), +1235 max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into), +1236 grease_quic_bit: Some(self.grease_quic_bit), +1237 unknown_parameters: Default::default(), +1238 } +1239 } +1240 +1241 fn to_qlog_restored(self) -> ParametersRestored { +1242 ParametersRestored { +1243 disable_active_migration: Some(self.disable_active_migration), +1244 max_idle_timeout: Some(self.max_idle_timeout.into()), +1245 max_udp_payload_size: Some(self.max_udp_payload_size.into()), +1246 active_connection_id_limit: Some(self.active_connection_id_limit.into()), +1247 initial_max_data: Some(self.initial_max_data.into()), +1248 initial_max_stream_data_bidi_local: Some( +1249 self.initial_max_stream_data_bidi_local.into(), 1250 ), -1251 initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()), -1252 initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()), -1253 initial_max_streams_uni: Some(self.initial_max_streams_uni.into()), -1254 max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into), -1255 grease_quic_bit: Some(self.grease_quic_bit), -1256 } -1257 } -1258} -1259 -1260#[cfg(feature = "qlog")] -1261impl From<&crate::transport_parameters::PreferredAddress> for PreferredAddress { -1262 fn from(value: &crate::transport_parameters::PreferredAddress) -> Self { -1263 let port_v4 = value.address_v4.map(|addr| addr.port()).unwrap_or_default(); -1264 let port_v6 = value.address_v6.map(|addr| addr.port()).unwrap_or_default(); -1265 let ip_v4 = value -1266 .address_v4 -1267 .map(|addr| addr.ip().to_string()) -1268 .unwrap_or_default(); -1269 let ip_v6 = value -1270 .address_v6 -1271 .map(|addr| addr.ip().to_string()) -1272 .unwrap_or_default(); -1273 let connection_id = value.connection_id.to_string(); -1274 let stateless_reset_token = value.stateless_reset_token.to_string(); -1275 -1276 Self { -1277 ip_v4, -1278 ip_v6, -1279 port_v4, -1280 port_v6, -1281 connection_id, -1282 stateless_reset_token, -1283 } -1284 } -1285} -1286 -1287#[cfg(feature = "qlog")] -1288impl crate::address_discovery::Role { -1289 fn to_qlog(self) -> Option<AddressDiscoveryRole> { -1290 match self { -1291 Self::SendOnly => Some(AddressDiscoveryRole::SendOnly), -1292 Self::ReceiveOnly => Some(AddressDiscoveryRole::ReceiveOnly), -1293 Self::Both => Some(AddressDiscoveryRole::Both), -1294 Self::Disabled => None, -1295 } -1296 } -1297}

\ No newline at end of file +1251 initial_max_stream_data_bidi_remote: Some( +1252 self.initial_max_stream_data_bidi_remote.into(), +1253 ), +1254 initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni.into()), +1255 initial_max_streams_bidi: Some(self.initial_max_streams_bidi.into()), +1256 initial_max_streams_uni: Some(self.initial_max_streams_uni.into()), +1257 max_datagram_frame_size: self.max_datagram_frame_size.map(Into::into), +1258 grease_quic_bit: Some(self.grease_quic_bit), +1259 } +1260 } +1261} +1262 +1263#[cfg(feature = "qlog")] +1264impl From<&crate::transport_parameters::PreferredAddress> for PreferredAddress { +1265 fn from(value: &crate::transport_parameters::PreferredAddress) -> Self { +1266 let port_v4 = value.address_v4.map(|addr| addr.port()).unwrap_or_default(); +1267 let port_v6 = value.address_v6.map(|addr| addr.port()).unwrap_or_default(); +1268 let ip_v4 = value +1269 .address_v4 +1270 .map(|addr| addr.ip().to_string()) +1271 .unwrap_or_default(); +1272 let ip_v6 = value +1273 .address_v6 +1274 .map(|addr| addr.ip().to_string()) +1275 .unwrap_or_default(); +1276 let connection_id = value.connection_id.to_string(); +1277 let stateless_reset_token = value.stateless_reset_token.to_string(); +1278 +1279 Self { +1280 ip_v4, +1281 ip_v6, +1282 port_v4, +1283 port_v6, +1284 connection_id, +1285 stateless_reset_token, +1286 } +1287 } +1288} +1289 +1290#[cfg(feature = "qlog")] +1291impl crate::address_discovery::Role { +1292 fn to_qlog(self) -> Option<AddressDiscoveryRole> { +1293 match (self.send, self.receive) { +1294 (false, false) => None, +1295 (true, false) => Some(AddressDiscoveryRole::SendOnly), +1296 (false, true) => Some(AddressDiscoveryRole::ReceiveOnly), +1297 (true, true) => Some(AddressDiscoveryRole::Both), +1298 } +1299 } +1300}

\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/send_buffer.rs.html b/pr/451/docs/src/noq_proto/connection/send_buffer.rs.html index c3b5ca93e..6d73e278d 100644 --- a/pr/451/docs/src/noq_proto/connection/send_buffer.rs.html +++ b/pr/451/docs/src/noq_proto/connection/send_buffer.rs.html @@ -585,9 +585,9 @@ 585mod proptests { 586 use super::*; 587 -588 use proptest::prelude::*; -589 use test_strategy::{Arbitrary, proptest}; -590 use crate::tests::subscribe; +588 use crate::tests::subscribe; +589 use proptest::prelude::*; +590 use test_strategy::{Arbitrary, proptest}; 591 use tracing::trace; 592 593 #[derive(Debug, Clone, Arbitrary)] diff --git a/pr/451/docs/src/noq_proto/connection/spaces.rs.html b/pr/451/docs/src/noq_proto/connection/spaces.rs.html index 3f3a3fa55..f0b4c6064 100644 --- a/pr/451/docs/src/noq_proto/connection/spaces.rs.html +++ b/pr/451/docs/src/noq_proto/connection/spaces.rs.html @@ -10,7 +10,7 @@ 10use sorted_index_buffer::SortedIndexBuffer; 11use tracing::trace; 12 -13use super::PathId; +13use super::{PathId, paths::PathResponses, paths::PathRetransmits}; 14use crate::{ 15 Dir, Duration, FourTuple, Instant, StreamId, TransportError, TransportErrorCode, VarInt, 16 connection::StreamsState, @@ -103,7 +103,7 @@ 103 if request_immediate_ack { 104 // The probe should be ACKed without delay (should only be used in the Data space and 105 // when the peer supports the acknowledgement frequency extension) -106 self.for_path(path_id).immediate_ack_pending = true; +106 self.for_path(path_id).pending_immediate_ack = true; 107 } 108 109 // We prefer to send new data to make most efficient use of bandwidth. @@ -129,8 +129,8 @@ 129 // Nothing new to send and nothing to retransmit, so fall back on a ping. This should only 130 // happen in rare cases during the handshake when the server becomes blocked by 131 // anti-amplification. -132 if !self.for_path(path_id).immediate_ack_pending { -133 self.for_path(path_id).ping_pending = true; +132 if !self.for_path(path_id).pending_immediate_ack { +133 self.for_path(path_id).pending_ping = true; 134 } 135 } 136 @@ -146,1450 +146,1490 @@ 146 .number_spaces 147 .values() 148 .any(|pns| pns.pending_acks.can_send()); -149 let space_specific = self -150 .number_spaces -151 .get(&path_id) -152 .is_some_and(|s| s.ping_pending || s.immediate_ack_pending); -153 let other = !self.pending.is_empty(streams); -154 SendableFrames { -155 acks, -156 close: false, -157 space_specific, -158 other, -159 } -160 } -161} -162 -163impl Index<SpaceId> for [PacketSpace; 3] { -164 type Output = PacketSpace; -165 fn index(&self, space: SpaceId) -> &PacketSpace { -166 &self.as_ref()[space as usize] -167 } -168} -169 -170impl IndexMut<SpaceId> for [PacketSpace; 3] { -171 fn index_mut(&mut self, space: SpaceId) -> &mut PacketSpace { -172 &mut self.as_mut()[space as usize] -173 } -174} -175 -176/// The three QUIC packet number space kinds -177/// -178/// Unlike [`SpaceId`], this always has exactly three variants — it represents the -179/// encryption level / space kind, not a specific packet number space identity. -180#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -181pub(crate) enum SpaceKind { -182 /// Initial packets (client and server). -183 Initial = 0, -184 /// Handshake packets. -185 Handshake = 1, -186 /// Data (1-RTT and 0-RTT) -187 Data = 2, -188} -189 -190impl SpaceKind { -191 /// Returns the encryption level for this space kind. -192 pub(crate) fn encryption_level(self) -> super::EncryptionLevel { -193 match self { -194 Self::Initial => super::EncryptionLevel::Initial, -195 Self::Handshake => super::EncryptionLevel::Handshake, -196 Self::Data => super::EncryptionLevel::OneRtt, -197 } -198 } -199} -200 -201impl Index<SpaceKind> for [PacketSpace; 3] { -202 type Output = PacketSpace; -203 fn index(&self, space: SpaceKind) -> &PacketSpace { -204 &self.as_ref()[space as usize] -205 } -206} -207 -208impl IndexMut<SpaceKind> for [PacketSpace; 3] { -209 fn index_mut(&mut self, space: SpaceKind) -> &mut PacketSpace { -210 &mut self.as_mut()[space as usize] -211 } -212} -213 -214/// The state of a single packet number space. -215/// -216/// In RFC9000 there are 3 packet number spaces: Initial, Handshake and Data. In QUIC -217/// Multipath there are multiple packet number spaces for Data, each identified by a -218/// [`PathId`]. -219/// -220/// This contains the state for a packet number space which is not specific to the 4-tuple -221/// this space is currently using. The 4-tuple specific state, like congestion controller, -222/// pacing, ECN, MTU etc, is stored in [`PathData`]. -223/// -224/// Note that the `Initial`, `Handshake` and `Data(PathId(0))` space all share the same -225/// [`PathData`]. -226/// -227/// You should access this via [`PacketSpace::for_path`]. -228/// -229/// [`PathData`]: super::paths::PathData -230pub(super) struct PacketNumberSpace { -231 /// Highest received packet number, if any -232 pub(super) largest_received_packet_number: Option<u64>, -233 /// The packet number of the next packet that will be sent, if any. In the Data space, the -234 /// packet number stored here is sometimes skipped by [`PacketNumberFilter`] logic. -235 pub(super) next_packet_number: u64, -236 /// The largest packet number the remote peer acknowledged in an ACK frame. -237 pub(super) largest_acked_packet_pn: Option<u64>, -238 pub(super) largest_acked_packet_send_time: Instant, -239 /// The highest-numbered ACK-eliciting packet we've sent -240 pub(super) largest_ack_eliciting_sent: u64, -241 /// Number of packets in `sent_packets` with numbers above `largest_ack_eliciting_sent` -242 pub(super) unacked_non_ack_eliciting_tail: u64, -243 /// Transmitted but not acked -244 // We use a BTreeMap here so we can efficiently query by range on ACK and for loss detection -245 pub(super) sent_packets: SortedIndexBuffer<SentPacket>, -246 /// Packets that were deemed lost -247 // Older packets are regularly removed in `Connection::drain_lost_packets`. -248 pub(super) lost_packets: SortedIndexBuffer<LostPacket>, -249 /// Number of explicit congestion notification codepoints seen on incoming packets -250 pub(super) ecn_counters: frame::EcnCounts, -251 /// Recent ECN counters sent by the peer in ACK frames -252 /// -253 /// Updated (and inspected) whenever we receive an ACK with a new highest acked packet -254 /// number. Stored per-space to simplify verification, which would otherwise have difficulty -255 /// distinguishing between ECN bleaching and counts having been updated by a near-simultaneous -256 /// ACK already processed in another space. -257 pub(super) ecn_feedback: frame::EcnCounts, -258 /// A PING frame needs to be sent on this path -259 pub(super) ping_pending: bool, -260 /// An IMMEDIATE_ACK (draft-ietf-quic-ack-frequency) frame needs to be sent on this path -261 pub(super) immediate_ack_pending: bool, -262 /// Packet deduplicator -263 pub(super) dedup: Dedup, -264 /// Packet numbers to acknowledge -265 pub(super) pending_acks: PendingAcks, -266 -267 // -268 // Loss Detection -269 // -270 /// The time the most recently sent retransmittable packet was sent. -271 pub(super) time_of_last_ack_eliciting_packet: Option<Instant>, -272 /// Earliest time when we might declare a packet lost. -273 /// -274 /// The time at which the earliest sent packet in this space will be considered lost -275 /// based on exceeding the reordering window in time. Only set for packets numbered -276 /// prior to a packet that has been acknowledged. -277 pub(super) loss_time: Option<Instant>, -278 /// Number of tail loss probes to send -279 pub(super) loss_probes: u32, -280 -281 /// Packet numbers to skip, only used in the data package space. -282 pn_filter: Option<PacketNumberFilter>, -283} -284 -285impl PacketNumberSpace { -286 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { -287 let pn_filter = match space { -288 SpaceId::Initial | SpaceId::Handshake => None, -289 SpaceId::Data => Some(PacketNumberFilter::new(rng)), -290 }; -291 Self { -292 largest_received_packet_number: None, -293 next_packet_number: 0, -294 largest_acked_packet_pn: None, -295 largest_acked_packet_send_time: now, -296 largest_ack_eliciting_sent: 0, -297 unacked_non_ack_eliciting_tail: 0, -298 sent_packets: SortedIndexBuffer::new(), -299 lost_packets: SortedIndexBuffer::new(), -300 ecn_counters: frame::EcnCounts::ZERO, -301 ecn_feedback: frame::EcnCounts::ZERO, -302 ping_pending: false, -303 immediate_ack_pending: false, -304 dedup: Default::default(), -305 pending_acks: PendingAcks::new(), -306 time_of_last_ack_eliciting_packet: None, -307 loss_time: None, -308 loss_probes: 0, -309 pn_filter, -310 } -311 } -312 -313 #[cfg(test)] -314 fn new_deterministic(now: Instant, space: SpaceId) -> Self { -315 let pn_filter = match space { -316 SpaceId::Initial | SpaceId::Handshake => None, -317 SpaceId::Data => Some(PacketNumberFilter::disabled()), -318 }; -319 Self { -320 largest_received_packet_number: None, -321 next_packet_number: 0, -322 largest_acked_packet_pn: None, -323 largest_acked_packet_send_time: now, -324 largest_ack_eliciting_sent: 0, -325 unacked_non_ack_eliciting_tail: 0, -326 sent_packets: SortedIndexBuffer::new(), -327 lost_packets: SortedIndexBuffer::new(), -328 ecn_counters: frame::EcnCounts::ZERO, -329 ecn_feedback: frame::EcnCounts::ZERO, -330 ping_pending: false, -331 immediate_ack_pending: false, -332 dedup: Default::default(), -333 pending_acks: PendingAcks::new(), -334 time_of_last_ack_eliciting_packet: None, -335 loss_time: None, -336 loss_probes: 0, -337 pn_filter, -338 } -339 } -340 -341 /// Get the next outgoing packet number in this space -342 /// -343 /// In the Data space, the connection's [`PacketNumberFilter`] must be used rather than calling -344 /// this directly. -345 pub(super) fn get_tx_number(&mut self, rng: &mut (impl CryptoRng + ?Sized)) -> u64 { -346 // TODO: Handle packet number overflow gracefully -347 assert!(self.next_packet_number < 2u64.pow(62)); -348 let mut pn = self.next_packet_number; -349 self.next_packet_number += 1; -350 -351 // Skip this number if the filter says so, only enabled in the data space -352 if let Some(ref mut filter) = self.pn_filter -353 && filter.skip_pn(pn, rng) -354 { -355 pn = self.next_packet_number; -356 self.next_packet_number += 1; -357 } -358 pn -359 } -360 -361 pub(super) fn peek_tx_number(&mut self) -> u64 { -362 let pn = self.next_packet_number; -363 if let Some(ref filter) = self.pn_filter -364 && pn == filter.next_skipped_packet_number -365 { -366 return pn + 1; -367 } -368 pn -369 } -370 -371 /// Checks whether a skipped packet number was ACKed. -372 pub(super) fn check_ack(&self, range: std::ops::Range<u64>) -> Result<(), TransportError> { -373 if let Some(ref filter) = self.pn_filter -374 && filter -375 .prev_skipped_packet_number -376 .is_some_and(|pn| range.contains(&pn)) -377 { -378 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); -379 } -380 Ok(()) -381 } -382 -383 /// Verifies sanity of an ECN block and returns whether congestion was encountered. -384 pub(super) fn detect_ecn( -385 &mut self, -386 newly_acked: u64, -387 ecn: frame::EcnCounts, -388 ) -> Result<bool, &'static str> { -389 let ect0_increase = ecn -390 .ect0 -391 .checked_sub(self.ecn_feedback.ect0) -392 .ok_or("peer ECT(0) count regression")?; -393 let ect1_increase = ecn -394 .ect1 -395 .checked_sub(self.ecn_feedback.ect1) -396 .ok_or("peer ECT(1) count regression")?; -397 let ce_increase = ecn -398 .ce -399 .checked_sub(self.ecn_feedback.ce) -400 .ok_or("peer CE count regression")?; -401 let total_increase = ect0_increase + ect1_increase + ce_increase; -402 if total_increase < newly_acked { -403 return Err("ECN bleaching"); -404 } -405 if (ect0_increase + ce_increase) < newly_acked || ect1_increase != 0 { -406 return Err("ECN corruption"); -407 } -408 // If total_increase > newly_acked (which happens when ACKs are lost), this is required by -409 // the draft so that long-term drift does not occur. If =, then the only question is whether -410 // to count CE packets as CE or ECT0. Recording them as CE is more consistent and keeps the -411 // congestion check obvious. -412 self.ecn_feedback = ecn; -413 Ok(ce_increase != 0) -414 } -415 -416 /// Stop tracking sent packet `number`, and return what we knew about it -417 pub(super) fn take(&mut self, number: u64) -> Option<SentPacket> { -418 let packet = self.sent_packets.remove(number)?; -419 if !packet.ack_eliciting && number > self.largest_ack_eliciting_sent { -420 self.unacked_non_ack_eliciting_tail = -421 self.unacked_non_ack_eliciting_tail.checked_sub(1).unwrap(); -422 } -423 Some(packet) -424 } -425 -426 /// May return a packet that should be forgotten -427 pub(super) fn sent(&mut self, number: u64, packet: SentPacket) -> Option<SentPacket> { -428 // Retain state for at most this many non-ACK-eliciting packets sent after the most recently -429 // sent ACK-eliciting packet. We're never guaranteed to receive an ACK for those, and we -430 // can't judge them as lost without an ACK, so to limit memory in applications which receive -431 // packets but don't send ACK-eliciting data for long periods use we must eventually start -432 // forgetting about them, although it might also be reasonable to just kill the connection -433 // due to weird peer behavior. -434 const MAX_UNACKED_NON_ACK_ELICTING_TAIL: u64 = 1_000; -435 -436 let mut forgotten = None; -437 if packet.ack_eliciting { -438 self.unacked_non_ack_eliciting_tail = 0; -439 self.largest_ack_eliciting_sent = number; -440 } else if self.unacked_non_ack_eliciting_tail > MAX_UNACKED_NON_ACK_ELICTING_TAIL { -441 let oldest_after_ack_eliciting = self -442 .sent_packets -443 .keys_range(( -444 Bound::Excluded(self.largest_ack_eliciting_sent), -445 Bound::Unbounded, -446 )) -447 .next() -448 .unwrap(); -449 // Per https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types, -450 // non-ACK-eliciting packets must only contain PADDING, ACK, and CONNECTION_CLOSE -451 // frames, which require no special handling on ACK or loss beyond removal from -452 // in-flight counters if padded. -453 let packet = self -454 .sent_packets -455 .remove(oldest_after_ack_eliciting) -456 .unwrap(); -457 debug_assert!(!packet.ack_eliciting); -458 forgotten = Some(packet); -459 } else { -460 self.unacked_non_ack_eliciting_tail += 1; -461 } -462 -463 self.sent_packets.insert(number, packet); -464 forgotten -465 } -466 -467 /// Whether any congestion-controlled packets in this space are not yet acknowledged or lost -468 pub(super) fn has_in_flight(&self) -> bool { -469 // The number of non-congestion-controlled (i.e. size == 0) packets in flight at a time -470 // should be small, since otherwise congestion control wouldn't be effective. Therefore, -471 // this shouldn't need to visit many packets before finishing one way or another. -472 self.sent_packets.values().any(|x| x.size != 0) -473 } -474} -475 -476/// Represents one or more packets subject to retransmission -477#[derive(Debug, Clone)] -478pub(super) struct SentPacket { -479 /// [`PathData::generation`](super::PathData::generation) of the path on which this packet was sent -480 pub(super) path_generation: u64, -481 /// The time the packet was sent. -482 pub(super) time_sent: Instant, -483 /// The number of bytes sent in the packet, not including UDP or IP overhead, but including QUIC -484 /// framing overhead. Zero if this packet is not counted towards congestion control, i.e. not an -485 /// "in flight" packet. -486 pub(super) size: u16, -487 /// Whether an acknowledgement is expected directly in response to this packet. -488 pub(super) ack_eliciting: bool, -489 /// The largest packet number acknowledged by this packet -490 pub(super) largest_acked: FxHashMap<PathId, u64>, -491 /// Data which needs to be retransmitted in case the packet is lost. -492 /// The data is boxed to minimize `SentPacket` size for the typical case of -493 /// packets only containing ACKs and STREAM frames. -494 pub(super) retransmits: ThinRetransmits, -495 /// Metadata for stream frames in a packet -496 /// -497 /// The actual application data is stored with the stream state. -498 pub(super) stream_frames: frame::StreamMetaVec, -499} -500 -501/// Represents one or more packets that are deemed lost. -502#[derive(Debug)] -503pub(super) struct LostPacket { -504 /// The time the packet was sent. -505 pub(super) time_sent: Instant, -506} -507 -508/// Retransmittable data queue -509#[allow(unreachable_pub)] // fuzzing only -510#[derive(Debug, Default, Clone)] -511pub struct Retransmits { -512 pub(super) max_data: bool, -513 pub(super) max_stream_id: [bool; 2], -514 pub(super) streams_blocked: [bool; 2], -515 pub(super) reset_stream: Vec<(StreamId, VarInt)>, -516 pub(super) stop_sending: Vec<frame::StopSending>, -517 pub(super) max_stream_data: FxHashSet<StreamId>, -518 pub(super) crypto: VecDeque<frame::Crypto>, -519 pub(super) new_cids: PendingNewCids, -520 pub(super) retire_cids: Vec<(PathId, u64)>, -521 pub(super) ack_frequency: bool, -522 pub(super) handshake_done: bool, -523 pub(super) observed_addr: bool, -524 /// Whether we should inform the peer we will allow higher [`PathId`]s. -525 pub(super) max_path_id: bool, -526 /// Whether we should inform the peer that their max [`PathId`] is blocking our attempt to open -527 /// new paths. -528 // TODO(@divma): we need logic to prevent sending this more than once after being ack-d once -529 pub(super) paths_blocked: bool, -530 /// For each enqueued NEW_TOKEN frame, a copy of the path's remote address +149 let space_specific = self.number_spaces.get(&path_id).is_some_and(|s| { +150 s.pending_ping || s.pending_immediate_ack || !s.pending_path_responses.is_empty() +151 }); +152 let other = !self.pending.is_empty(streams); +153 SendableFrames { +154 acks, +155 close: false, +156 space_specific, +157 other, +158 } +159 } +160} +161 +162impl Index<SpaceId> for [PacketSpace; 3] { +163 type Output = PacketSpace; +164 fn index(&self, space: SpaceId) -> &PacketSpace { +165 &self.as_ref()[space as usize] +166 } +167} +168 +169impl IndexMut<SpaceId> for [PacketSpace; 3] { +170 fn index_mut(&mut self, space: SpaceId) -> &mut PacketSpace { +171 &mut self.as_mut()[space as usize] +172 } +173} +174 +175/// The three QUIC packet number space kinds +176/// +177/// Unlike [`SpaceId`], this always has exactly three variants — it represents the +178/// encryption level / space kind, not a specific packet number space identity. +179#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +180pub(crate) enum SpaceKind { +181 /// Initial packets (client and server). +182 Initial = 0, +183 /// Handshake packets. +184 Handshake = 1, +185 /// Data (1-RTT and 0-RTT) +186 Data = 2, +187} +188 +189impl SpaceKind { +190 /// Returns the encryption level for this space kind. +191 pub(crate) fn encryption_level(self) -> super::EncryptionLevel { +192 match self { +193 Self::Initial => super::EncryptionLevel::Initial, +194 Self::Handshake => super::EncryptionLevel::Handshake, +195 Self::Data => super::EncryptionLevel::OneRtt, +196 } +197 } +198} +199 +200impl Index<SpaceKind> for [PacketSpace; 3] { +201 type Output = PacketSpace; +202 fn index(&self, space: SpaceKind) -> &PacketSpace { +203 &self.as_ref()[space as usize] +204 } +205} +206 +207impl IndexMut<SpaceKind> for [PacketSpace; 3] { +208 fn index_mut(&mut self, space: SpaceKind) -> &mut PacketSpace { +209 &mut self.as_mut()[space as usize] +210 } +211} +212 +213/// The state of a single packet number space. +214/// +215/// In RFC9000 there are 3 packet number spaces: Initial, Handshake and Data. In QUIC +216/// Multipath there are multiple packet number spaces for Data, each identified by a +217/// [`PathId`]. +218/// +219/// This contains the state for a packet number space which is not specific to the 4-tuple +220/// this space is currently using. The 4-tuple specific state, like congestion controller, +221/// pacing, ECN, MTU etc, is stored in [`PathData`]. +222/// +223/// Note that the `Initial`, `Handshake` and `Data(PathId(0))` space all share the same +224/// [`PathData`]. +225/// +226/// You should access this via [`PacketSpace::for_path`]. +227/// +228/// [`PathData`]: super::paths::PathData +229pub(super) struct PacketNumberSpace { +230 /// Whether the path has already been considered opened from an application perspective. +231 /// +232 /// This means, for paths other than the original [`PathId::ZERO`], a first path +233 /// challenge has been responded to, regardless of the initial validation status of the +234 /// path. This state is irreversible, since it's not affected by the path being closed. +235 /// +236 /// Sending a PATH_CHALLENGE and receiving a valid response before the application is +237 /// informed of the path, is a way to ensure the path is usable before it is +238 /// reported. This is not required by the spec, and in the future might be changed for +239 /// simply requiring a first ack'd packet. +240 pub(super) open_status: OpenStatus, +241 +242 /// Highest received packet number, if any +243 pub(super) largest_received_packet_number: Option<u64>, +244 /// The packet number of the next packet that will be sent, if any. In the Data space, the +245 /// packet number stored here is sometimes skipped by [`PacketNumberFilter`] logic. +246 pub(super) next_packet_number: u64, +247 /// The largest packet number the remote peer acknowledged in an ACK frame. +248 pub(super) largest_acked_packet_pn: Option<u64>, +249 pub(super) largest_acked_packet_send_time: Instant, +250 /// The highest-numbered ACK-eliciting packet we've sent +251 pub(super) largest_ack_eliciting_sent: u64, +252 /// Number of packets in `sent_packets` with numbers above `largest_ack_eliciting_sent` +253 pub(super) unacked_non_ack_eliciting_tail: u64, +254 /// Transmitted but not acked +255 // We use a BTreeMap here so we can efficiently query by range on ACK and for loss detection +256 pub(super) sent_packets: SortedIndexBuffer<SentPacket>, +257 /// Packets that were deemed lost +258 // Older packets are regularly removed in `Connection::drain_lost_packets`. +259 pub(super) lost_packets: SortedIndexBuffer<LostPacket>, +260 /// Number of explicit congestion notification codepoints seen on incoming packets +261 pub(super) ecn_counters: frame::EcnCounts, +262 /// Recent ECN counters sent by the peer in ACK frames +263 /// +264 /// Updated (and inspected) whenever we receive an ACK with a new highest acked packet +265 /// number. Stored per-space to simplify verification, which would otherwise have difficulty +266 /// distinguishing between ECN bleaching and counts having been updated by a near-simultaneous +267 /// ACK already processed in another space. +268 pub(super) ecn_feedback: frame::EcnCounts, +269 /// A PING frame needs to be sent on this path. +270 pub(super) pending_ping: bool, +271 /// Packet numbers to acknowledge. +272 pub(super) pending_acks: PendingAcks, +273 /// An IMMEDIATE_ACK (draft-ietf-quic-ack-frequency) frame needs to be sent on this path. +274 pub(super) pending_immediate_ack: bool, +275 /// Responses to path challenges that need to be sent, on and off-path. +276 /// +277 /// Responses are only tied to the 4-tuple they were received on, not to a specific path +278 /// generation. Whether the response is on or off-path only depends on the path's +279 /// 4-tuple at sending time. +280 pub(super) pending_path_responses: PathResponses, +281 /// Packet deduplicator +282 pub(super) dedup: Dedup, +283 +284 // +285 // Loss Detection +286 // +287 /// The time the most recently sent retransmittable packet was sent. +288 pub(super) time_of_last_ack_eliciting_packet: Option<Instant>, +289 /// Earliest time when we might declare a packet lost. +290 /// +291 /// The time at which the earliest sent packet in this space will be considered lost +292 /// based on exceeding the reordering window in time. Only set for packets numbered +293 /// prior to a packet that has been acknowledged. +294 pub(super) loss_time: Option<Instant>, +295 /// Number of tail loss probes to send +296 pub(super) loss_probes: u32, +297 +298 /// Packet numbers to skip, only used in the data package space. +299 pn_filter: Option<PacketNumberFilter>, +300} +301 +302impl PacketNumberSpace { +303 pub(super) fn new(now: Instant, space: SpaceId, rng: &mut (impl CryptoRng + ?Sized)) -> Self { +304 let pn_filter = match space { +305 SpaceId::Initial | SpaceId::Handshake => None, +306 SpaceId::Data => Some(PacketNumberFilter::new(rng)), +307 }; +308 Self { +309 open_status: OpenStatus::default(), +310 largest_received_packet_number: None, +311 next_packet_number: 0, +312 largest_acked_packet_pn: None, +313 largest_acked_packet_send_time: now, +314 largest_ack_eliciting_sent: 0, +315 unacked_non_ack_eliciting_tail: 0, +316 sent_packets: SortedIndexBuffer::new(), +317 lost_packets: SortedIndexBuffer::new(), +318 ecn_counters: frame::EcnCounts::ZERO, +319 ecn_feedback: frame::EcnCounts::ZERO, +320 pending_ping: false, +321 pending_acks: PendingAcks::new(), +322 pending_immediate_ack: false, +323 pending_path_responses: PathResponses::default(), +324 dedup: Default::default(), +325 time_of_last_ack_eliciting_packet: None, +326 loss_time: None, +327 loss_probes: 0, +328 pn_filter, +329 } +330 } +331 +332 #[cfg(test)] +333 fn new_deterministic(now: Instant, space: SpaceId) -> Self { +334 let pn_filter = match space { +335 SpaceId::Initial | SpaceId::Handshake => None, +336 SpaceId::Data => Some(PacketNumberFilter::disabled()), +337 }; +338 Self { +339 open_status: OpenStatus::default(), +340 largest_received_packet_number: None, +341 next_packet_number: 0, +342 largest_acked_packet_pn: None, +343 largest_acked_packet_send_time: now, +344 largest_ack_eliciting_sent: 0, +345 unacked_non_ack_eliciting_tail: 0, +346 sent_packets: SortedIndexBuffer::new(), +347 lost_packets: SortedIndexBuffer::new(), +348 ecn_counters: frame::EcnCounts::ZERO, +349 ecn_feedback: frame::EcnCounts::ZERO, +350 pending_ping: false, +351 pending_acks: PendingAcks::new(), +352 pending_immediate_ack: false, +353 pending_path_responses: PathResponses::default(), +354 dedup: Default::default(), +355 time_of_last_ack_eliciting_packet: None, +356 loss_time: None, +357 loss_probes: 0, +358 pn_filter, +359 } +360 } +361 +362 /// Get the next outgoing packet number in this space +363 /// +364 /// In the Data space, the connection's [`PacketNumberFilter`] must be used rather than calling +365 /// this directly. +366 pub(super) fn get_tx_number(&mut self, rng: &mut (impl CryptoRng + ?Sized)) -> u64 { +367 // TODO: Handle packet number overflow gracefully +368 assert!(self.next_packet_number < 2u64.pow(62)); +369 let mut pn = self.next_packet_number; +370 self.next_packet_number += 1; +371 +372 // Skip this number if the filter says so, only enabled in the data space +373 if let Some(ref mut filter) = self.pn_filter +374 && filter.skip_pn(pn, rng) +375 { +376 pn = self.next_packet_number; +377 self.next_packet_number += 1; +378 } +379 pn +380 } +381 +382 pub(super) fn peek_tx_number(&mut self) -> u64 { +383 let pn = self.next_packet_number; +384 if let Some(ref filter) = self.pn_filter +385 && pn == filter.next_skipped_packet_number +386 { +387 return pn + 1; +388 } +389 pn +390 } +391 +392 /// Checks whether a skipped packet number was ACKed. +393 pub(super) fn check_ack(&self, range: std::ops::Range<u64>) -> Result<(), TransportError> { +394 if let Some(ref filter) = self.pn_filter +395 && filter +396 .prev_skipped_packet_number +397 .is_some_and(|pn| range.contains(&pn)) +398 { +399 return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); +400 } +401 Ok(()) +402 } +403 +404 /// Verifies sanity of an ECN block and returns whether congestion was encountered. +405 pub(super) fn detect_ecn( +406 &mut self, +407 newly_acked: u64, +408 ecn: frame::EcnCounts, +409 ) -> Result<bool, &'static str> { +410 let ect0_increase = ecn +411 .ect0 +412 .checked_sub(self.ecn_feedback.ect0) +413 .ok_or("peer ECT(0) count regression")?; +414 let ect1_increase = ecn +415 .ect1 +416 .checked_sub(self.ecn_feedback.ect1) +417 .ok_or("peer ECT(1) count regression")?; +418 let ce_increase = ecn +419 .ce +420 .checked_sub(self.ecn_feedback.ce) +421 .ok_or("peer CE count regression")?; +422 let total_increase = ect0_increase + ect1_increase + ce_increase; +423 if total_increase < newly_acked { +424 return Err("ECN bleaching"); +425 } +426 if (ect0_increase + ce_increase) < newly_acked || ect1_increase != 0 { +427 return Err("ECN corruption"); +428 } +429 // If total_increase > newly_acked (which happens when ACKs are lost), this is required by +430 // the draft so that long-term drift does not occur. If =, then the only question is whether +431 // to count CE packets as CE or ECT0. Recording them as CE is more consistent and keeps the +432 // congestion check obvious. +433 self.ecn_feedback = ecn; +434 Ok(ce_increase != 0) +435 } +436 +437 /// Stop tracking sent packet `number`, and return what we knew about it +438 pub(super) fn take(&mut self, number: u64) -> Option<SentPacket> { +439 let packet = self.sent_packets.remove(number)?; +440 if !packet.ack_eliciting && number > self.largest_ack_eliciting_sent { +441 self.unacked_non_ack_eliciting_tail = +442 self.unacked_non_ack_eliciting_tail.checked_sub(1).unwrap(); +443 } +444 Some(packet) +445 } +446 +447 /// May return a packet that should be forgotten +448 pub(super) fn sent(&mut self, number: u64, packet: SentPacket) -> Option<SentPacket> { +449 // Retain state for at most this many non-ACK-eliciting packets sent after the most recently +450 // sent ACK-eliciting packet. We're never guaranteed to receive an ACK for those, and we +451 // can't judge them as lost without an ACK, so to limit memory in applications which receive +452 // packets but don't send ACK-eliciting data for long periods use we must eventually start +453 // forgetting about them, although it might also be reasonable to just kill the connection +454 // due to weird peer behavior. +455 const MAX_UNACKED_NON_ACK_ELICTING_TAIL: u64 = 1_000; +456 +457 let mut forgotten = None; +458 if packet.ack_eliciting { +459 self.unacked_non_ack_eliciting_tail = 0; +460 self.largest_ack_eliciting_sent = number; +461 } else if self.unacked_non_ack_eliciting_tail > MAX_UNACKED_NON_ACK_ELICTING_TAIL { +462 let oldest_after_ack_eliciting = self +463 .sent_packets +464 .keys_range(( +465 Bound::Excluded(self.largest_ack_eliciting_sent), +466 Bound::Unbounded, +467 )) +468 .next() +469 .unwrap(); +470 // Per https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types, +471 // non-ACK-eliciting packets must only contain PADDING, ACK, and CONNECTION_CLOSE +472 // frames, which require no special handling on ACK or loss beyond removal from +473 // in-flight counters if padded. +474 let packet = self +475 .sent_packets +476 .remove(oldest_after_ack_eliciting) +477 .unwrap(); +478 debug_assert!(!packet.ack_eliciting); +479 forgotten = Some(packet); +480 } else { +481 self.unacked_non_ack_eliciting_tail += 1; +482 } +483 +484 self.sent_packets.insert(number, packet); +485 forgotten +486 } +487 +488 /// Whether any congestion-controlled packets in this space are not yet acknowledged or lost +489 pub(super) fn has_in_flight(&self) -> bool { +490 // The number of non-congestion-controlled (i.e. size == 0) packets in flight at a time +491 // should be small, since otherwise congestion control wouldn't be effective. Therefore, +492 // this shouldn't need to visit many packets before finishing one way or another. +493 self.sent_packets.values().any(|x| x.size != 0) +494 } +495} +496 +497#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +498pub(super) enum OpenStatus { +499 /// A first packet has not been sent using this [`PathId`]. +500 #[default] +501 Pending, +502 /// The first packet has been sent using this [`PathId`]. However, it is not yet deemed good +503 /// enough to be reported to the application. +504 Sent, +505 /// The application has been informed of this path. +506 Informed, +507} +508 +509/// Represents one or more packets subject to retransmission +510#[derive(Debug, Clone)] +511pub(super) struct SentPacket { +512 /// [`PathData::generation`](super::PathData::generation) of the path on which this packet was sent +513 pub(super) path_generation: u64, +514 /// The time the packet was sent. +515 pub(super) time_sent: Instant, +516 /// The number of bytes sent in the packet, not including UDP or IP overhead, but including QUIC +517 /// framing overhead. Zero if this packet is not counted towards congestion control, i.e. not an +518 /// "in flight" packet. +519 pub(super) size: u16, +520 /// Whether an acknowledgement is expected directly in response to this packet. +521 pub(super) ack_eliciting: bool, +522 /// The largest packet number acknowledged by this packet +523 pub(super) largest_acked: FxHashMap<PathId, u64>, +524 /// Data which needs to be retransmitted in case the packet is lost. +525 /// +526 /// These might be retransmitted over any available path in the same [`SpaceKind`]. +527 pub(super) retransmits: ThinRetransmits, +528 /// Retransmittable data specific to a path generation. +529 pub(super) path_retransmits: PathRetransmits, +530 /// Metadata for stream frames in a packet 531 /// -532 /// There are 2 reasons this is unusual: -533 /// -534 /// - If the path changes, NEW_TOKEN frames bound for the old path are not retransmitted on the -535 /// new path. That is why this field stores the remote address: so that ones for old paths -536 /// can be filtered out. -537 /// - If a token is lost, a new randomly generated token is re-transmitted, rather than the -538 /// original. This is so that if both transmissions are received, the client won't risk -539 /// sending the same token twice. That is why this field does _not_ store any actual token. -540 /// -541 /// It is true that a QUIC endpoint will only want to effectively have NEW_TOKEN frames -542 /// enqueued for its current path at a given point in time. Based on that, we could conceivably -543 /// change this from a vector to an `Option<(FourTuple, usize)>` or just a `usize` or -544 /// something. However, due to the architecture of noq, it is considerably simpler to not do -545 /// that; consider what such a change would mean for implementing `BitOrAssign` on Self. -546 pub(super) new_tokens: Vec<FourTuple>, -547 /// Paths which need to be abandoned -548 pub(super) path_abandon: BTreeMap<PathId, TransportErrorCode>, -549 /// If a [`frame::PathStatusAvailable`] and [`frame::PathStatusBackup`] need to be sent for a path -550 pub(super) path_status: BTreeSet<PathId>, -551 /// If a PATH_CIDS_BLOCKED frame needs to be sent for a path -552 pub(super) path_cids_blocked: BTreeSet<PathId>, -553 -554 // Nat traversal data -555 /// Addresses to report in `ADD_ADDRESS` frames -556 pub(super) add_address: BTreeSet<AddAddress>, -557 /// Address IDs to remove in `REMOVE_ADDRESS` frames -558 pub(super) remove_address: BTreeSet<RemoveAddress>, -559 /// Round and local addresses to advertise in `REACH_OUT` frames -560 pub(super) reach_out: PendingReachOutFrames, -561} -562 -563impl Retransmits { -564 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { -565 let Self { -566 max_data, -567 max_stream_id, -568 streams_blocked, -569 reset_stream, -570 stop_sending, -571 max_stream_data, -572 crypto, -573 new_cids, -574 retire_cids, -575 ack_frequency, -576 handshake_done, -577 observed_addr, -578 max_path_id, -579 paths_blocked, -580 new_tokens, -581 path_abandon, -582 path_status, -583 path_cids_blocked, -584 add_address, -585 remove_address, -586 reach_out, -587 } = &self; -588 !max_data -589 && !max_stream_id.iter().any(|x| *x) -590 && !streams_blocked.iter().any(|x| *x) -591 && reset_stream.is_empty() -592 && stop_sending.is_empty() -593 && max_stream_data -594 .iter() -595 .all(|&id| !streams.can_send_flow_control(id)) -596 && crypto.is_empty() -597 && new_cids.is_empty() -598 && retire_cids.is_empty() -599 && !ack_frequency -600 && !handshake_done -601 && !observed_addr -602 && !max_path_id -603 && !paths_blocked -604 && new_tokens.is_empty() -605 && path_abandon.is_empty() -606 && path_status.is_empty() -607 && path_cids_blocked.is_empty() -608 && add_address.is_empty() -609 && remove_address.is_empty() -610 && reach_out.is_empty() -611 } -612} -613 -614impl ::std::ops::BitOrAssign for Retransmits { -615 fn bitor_assign(&mut self, rhs: Self) { -616 let Self { -617 max_data, -618 max_stream_id, -619 streams_blocked, -620 reset_stream, -621 stop_sending, -622 max_stream_data, -623 crypto, -624 new_cids, -625 retire_cids, -626 ack_frequency, -627 handshake_done, -628 observed_addr, -629 max_path_id, -630 paths_blocked, -631 new_tokens, -632 mut path_abandon, -633 mut path_status, -634 mut path_cids_blocked, -635 add_address, -636 remove_address, -637 mut reach_out, -638 } = rhs; -639 -640 // We reduce in-stream head-of-line blocking by queueing retransmits before other data for -641 // STREAM and CRYPTO frames. -642 self.max_data |= max_data; -643 for dir in Dir::iter() { -644 self.max_stream_id[dir as usize] |= max_stream_id[dir as usize]; -645 self.streams_blocked[dir as usize] |= streams_blocked[dir as usize]; -646 } -647 self.reset_stream.extend_from_slice(&reset_stream); -648 self.stop_sending.extend_from_slice(&stop_sending); -649 self.max_stream_data.extend(&max_stream_data); -650 for crypto in crypto.into_iter().rev() { -651 self.crypto.push_front(crypto); -652 } -653 self.new_cids.extend(&new_cids); -654 self.retire_cids.extend(retire_cids); -655 self.ack_frequency |= ack_frequency; -656 self.handshake_done |= handshake_done; -657 self.observed_addr |= observed_addr; -658 self.max_path_id |= max_path_id; -659 self.paths_blocked |= paths_blocked; -660 self.new_tokens.extend_from_slice(&new_tokens); -661 self.path_abandon.append(&mut path_abandon); -662 self.path_status.append(&mut path_status); -663 self.path_cids_blocked.append(&mut path_cids_blocked); -664 self.add_address.extend(add_address.iter().copied()); -665 self.remove_address.extend(remove_address.iter().copied()); -666 self.reach_out.append(&mut reach_out); -667 } -668} -669 -670impl ::std::ops::BitOrAssign<ThinRetransmits> for Retransmits { -671 fn bitor_assign(&mut self, rhs: ThinRetransmits) { -672 let ThinRetransmits { retransmits } = rhs; -673 if let Some(retransmits) = retransmits { -674 self.bitor_assign(*retransmits) -675 } -676 } -677} -678 -679impl ::std::iter::FromIterator<Self> for Retransmits { -680 fn from_iter<T>(iter: T) -> Self -681 where -682 T: IntoIterator<Item = Self>, -683 { -684 let mut result = Self::default(); -685 for packet in iter { -686 result |= packet; +532 /// The actual application data is stored with the stream state. +533 pub(super) stream_frames: frame::StreamMetaVec, +534} +535 +536/// Represents one or more packets that are deemed lost. +537#[derive(Debug)] +538pub(super) struct LostPacket { +539 /// The time the packet was sent. +540 pub(super) time_sent: Instant, +541} +542 +543/// Retransmittable data queue. +544/// +545/// Data in this queue must be retransmittable over any path in the same [`SpaceKind`]. +546#[allow(unreachable_pub)] // fuzzing only +547#[derive(Debug, Default, Clone)] +548pub struct Retransmits { +549 pub(super) max_data: bool, +550 pub(super) max_stream_id: [bool; 2], +551 pub(super) streams_blocked: [bool; 2], +552 pub(super) reset_stream: Vec<(StreamId, VarInt)>, +553 pub(super) stop_sending: Vec<frame::StopSending>, +554 pub(super) max_stream_data: FxHashSet<StreamId>, +555 pub(super) crypto: VecDeque<frame::Crypto>, +556 pub(super) new_cids: PendingNewCids, +557 pub(super) retire_cids: Vec<(PathId, u64)>, +558 pub(super) ack_frequency: bool, +559 pub(super) handshake_done: bool, +560 /// Whether we should inform the peer we will allow higher [`PathId`]s. +561 pub(super) max_path_id: bool, +562 /// Whether we should inform the peer that their max [`PathId`] is blocking our attempt to open +563 /// new paths. +564 /// +565 /// Stores the remote_max_path_id at the time this was generated. +566 /// This frame is entirely informational, so when it's retransmitted, the remote_max_path_id is +567 /// intentionally not updated to preserve the fact that this was the state of the client at some +568 /// point. +569 pub(super) paths_blocked: Option<PathId>, +570 /// For each enqueued NEW_TOKEN frame, a copy of the path's remote address +571 /// +572 /// There are 2 reasons this is unusual: +573 /// +574 /// - If the path changes, NEW_TOKEN frames bound for the old path are not retransmitted on the +575 /// new path. That is why this field stores the remote address: so that ones for old paths +576 /// can be filtered out. +577 /// - If a token is lost, a new randomly generated token is re-transmitted, rather than the +578 /// original. This is so that if both transmissions are received, the client won't risk +579 /// sending the same token twice. That is why this field does _not_ store any actual token. +580 /// +581 /// It is true that a QUIC endpoint will only want to effectively have NEW_TOKEN frames +582 /// enqueued for its current path at a given point in time. Based on that, we could conceivably +583 /// change this from a vector to an `Option<(FourTuple, usize)>` or just a `usize` or +584 /// something. However, due to the architecture of noq, it is considerably simpler to not do +585 /// that; consider what such a change would mean for implementing `BitOrAssign` on Self. +586 pub(super) new_tokens: Vec<FourTuple>, +587 /// Paths which need to be abandoned +588 pub(super) path_abandon: BTreeMap<PathId, TransportErrorCode>, +589 /// If a [`frame::PathStatusAvailable`] and [`frame::PathStatusBackup`] need to be sent for a path +590 pub(super) path_status: BTreeSet<PathId>, +591 /// Whether a PATH_CIDS_BLOCKED frame needs to be sent for a path. +592 /// +593 /// Stores the next_seq number for the blocked path. This number can be "outdated" at the time of +594 /// sending when this is a retransmission. This is intentional, as this frame is purely +595 /// informational, and this would preserve this information. +596 pub(super) path_cids_blocked: BTreeMap<PathId, VarInt>, +597 +598 // Nat traversal data +599 /// Addresses to report in `ADD_ADDRESS` frames +600 pub(super) add_address: BTreeSet<AddAddress>, +601 /// Address IDs to remove in `REMOVE_ADDRESS` frames +602 pub(super) remove_address: BTreeSet<RemoveAddress>, +603 /// Round and local addresses to advertise in `REACH_OUT` frames +604 pub(super) reach_out: PendingReachOutFrames, +605} +606 +607impl Retransmits { +608 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { +609 let Self { +610 max_data, +611 max_stream_id, +612 streams_blocked, +613 reset_stream, +614 stop_sending, +615 max_stream_data, +616 crypto, +617 new_cids, +618 retire_cids, +619 ack_frequency, +620 handshake_done, +621 max_path_id, +622 paths_blocked, +623 new_tokens, +624 path_abandon, +625 path_status, +626 path_cids_blocked, +627 add_address, +628 remove_address, +629 reach_out, +630 } = &self; +631 !max_data +632 && !max_stream_id.iter().any(|x| *x) +633 && !streams_blocked.iter().any(|x| *x) +634 && reset_stream.is_empty() +635 && stop_sending.is_empty() +636 && max_stream_data +637 .iter() +638 .all(|&id| !streams.can_send_flow_control(id)) +639 && crypto.is_empty() +640 && new_cids.is_empty() +641 && retire_cids.is_empty() +642 && !ack_frequency +643 && !handshake_done +644 && !max_path_id +645 && paths_blocked.is_none() +646 && new_tokens.is_empty() +647 && path_abandon.is_empty() +648 && path_status.is_empty() +649 && path_cids_blocked.is_empty() +650 && add_address.is_empty() +651 && remove_address.is_empty() +652 && reach_out.is_empty() +653 } +654} +655 +656impl ::std::ops::BitOrAssign for Retransmits { +657 fn bitor_assign(&mut self, rhs: Self) { +658 let Self { +659 max_data, +660 max_stream_id, +661 streams_blocked, +662 reset_stream, +663 stop_sending, +664 max_stream_data, +665 crypto, +666 new_cids, +667 retire_cids, +668 ack_frequency, +669 handshake_done, +670 max_path_id, +671 paths_blocked, +672 new_tokens, +673 mut path_abandon, +674 mut path_status, +675 mut path_cids_blocked, +676 add_address, +677 remove_address, +678 mut reach_out, +679 } = rhs; +680 +681 // We reduce in-stream head-of-line blocking by queueing retransmits before other data for +682 // STREAM and CRYPTO frames. +683 self.max_data |= max_data; +684 for dir in Dir::iter() { +685 self.max_stream_id[dir as usize] |= max_stream_id[dir as usize]; +686 self.streams_blocked[dir as usize] |= streams_blocked[dir as usize]; 687 } -688 result -689 } -690} -691 -692/// The queue of new CIDs to be transmitted to the peer. -693/// -694/// This queue is always sorted, so that popping off the last item is always the lowest -695/// sequence number of the lowest path ID. Which is the CID you want to be issued next. -696/// -697/// This is but a newtype over a `Vec` to enforce the sorted invariant. -698#[derive(Clone, Debug, Default)] -699pub(super) struct PendingNewCids { -700 /// The CIDs themselves. -701 cids: Vec<IssuedCid>, -702 /// Whether [`Self::cids`] is sorted or not. -703 sorted: bool, -704} -705 -706impl PendingNewCids { -707 /// Inserts an issued CID into the queue. -708 pub(super) fn push(&mut self, cid: IssuedCid) { -709 self.cids.push(cid); -710 self.sorted = false; -711 } -712 -713 /// Pops the next issued CID to transmit from the queue. -714 pub(super) fn pop(&mut self) -> Option<IssuedCid> { -715 if !std::mem::replace(&mut self.sorted, true) { -716 self.cids -717 .sort_by_key(|cid| cmp::Reverse((cid.path_id, cid.sequence))); -718 } -719 self.cids.pop() -720 } -721 -722 pub(super) fn is_empty(&self) -> bool { -723 self.cids.is_empty() -724 } -725 -726 pub(super) fn extend(&mut self, other: &Self) { -727 self.cids.extend(&other.cids); -728 self.sorted = false; +688 self.reset_stream.extend_from_slice(&reset_stream); +689 self.stop_sending.extend_from_slice(&stop_sending); +690 self.max_stream_data.extend(&max_stream_data); +691 for crypto in crypto.into_iter().rev() { +692 self.crypto.push_front(crypto); +693 } +694 self.new_cids.extend(&new_cids); +695 self.retire_cids.extend(retire_cids); +696 self.ack_frequency |= ack_frequency; +697 self.handshake_done |= handshake_done; +698 self.max_path_id |= max_path_id; +699 self.paths_blocked = cmp::max(self.paths_blocked, paths_blocked); +700 self.new_tokens.extend_from_slice(&new_tokens); +701 self.path_abandon.append(&mut path_abandon); +702 self.path_status.append(&mut path_status); +703 self.path_cids_blocked.append(&mut path_cids_blocked); +704 self.add_address.extend(add_address.iter().copied()); +705 self.remove_address.extend(remove_address.iter().copied()); +706 self.reach_out.append(&mut reach_out); +707 } +708} +709 +710impl ::std::ops::BitOrAssign<ThinRetransmits> for Retransmits { +711 fn bitor_assign(&mut self, rhs: ThinRetransmits) { +712 let ThinRetransmits { retransmits } = rhs; +713 if let Some(retransmits) = retransmits { +714 self.bitor_assign(*retransmits) +715 } +716 } +717} +718 +719impl ::std::iter::FromIterator<Self> for Retransmits { +720 fn from_iter<T>(iter: T) -> Self +721 where +722 T: IntoIterator<Item = Self>, +723 { +724 let mut result = Self::default(); +725 for packet in iter { +726 result |= packet; +727 } +728 result 729 } -730 -731 pub(super) fn retain<F>(&mut self, f: F) -732 where -733 F: FnMut(&IssuedCid) -> bool, -734 { -735 self.cids.retain(f); -736 } -737} -738 -739/// Logically a Vec of REACH_OUT frames queued for transmit. -740/// -741/// This keeps track of the highest round ID and automatically drops frames with a lower -742/// round ID. -743/// -744/// The API is directly modelled on [`Vec`]. -745#[derive(Debug, Default, Clone)] -746pub(crate) struct PendingReachOutFrames { -747 /// The round ID of the REACH_OUT frames currently pending. -748 round: VarInt, -749 /// The REACH_OUT frames, always all having the same round ID. -750 frames: Vec<frame::ReachOut>, -751} +730} +731 +732/// The queue of new CIDs to be transmitted to the peer. +733/// +734/// This queue is always sorted, so that popping off the last item is always the lowest +735/// sequence number of the lowest path ID. Which is the CID you want to be issued next. +736/// +737/// This is but a newtype over a `Vec` to enforce the sorted invariant. +738#[derive(Clone, Debug, Default)] +739pub(super) struct PendingNewCids { +740 /// The CIDs themselves. +741 cids: Vec<IssuedCid>, +742 /// Whether [`Self::cids`] is sorted or not. +743 sorted: bool, +744} +745 +746impl PendingNewCids { +747 /// Inserts an issued CID into the queue. +748 pub(super) fn push(&mut self, cid: IssuedCid) { +749 self.cids.push(cid); +750 self.sorted = false; +751 } 752 -753impl PendingReachOutFrames { -754 pub(crate) fn len(&self) -> usize { -755 self.frames.len() -756 } -757 -758 pub(crate) fn is_empty(&self) -> bool { -759 self.frames.is_empty() +753 /// Pops the next issued CID to transmit from the queue. +754 pub(super) fn pop(&mut self) -> Option<IssuedCid> { +755 if !std::mem::replace(&mut self.sorted, true) { +756 self.cids +757 .sort_by_key(|cid| cmp::Reverse((cid.path_id, cid.sequence))); +758 } +759 self.cids.pop() 760 } 761 -762 pub(crate) fn push(&mut self, frame: frame::ReachOut) { -763 if frame.round < self.round { -764 return; -765 } else if frame.round > self.round { -766 self.round = frame.round; -767 self.frames.clear(); -768 } -769 self.frames.push(frame); -770 } -771 -772 pub(crate) fn append(&mut self, other: &mut Self) { -773 if other.round < self.round { -774 other.frames.clear(); -775 return; -776 } else if other.round > self.round { -777 self.round = other.round; -778 self.frames.clear(); -779 } -780 self.frames.append(&mut other.frames); -781 } -782 -783 pub(crate) fn pop_if( -784 &mut self, -785 predicate: impl FnOnce(&mut frame::ReachOut) -> bool, -786 ) -> Option<frame::ReachOut> { -787 self.frames.pop_if(predicate) -788 } -789} -790 -791impl FromIterator<frame::ReachOut> for PendingReachOutFrames { -792 fn from_iter<T: IntoIterator<Item = frame::ReachOut>>(iter: T) -> Self { -793 let iter = iter.into_iter(); -794 let size_hint = iter.size_hint(); -795 let mut this = Self { -796 round: Default::default(), -797 frames: Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0)), -798 }; -799 for frame in iter { -800 this.push(frame); -801 } -802 this -803 } -804} -805 -806/// A variant of `Retransmits` which only allocates storage when required -807#[derive(Debug, Default, Clone)] -808pub(super) struct ThinRetransmits { -809 retransmits: Option<Box<Retransmits>>, -810} +762 pub(super) fn is_empty(&self) -> bool { +763 self.cids.is_empty() +764 } +765 +766 pub(super) fn extend(&mut self, other: &Self) { +767 self.cids.extend(&other.cids); +768 self.sorted = false; +769 } +770 +771 pub(super) fn retain<F>(&mut self, f: F) +772 where +773 F: FnMut(&IssuedCid) -> bool, +774 { +775 self.cids.retain(f); +776 } +777} +778 +779/// Logically a Vec of REACH_OUT frames queued for transmit. +780/// +781/// This keeps track of the highest round ID and automatically drops frames with a lower +782/// round ID. +783/// +784/// The API is directly modelled on [`Vec`]. +785#[derive(Debug, Default, Clone)] +786pub(crate) struct PendingReachOutFrames { +787 /// The round ID of the REACH_OUT frames currently pending. +788 round: VarInt, +789 /// The REACH_OUT frames, always all having the same round ID. +790 frames: Vec<frame::ReachOut>, +791} +792 +793impl PendingReachOutFrames { +794 pub(crate) fn len(&self) -> usize { +795 self.frames.len() +796 } +797 +798 pub(crate) fn is_empty(&self) -> bool { +799 self.frames.is_empty() +800 } +801 +802 pub(crate) fn push(&mut self, frame: frame::ReachOut) { +803 if frame.round < self.round { +804 return; +805 } else if frame.round > self.round { +806 self.round = frame.round; +807 self.frames.clear(); +808 } +809 self.frames.push(frame); +810 } 811 -812impl ThinRetransmits { -813 /// Returns `true` if no retransmits are necessary -814 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { -815 match &self.retransmits { -816 Some(retransmits) => retransmits.is_empty(streams), -817 None => true, -818 } -819 } -820 -821 /// Returns a reference to the retransmits stored in this box -822 pub(super) fn get(&self) -> Option<&Retransmits> { -823 self.retransmits.as_deref() -824 } -825 -826 /// Returns a mutable reference to the retransmits stored in this box -827 pub(super) fn get_mut(&mut self) -> Option<&mut Retransmits> { -828 self.retransmits.as_deref_mut() -829 } +812 pub(crate) fn append(&mut self, other: &mut Self) { +813 if other.round < self.round { +814 other.frames.clear(); +815 return; +816 } else if other.round > self.round { +817 self.round = other.round; +818 self.frames.clear(); +819 } +820 self.frames.append(&mut other.frames); +821 } +822 +823 pub(crate) fn pop_if( +824 &mut self, +825 predicate: impl FnOnce(&mut frame::ReachOut) -> bool, +826 ) -> Option<frame::ReachOut> { +827 self.frames.pop_if(predicate) +828 } +829} 830 -831 /// Returns a mutable reference to the stored retransmits -832 /// -833 /// This function will allocate a backing storage if required. -834 pub(super) fn get_or_create(&mut self) -> &mut Retransmits { -835 if self.retransmits.is_none() { -836 self.retransmits = Some(Box::default()); -837 } -838 self.retransmits.as_deref_mut().unwrap() -839 } -840} -841 -842/// RFC4303-style sliding window packet number deduplicator. -843/// -844/// A contiguous bitfield, where each bit corresponds to a packet number and the rightmost bit is -845/// always set. A set bit represents a packet that has been successfully authenticated. Bits left of -846/// the window are assumed to be set. -847/// -848/// ```text -849/// ...xxxxxxxxx 1 0 -850/// ^ ^ ^ -851/// window highest next -852/// ``` -853#[derive(Debug, Default)] -854pub(super) struct Dedup { -855 window: Window, -856 /// Lowest packet number higher than all yet authenticated. -857 next: u64, -858} -859 -860/// Inner bitfield type. -861/// -862/// Because QUIC never reuses packet numbers, this only needs to be large enough to deal with -863/// packets that are reordered but still delivered in a timely manner. -864type Window = u128; +831impl FromIterator<frame::ReachOut> for PendingReachOutFrames { +832 fn from_iter<T: IntoIterator<Item = frame::ReachOut>>(iter: T) -> Self { +833 let iter = iter.into_iter(); +834 let size_hint = iter.size_hint(); +835 let mut this = Self { +836 round: Default::default(), +837 frames: Vec::with_capacity(size_hint.1.unwrap_or(size_hint.0)), +838 }; +839 for frame in iter { +840 this.push(frame); +841 } +842 this +843 } +844} +845 +846/// A variant of `Retransmits` which only allocates storage when required +847#[derive(Debug, Default, Clone)] +848pub(super) struct ThinRetransmits { +849 retransmits: Option<Box<Retransmits>>, +850} +851 +852impl ThinRetransmits { +853 /// Returns `true` if no retransmits are necessary +854 pub(super) fn is_empty(&self, streams: &StreamsState) -> bool { +855 match &self.retransmits { +856 Some(retransmits) => retransmits.is_empty(streams), +857 None => true, +858 } +859 } +860 +861 /// Returns a reference to the retransmits stored in this box +862 pub(super) fn get(&self) -> Option<&Retransmits> { +863 self.retransmits.as_deref() +864 } 865 -866/// Number of packets tracked by `Dedup`. -867const WINDOW_SIZE: u64 = 1 + mem::size_of::<Window>() as u64 * 8; -868 -869impl Dedup { -870 /// Construct an empty window positioned at the start. -871 #[cfg(test)] -872 pub(super) fn new() -> Self { -873 Self { window: 0, next: 0 } -874 } -875 -876 /// Highest packet number authenticated. -877 fn highest(&self) -> u64 { -878 self.next - 1 -879 } -880 -881 /// Record a newly authenticated packet number. -882 /// -883 /// Returns whether the packet might be a duplicate. -884 pub(super) fn insert(&mut self, packet: u64) -> bool { -885 if let Some(diff) = packet.checked_sub(self.next) { -886 // Right of window -887 self.window = ((self.window << 1) | 1) -888 .checked_shl(cmp::min(diff, u64::from(u32::MAX)) as u32) -889 .unwrap_or(0); -890 self.next = packet + 1; -891 false -892 } else if self.highest() - packet < WINDOW_SIZE { -893 // Within window -894 if let Some(bit) = (self.highest() - packet).checked_sub(1) { -895 // < highest -896 let mask = 1 << bit; -897 let duplicate = self.window & mask != 0; -898 self.window |= mask; -899 duplicate -900 } else { -901 // == highest -902 true -903 } -904 } else { -905 // Left of window -906 true -907 } -908 } -909 -910 /// Returns the packet number of the smallest packet missing between the provided interval -911 /// -912 /// If there are no missing packets, returns `None` -913 fn smallest_missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> Option<u64> { -914 debug_assert!(lower_bound <= upper_bound); -915 debug_assert!(upper_bound <= self.highest()); -916 const BITFIELD_SIZE: u64 = (mem::size_of::<Window>() * 8) as u64; -917 -918 // Since we already know the packets at the boundaries have been received, we only need to -919 // check those in between them (this removes the necessity of extra logic to deal with the -920 // highest packet, which is stored outside the bitfield) -921 let lower_bound = lower_bound + 1; -922 let upper_bound = upper_bound.saturating_sub(1); -923 -924 // Note: the offsets are counted from the right -925 // The highest packet is not included in the bitfield, so we subtract 1 to account for that -926 let start_offset = (self.highest() - upper_bound).max(1) - 1; -927 if start_offset >= BITFIELD_SIZE { -928 // The start offset is outside of the window. All packets outside of the window are -929 // considered to be received. -930 return None; -931 } -932 -933 let end_offset_exclusive = self.highest().saturating_sub(lower_bound); -934 -935 // The range is clamped at the edge of the window, because any earlier packets are -936 // considered to be received -937 let range_len = end_offset_exclusive -938 .saturating_sub(start_offset) -939 .min(BITFIELD_SIZE); -940 if range_len == 0 { -941 return None; -942 } -943 -944 // Ensure the shift is within bounds (we already know start_offset < BITFIELD_SIZE, -945 // because of the early return) -946 let mask = if range_len == BITFIELD_SIZE { -947 u128::MAX -948 } else { -949 ((1u128 << range_len) - 1) << start_offset -950 }; -951 let gaps = !self.window & mask; -952 -953 let smallest_missing_offset = 128 - gaps.leading_zeros() as u64; -954 let smallest_missing_packet = self.highest() - smallest_missing_offset; -955 -956 if smallest_missing_packet <= upper_bound { -957 Some(smallest_missing_packet) -958 } else { -959 None -960 } -961 } -962 -963 /// Returns true if there are any missing packets between the provided interval -964 /// -965 /// The provided packet numbers must have been received before calling this function -966 fn missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> bool { -967 self.smallest_missing_in_interval(lower_bound, upper_bound) -968 .is_some() -969 } -970} -971 -972/// Indicates which data is available for sending. -973/// -974/// This applies to a particular space ID that was queried and all refers to on-path data. -975#[derive(Clone, Copy, PartialEq, Eq, Debug)] -976pub(super) struct SendableFrames { -977 /// Whether there are ACK frames to send, these are not ack-eliciting. -978 pub(super) acks: bool, -979 /// Whether there is a CONNECTION_CLOSE to send, this is not ack-eliciting. -980 pub(super) close: bool, -981 /// Whether there are any frames that must be sent on this specific space. -982 /// -983 /// A space here in the sense of a QUIC Multipath packet number space: `Initial`, -984 /// `Handshake` and all `Data(PathId)` spaces. -985 /// -986 /// These are ack-eliciting. Some frames are scheduled per path, e.g. PING, -987 /// IMMEDIATE_ACK, PATH_CHALLENGE or PATH_RESPONSE. -988 pub(super) space_specific: bool, -989 /// Whether there are any other frames to send, these are ack-eliciting. -990 pub(super) other: bool, -991} +866 /// Returns a mutable reference to the retransmits stored in this box +867 pub(super) fn get_mut(&mut self) -> Option<&mut Retransmits> { +868 self.retransmits.as_deref_mut() +869 } +870 +871 /// Returns a mutable reference to the stored retransmits +872 /// +873 /// This function will allocate a backing storage if required. +874 pub(super) fn get_or_create(&mut self) -> &mut Retransmits { +875 if self.retransmits.is_none() { +876 self.retransmits = Some(Box::default()); +877 } +878 self.retransmits.as_deref_mut().unwrap() +879 } +880} +881 +882/// RFC4303-style sliding window packet number deduplicator. +883/// +884/// A contiguous bitfield, where each bit corresponds to a packet number and the rightmost bit is +885/// always set. A set bit represents a packet that has been successfully authenticated. Bits left of +886/// the window are assumed to be set. +887/// +888/// ```text +889/// ...xxxxxxxxx 1 0 +890/// ^ ^ ^ +891/// window highest next +892/// ``` +893#[derive(Debug, Default)] +894pub(super) struct Dedup { +895 window: Window, +896 /// Lowest packet number higher than all yet authenticated. +897 next: u64, +898} +899 +900/// Inner bitfield type. +901/// +902/// Because QUIC never reuses packet numbers, this only needs to be large enough to deal with +903/// packets that are reordered but still delivered in a timely manner. +904type Window = u128; +905 +906/// Number of packets tracked by `Dedup`. +907const WINDOW_SIZE: u64 = 1 + mem::size_of::<Window>() as u64 * 8; +908 +909impl Dedup { +910 /// Construct an empty window positioned at the start. +911 #[cfg(test)] +912 pub(super) fn new() -> Self { +913 Self { window: 0, next: 0 } +914 } +915 +916 /// Highest packet number authenticated. +917 fn highest(&self) -> u64 { +918 self.next - 1 +919 } +920 +921 /// Record a newly authenticated packet number. +922 /// +923 /// Returns whether the packet might be a duplicate. +924 pub(super) fn insert(&mut self, packet: u64) -> bool { +925 if let Some(diff) = packet.checked_sub(self.next) { +926 // Right of window +927 self.window = ((self.window << 1) | 1) +928 .checked_shl(cmp::min(diff, u64::from(u32::MAX)) as u32) +929 .unwrap_or(0); +930 self.next = packet + 1; +931 false +932 } else if self.highest() - packet < WINDOW_SIZE { +933 // Within window +934 if let Some(bit) = (self.highest() - packet).checked_sub(1) { +935 // < highest +936 let mask = 1 << bit; +937 let duplicate = self.window & mask != 0; +938 self.window |= mask; +939 duplicate +940 } else { +941 // == highest +942 true +943 } +944 } else { +945 // Left of window +946 true +947 } +948 } +949 +950 /// Returns the packet number of the smallest packet missing between the provided interval +951 /// +952 /// If there are no missing packets, returns `None` +953 fn smallest_missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> Option<u64> { +954 debug_assert!(lower_bound <= upper_bound); +955 debug_assert!(upper_bound <= self.highest()); +956 const BITFIELD_SIZE: u64 = (mem::size_of::<Window>() * 8) as u64; +957 +958 // Since we already know the packets at the boundaries have been received, we only need to +959 // check those in between them (this removes the necessity of extra logic to deal with the +960 // highest packet, which is stored outside the bitfield) +961 let lower_bound = lower_bound + 1; +962 let upper_bound = upper_bound.saturating_sub(1); +963 +964 // Note: the offsets are counted from the right +965 // The highest packet is not included in the bitfield, so we subtract 1 to account for that +966 let start_offset = (self.highest() - upper_bound).max(1) - 1; +967 if start_offset >= BITFIELD_SIZE { +968 // The start offset is outside of the window. All packets outside of the window are +969 // considered to be received. +970 return None; +971 } +972 +973 let end_offset_exclusive = self.highest().saturating_sub(lower_bound); +974 +975 // The range is clamped at the edge of the window, because any earlier packets are +976 // considered to be received +977 let range_len = end_offset_exclusive +978 .saturating_sub(start_offset) +979 .min(BITFIELD_SIZE); +980 if range_len == 0 { +981 return None; +982 } +983 +984 // Ensure the shift is within bounds (we already know start_offset < BITFIELD_SIZE, +985 // because of the early return) +986 let mask = if range_len == BITFIELD_SIZE { +987 u128::MAX +988 } else { +989 ((1u128 << range_len) - 1) << start_offset +990 }; +991 let gaps = !self.window & mask; 992 -993impl SendableFrames { -994 /// Returns that no data is available for sending -995 pub(super) fn empty() -> Self { -996 Self { -997 acks: false, -998 close: false, -999 space_specific: false, -1000 other: false, -1001 } -1002 } -1003 -1004 /// Whether an ack-eliciting packet will be sent. -1005 pub(super) fn is_ack_eliciting(&self) -> bool { -1006 let Self { -1007 acks: _, -1008 close, -1009 space_specific, -1010 other, -1011 } = *self; -1012 if close { -1013 // No ack-eliciting frames are included with a CONNECTION_CLOSE, only acks. -1014 return false; -1015 } -1016 space_specific || other -1017 } -1018 -1019 /// Whether no data is sendable. -1020 pub(super) fn is_empty(&self) -> bool { -1021 let Self { -1022 acks, -1023 close, -1024 space_specific, -1025 other, -1026 } = *self; -1027 !acks && !close && !space_specific && !other -1028 } -1029} -1030 -1031impl ::std::ops::BitOrAssign for SendableFrames { -1032 fn bitor_assign(&mut self, rhs: Self) { -1033 let Self { -1034 acks, -1035 close, -1036 space_specific, -1037 other, -1038 } = rhs; -1039 -1040 self.acks |= acks; -1041 self.close |= close; -1042 self.space_specific |= space_specific; -1043 self.other |= other; -1044 } -1045} -1046 -1047#[derive(Debug)] -1048pub(super) struct PendingAcks { -1049 /// Whether we should send an ACK immediately, even if that means sending an ACK-only packet -1050 /// -1051 /// When `immediate_ack_required` is false, the normal behavior is to send ACK frames only when -1052 /// there is other data to send, or when the `MaxAckDelay` timer expires. -1053 immediate_ack_required: bool, -1054 /// The number of ack-eliciting packets received since the last ACK frame was sent -1055 /// -1056 /// Once the count _exceeds_ `ack_eliciting_threshold`, an immediate ACK is required -1057 ack_eliciting_since_last_ack_sent: u64, -1058 non_ack_eliciting_since_last_ack_sent: u64, -1059 ack_eliciting_threshold: u64, -1060 /// The reordering threshold, controlling how we respond to out-of-order ack-eliciting packets -1061 /// -1062 /// Different values enable different behavior: -1063 /// -1064 /// * `0`: no special action is taken -1065 /// * `1`: an ACK is immediately sent if it is out-of-order according to RFC 9000 -1066 /// * `>1`: an ACK is immediately sent if it is out-of-order according to the ACK frequency draft -1067 reordering_threshold: u64, -1068 /// The earliest ack-eliciting packet since the last ACK was sent, used to calculate the moment -1069 /// upon which `max_ack_delay` elapses -1070 earliest_ack_eliciting_since_last_ack_sent: Option<Instant>, -1071 /// Packet number ranges for which to still send acknowledgements. -1072 /// -1073 /// These are packet number ranges of ack-eliciting packets the peer has sent and which -1074 /// need to be acknowledged. Packet numbers are only removed from here once the peer has -1075 /// acknowledged the ACKs for them. -1076 ranges: ArrayRangeSet, -1077 /// The largest packet number received and the time it was received -1078 /// -1079 /// Used to calculate ACK delay in [`PendingAcks::ack_delay`]. -1080 largest_packet: Option<(u64, Instant)>, -1081 /// The ack-eliciting packet we have received with the largest packet number -1082 largest_ack_eliciting_packet: Option<u64>, -1083 /// The largest acknowledged packet number sent in an ACK frame -1084 largest_acked: Option<u64>, +993 let smallest_missing_offset = 128 - gaps.leading_zeros() as u64; +994 let smallest_missing_packet = self.highest() - smallest_missing_offset; +995 +996 if smallest_missing_packet <= upper_bound { +997 Some(smallest_missing_packet) +998 } else { +999 None +1000 } +1001 } +1002 +1003 /// Returns true if there are any missing packets between the provided interval +1004 /// +1005 /// The provided packet numbers must have been received before calling this function +1006 fn missing_in_interval(&self, lower_bound: u64, upper_bound: u64) -> bool { +1007 self.smallest_missing_in_interval(lower_bound, upper_bound) +1008 .is_some() +1009 } +1010} +1011 +1012/// Indicates which data is available for sending. +1013/// +1014/// This applies to a particular space ID that was queried and all refers to on-path data. +1015#[derive(Clone, Copy, PartialEq, Eq, Debug)] +1016pub(super) struct SendableFrames { +1017 /// Whether there are ACK frames to send, these are not ack-eliciting. +1018 pub(super) acks: bool, +1019 /// Whether there is a CONNECTION_CLOSE to send, this is not ack-eliciting. +1020 pub(super) close: bool, +1021 /// Whether there are any frames that must be sent on this specific space. +1022 /// +1023 /// A space here in the sense of a QUIC Multipath packet number space: `Initial`, +1024 /// `Handshake` and all `Data(PathId)` spaces. +1025 /// +1026 /// These are ack-eliciting. Some frames are scheduled per path, e.g. PING, +1027 /// IMMEDIATE_ACK, PATH_CHALLENGE or PATH_RESPONSE. +1028 pub(super) space_specific: bool, +1029 /// Whether there are any other frames to send, these are ack-eliciting. +1030 pub(super) other: bool, +1031} +1032 +1033impl SendableFrames { +1034 /// Returns that no data is available for sending +1035 pub(super) fn empty() -> Self { +1036 Self { +1037 acks: false, +1038 close: false, +1039 space_specific: false, +1040 other: false, +1041 } +1042 } +1043 +1044 /// Whether an ack-eliciting packet will be sent. +1045 pub(super) fn is_ack_eliciting(&self) -> bool { +1046 let Self { +1047 acks: _, +1048 close, +1049 space_specific, +1050 other, +1051 } = *self; +1052 if close { +1053 // No ack-eliciting frames are included with a CONNECTION_CLOSE, only acks. +1054 return false; +1055 } +1056 space_specific || other +1057 } +1058 +1059 /// Whether no data is sendable. +1060 pub(super) fn is_empty(&self) -> bool { +1061 let Self { +1062 acks, +1063 close, +1064 space_specific, +1065 other, +1066 } = *self; +1067 !acks && !close && !space_specific && !other +1068 } +1069} +1070 +1071impl ::std::ops::BitOrAssign for SendableFrames { +1072 fn bitor_assign(&mut self, rhs: Self) { +1073 let Self { +1074 acks, +1075 close, +1076 space_specific, +1077 other, +1078 } = rhs; +1079 +1080 self.acks |= acks; +1081 self.close |= close; +1082 self.space_specific |= space_specific; +1083 self.other |= other; +1084 } 1085} 1086 -1087impl PendingAcks { -1088 fn new() -> Self { -1089 Self { -1090 immediate_ack_required: false, -1091 ack_eliciting_since_last_ack_sent: 0, -1092 non_ack_eliciting_since_last_ack_sent: 0, -1093 ack_eliciting_threshold: 1, -1094 reordering_threshold: 1, -1095 earliest_ack_eliciting_since_last_ack_sent: None, -1096 ranges: Default::default(), -1097 largest_packet: Default::default(), -1098 largest_ack_eliciting_packet: Default::default(), -1099 largest_acked: Default::default(), -1100 } -1101 } -1102 -1103 pub(super) fn set_ack_frequency_params(&mut self, frame: &frame::AckFrequency) { -1104 self.ack_eliciting_threshold = frame.ack_eliciting_threshold.into_inner(); -1105 self.reordering_threshold = frame.reordering_threshold.into_inner(); -1106 } -1107 -1108 pub(super) fn set_immediate_ack_required(&mut self) { -1109 self.immediate_ack_required = true; -1110 } -1111 -1112 pub(super) fn on_max_ack_delay_timeout(&mut self) { -1113 self.immediate_ack_required = self.ack_eliciting_since_last_ack_sent > 0; -1114 } -1115 -1116 pub(super) fn max_ack_delay_timeout(&self, max_ack_delay: Duration) -> Option<Instant> { -1117 self.earliest_ack_eliciting_since_last_ack_sent -1118 .map(|earliest_unacked| earliest_unacked + max_ack_delay) -1119 } -1120 -1121 /// Whether any ACK frames SHOULD be sent -1122 /// -1123 /// This is used in the top-level [`Connection::space_can_send`], so determines if a -1124 /// packet will be built. It is often possible to construct new ACK ranges to send -1125 /// before this returns `true`. This results in more ACK frames being sent, and -1126 /// processing those at the receiver costs CPU for very little improvements. -1127 /// -1128 /// [`Connection::space_can_send`]: super::Connection::space_can_send -1129 pub(super) fn can_send(&self) -> bool { -1130 self.immediate_ack_required && !self.ranges.is_empty() -1131 } -1132 -1133 /// Returns the delay since the packet with the largest packet number was received -1134 pub(super) fn ack_delay(&self, now: Instant) -> Duration { -1135 self.largest_packet -1136 .map_or_else(Duration::default, |(_, received)| now - received) -1137 } -1138 -1139 /// Handle receipt of a new packet -1140 /// -1141 /// Returns true if the max ack delay timer should be armed -1142 pub(super) fn packet_received( -1143 &mut self, -1144 now: Instant, -1145 packet_number: u64, -1146 ack_eliciting: bool, -1147 dedup: &Dedup, -1148 ) -> bool { -1149 if !ack_eliciting { -1150 self.non_ack_eliciting_since_last_ack_sent += 1; -1151 return false; -1152 } -1153 -1154 let prev_largest_ack_eliciting = self.largest_ack_eliciting_packet.unwrap_or(0); +1087#[derive(Debug)] +1088pub(super) struct PendingAcks { +1089 /// Whether we should send an ACK immediately, even if that means sending an ACK-only packet +1090 /// +1091 /// When `immediate_ack_required` is false, the normal behavior is to send ACK frames only when +1092 /// there is other data to send, or when the `MaxAckDelay` timer expires. +1093 immediate_ack_required: bool, +1094 /// The number of ack-eliciting packets received since the last ACK frame was sent +1095 /// +1096 /// Once the count _exceeds_ `ack_eliciting_threshold`, an immediate ACK is required +1097 ack_eliciting_since_last_ack_sent: u64, +1098 non_ack_eliciting_since_last_ack_sent: u64, +1099 ack_eliciting_threshold: u64, +1100 /// The reordering threshold, controlling how we respond to out-of-order ack-eliciting packets +1101 /// +1102 /// Different values enable different behavior: +1103 /// +1104 /// * `0`: no special action is taken +1105 /// * `1`: an ACK is immediately sent if it is out-of-order according to RFC 9000 +1106 /// * `>1`: an ACK is immediately sent if it is out-of-order according to the ACK frequency draft +1107 reordering_threshold: u64, +1108 /// The earliest ack-eliciting packet since the last ACK was sent, used to calculate the moment +1109 /// upon which `max_ack_delay` elapses +1110 earliest_ack_eliciting_since_last_ack_sent: Option<Instant>, +1111 /// Packet number ranges for which to still send acknowledgements. +1112 /// +1113 /// These are packet number ranges of ack-eliciting packets the peer has sent and which +1114 /// need to be acknowledged. Packet numbers are only removed from here once the peer has +1115 /// acknowledged the ACKs for them. +1116 ranges: ArrayRangeSet, +1117 /// The largest packet number received and the time it was received +1118 /// +1119 /// Used to calculate ACK delay in [`PendingAcks::ack_delay`]. +1120 largest_packet: Option<(u64, Instant)>, +1121 /// The ack-eliciting packet we have received with the largest packet number +1122 largest_ack_eliciting_packet: Option<u64>, +1123 /// The largest acknowledged packet number sent in an ACK frame +1124 largest_acked: Option<u64>, +1125} +1126 +1127impl PendingAcks { +1128 fn new() -> Self { +1129 Self { +1130 immediate_ack_required: false, +1131 ack_eliciting_since_last_ack_sent: 0, +1132 non_ack_eliciting_since_last_ack_sent: 0, +1133 ack_eliciting_threshold: 1, +1134 reordering_threshold: 1, +1135 earliest_ack_eliciting_since_last_ack_sent: None, +1136 ranges: Default::default(), +1137 largest_packet: Default::default(), +1138 largest_ack_eliciting_packet: Default::default(), +1139 largest_acked: Default::default(), +1140 } +1141 } +1142 +1143 pub(super) fn set_ack_frequency_params(&mut self, frame: &frame::AckFrequency) { +1144 self.ack_eliciting_threshold = frame.ack_eliciting_threshold.into_inner(); +1145 self.reordering_threshold = frame.reordering_threshold.into_inner(); +1146 } +1147 +1148 pub(super) fn set_immediate_ack_required(&mut self) { +1149 self.immediate_ack_required = true; +1150 } +1151 +1152 pub(super) fn on_max_ack_delay_timeout(&mut self) { +1153 self.immediate_ack_required = self.ack_eliciting_since_last_ack_sent > 0; +1154 } 1155 -1156 // Track largest ack-eliciting packet -1157 self.largest_ack_eliciting_packet = self -1158 .largest_ack_eliciting_packet -1159 .map(|pn| pn.max(packet_number)) -1160 .or(Some(packet_number)); -1161 -1162 // Handle ack_eliciting_threshold -1163 self.ack_eliciting_since_last_ack_sent += 1; -1164 self.immediate_ack_required |= -1165 self.ack_eliciting_since_last_ack_sent > self.ack_eliciting_threshold; -1166 -1167 // Handle out-of-order packets -1168 self.immediate_ack_required |= -1169 self.is_out_of_order(packet_number, prev_largest_ack_eliciting, dedup); -1170 -1171 // Arm max_ack_delay timer if necessary -1172 if self.earliest_ack_eliciting_since_last_ack_sent.is_none() && !self.can_send() { -1173 self.earliest_ack_eliciting_since_last_ack_sent = Some(now); -1174 return true; -1175 } -1176 -1177 false -1178 } -1179 -1180 fn is_out_of_order( -1181 &self, -1182 packet_number: u64, -1183 prev_largest_ack_eliciting: u64, -1184 dedup: &Dedup, -1185 ) -> bool { -1186 match self.reordering_threshold { -1187 0 => false, -1188 1 => { -1189 // From https://www.rfc-editor.org/rfc/rfc9000#section-13.2.1-7 -1190 packet_number < prev_largest_ack_eliciting -1191 || dedup.missing_in_interval(prev_largest_ack_eliciting, packet_number) -1192 } -1193 _ => { -1194 // From acknowledgement frequency draft, section 6.1: send an ACK immediately if -1195 // doing so would cause the sender to detect a new packet loss -1196 let Some((largest_acked, largest_unacked)) = -1197 self.largest_acked.zip(self.largest_ack_eliciting_packet) -1198 else { -1199 return false; -1200 }; -1201 if self.reordering_threshold > largest_acked { -1202 return false; -1203 } -1204 // The largest packet number that could be declared lost without a new ACK being -1205 // sent -1206 let largest_reported = largest_acked - self.reordering_threshold + 1; -1207 let Some(smallest_missing_unreported) = -1208 dedup.smallest_missing_in_interval(largest_reported, largest_unacked) -1209 else { -1210 return false; -1211 }; -1212 largest_unacked - smallest_missing_unreported >= self.reordering_threshold -1213 } -1214 } -1215 } +1156 pub(super) fn max_ack_delay_timeout(&self, max_ack_delay: Duration) -> Option<Instant> { +1157 self.earliest_ack_eliciting_since_last_ack_sent +1158 .map(|earliest_unacked| earliest_unacked + max_ack_delay) +1159 } +1160 +1161 /// Whether any ACK frames SHOULD be sent +1162 /// +1163 /// This is used in the top-level [`Connection::space_can_send`], so determines if a +1164 /// packet will be built. It is often possible to construct new ACK ranges to send +1165 /// before this returns `true`. This results in more ACK frames being sent, and +1166 /// processing those at the receiver costs CPU for very little improvements. +1167 /// +1168 /// [`Connection::space_can_send`]: super::Connection::space_can_send +1169 pub(super) fn can_send(&self) -> bool { +1170 self.immediate_ack_required && !self.ranges.is_empty() +1171 } +1172 +1173 /// Returns the delay since the packet with the largest packet number was received +1174 pub(super) fn ack_delay(&self, now: Instant) -> Duration { +1175 self.largest_packet +1176 .map_or_else(Duration::default, |(_, received)| now - received) +1177 } +1178 +1179 /// Handle receipt of a new packet +1180 /// +1181 /// Returns true if the max ack delay timer should be armed +1182 pub(super) fn packet_received( +1183 &mut self, +1184 now: Instant, +1185 packet_number: u64, +1186 ack_eliciting: bool, +1187 dedup: &Dedup, +1188 ) -> bool { +1189 if !ack_eliciting { +1190 self.non_ack_eliciting_since_last_ack_sent += 1; +1191 return false; +1192 } +1193 +1194 let prev_largest_ack_eliciting = self.largest_ack_eliciting_packet.unwrap_or(0); +1195 +1196 // Track largest ack-eliciting packet +1197 self.largest_ack_eliciting_packet = self +1198 .largest_ack_eliciting_packet +1199 .map(|pn| pn.max(packet_number)) +1200 .or(Some(packet_number)); +1201 +1202 // Handle ack_eliciting_threshold +1203 self.ack_eliciting_since_last_ack_sent += 1; +1204 self.immediate_ack_required |= +1205 self.ack_eliciting_since_last_ack_sent > self.ack_eliciting_threshold; +1206 +1207 // Handle out-of-order packets +1208 self.immediate_ack_required |= +1209 self.is_out_of_order(packet_number, prev_largest_ack_eliciting, dedup); +1210 +1211 // Arm max_ack_delay timer if necessary +1212 if self.earliest_ack_eliciting_since_last_ack_sent.is_none() && !self.can_send() { +1213 self.earliest_ack_eliciting_since_last_ack_sent = Some(now); +1214 return true; +1215 } 1216 -1217 /// Should be called whenever ACKs have been sent -1218 /// -1219 /// This will suppress sending further ACKs until additional ACK eliciting frames arrive -1220 pub(super) fn acks_sent(&mut self) { -1221 // It is possible (though unlikely) that the ACKs we just sent do not cover all the -1222 // ACK-eliciting packets we have received (e.g. if there is not enough room in the packet to -1223 // fit all the ranges). To keep things simple, however, we assume they do. If there are -1224 // indeed some ACKs that weren't covered, the packets might be ACKed later anyway, because -1225 // they are still contained in `self.ranges`. If we somehow fail to send the ACKs at a later -1226 // moment, the peer will assume the packets got lost and will retransmit their frames in a -1227 // new packet, which is suboptimal, because we already received them. Our assumption here is -1228 // that simplicity results in code that is more performant, even in the presence of -1229 // occasional redundant retransmits. -1230 self.immediate_ack_required = false; -1231 self.ack_eliciting_since_last_ack_sent = 0; -1232 self.non_ack_eliciting_since_last_ack_sent = 0; -1233 self.earliest_ack_eliciting_since_last_ack_sent = None; -1234 self.largest_acked = self.largest_ack_eliciting_packet; -1235 } -1236 -1237 /// Insert one packet that needs to be acknowledged -1238 pub(super) fn insert_one(&mut self, packet: u64, now: Instant) { -1239 self.ranges.insert_one(packet); -1240 -1241 if self.largest_packet.is_none_or(|(pn, _)| packet > pn) { -1242 self.largest_packet = Some((packet, now)); -1243 } -1244 -1245 if self.ranges.len() > MAX_ACK_BLOCKS { -1246 self.ranges.pop_min(); -1247 } -1248 } -1249 -1250 /// Remove ACKs of packets numbered at or below `max` from the set of pending ACKs -1251 pub(super) fn subtract_below(&mut self, max: u64) { -1252 self.ranges.remove(0..(max + 1)); -1253 } -1254 -1255 /// Returns the set of currently pending ACK ranges -1256 pub(super) fn ranges(&self) -> &ArrayRangeSet { -1257 &self.ranges -1258 } -1259 -1260 /// Queue an ACK if a significant number of non-ACK-eliciting packets have not yet been -1261 /// acknowledged -1262 /// -1263 /// Should be called immediately before a non-probing packet is composed, when we've already -1264 /// committed to sending a packet regardless. -1265 pub(super) fn maybe_ack_non_eliciting(&mut self) { -1266 // If we're going to send a packet anyway, and we've received a significant number of -1267 // non-ACK-eliciting packets, then include an ACK to help the peer perform timely loss -1268 // detection even if they're not sending any ACK-eliciting packets themselves. Exact -1269 // threshold chosen somewhat arbitrarily. -1270 const LAZY_ACK_THRESHOLD: u64 = 10; -1271 if self.non_ack_eliciting_since_last_ack_sent > LAZY_ACK_THRESHOLD { -1272 self.immediate_ack_required = true; -1273 } -1274 } -1275} +1217 false +1218 } +1219 +1220 fn is_out_of_order( +1221 &self, +1222 packet_number: u64, +1223 prev_largest_ack_eliciting: u64, +1224 dedup: &Dedup, +1225 ) -> bool { +1226 match self.reordering_threshold { +1227 0 => false, +1228 1 => { +1229 // From https://www.rfc-editor.org/rfc/rfc9000#section-13.2.1-7 +1230 packet_number < prev_largest_ack_eliciting +1231 || dedup.missing_in_interval(prev_largest_ack_eliciting, packet_number) +1232 } +1233 _ => { +1234 // From acknowledgement frequency draft, section 6.1: send an ACK immediately if +1235 // doing so would cause the sender to detect a new packet loss +1236 let Some((largest_acked, largest_unacked)) = +1237 self.largest_acked.zip(self.largest_ack_eliciting_packet) +1238 else { +1239 return false; +1240 }; +1241 if self.reordering_threshold > largest_acked { +1242 return false; +1243 } +1244 // The largest packet number that could be declared lost without a new ACK being +1245 // sent +1246 let largest_reported = largest_acked - self.reordering_threshold + 1; +1247 let Some(smallest_missing_unreported) = +1248 dedup.smallest_missing_in_interval(largest_reported, largest_unacked) +1249 else { +1250 return false; +1251 }; +1252 largest_unacked - smallest_missing_unreported >= self.reordering_threshold +1253 } +1254 } +1255 } +1256 +1257 /// Should be called whenever ACKs have been sent +1258 /// +1259 /// This will suppress sending further ACKs until additional ACK eliciting frames arrive +1260 pub(super) fn acks_sent(&mut self) { +1261 // It is possible (though unlikely) that the ACKs we just sent do not cover all the +1262 // ACK-eliciting packets we have received (e.g. if there is not enough room in the packet to +1263 // fit all the ranges). To keep things simple, however, we assume they do. If there are +1264 // indeed some ACKs that weren't covered, the packets might be ACKed later anyway, because +1265 // they are still contained in `self.ranges`. If we somehow fail to send the ACKs at a later +1266 // moment, the peer will assume the packets got lost and will retransmit their frames in a +1267 // new packet, which is suboptimal, because we already received them. Our assumption here is +1268 // that simplicity results in code that is more performant, even in the presence of +1269 // occasional redundant retransmits. +1270 self.immediate_ack_required = false; +1271 self.ack_eliciting_since_last_ack_sent = 0; +1272 self.non_ack_eliciting_since_last_ack_sent = 0; +1273 self.earliest_ack_eliciting_since_last_ack_sent = None; +1274 self.largest_acked = self.largest_ack_eliciting_packet; +1275 } 1276 -1277/// Helper for mitigating [optimistic ACK attacks] -1278/// -1279/// A malicious peer could prompt the local application to begin a large data transfer, and then -1280/// send ACKs without first waiting for data to be received. This could defeat congestion control, -1281/// allowing the connection to consume disproportionate resources. We therefore occasionally skip -1282/// packet numbers, and classify any ACK referencing a skipped packet number as a transport error. -1283/// -1284/// Skipped packet numbers occur only in the application data space (where costly transfers might -1285/// take place) and are distributed exponentially to reflect the reduced likelihood and impact of -1286/// bad behavior from a peer that has been well-behaved for an extended period. -1287/// -1288/// ACKs for packet numbers that have not yet been allocated are also a transport error, but an -1289/// attacker with knowledge of the congestion control algorithm in use could time falsified ACKs to -1290/// arrive after the packets they reference are sent. -1291/// -1292/// [optimistic ACK attacks]: https://www.rfc-editor.org/rfc/rfc9000.html#name-optimistic-ack-attack -1293pub(super) struct PacketNumberFilter { -1294 /// Next outgoing packet number to skip -1295 next_skipped_packet_number: u64, -1296 /// Most recently skipped packet number -1297 prev_skipped_packet_number: Option<u64>, -1298 /// Next packet number to skip is randomly selected from 2^n..2^n+1 -1299 exponent: u32, -1300} -1301 -1302impl PacketNumberFilter { -1303 pub(super) fn new(rng: &mut (impl CryptoRng + ?Sized)) -> Self { -1304 // First skipped PN is in 0..64 -1305 let exponent = 6; -1306 Self { -1307 next_skipped_packet_number: rng.random_range(0..2u64.saturating_pow(exponent)), -1308 prev_skipped_packet_number: None, -1309 exponent, -1310 } -1311 } -1312 -1313 #[cfg(test)] -1314 pub(super) fn disabled() -> Self { -1315 Self { -1316 next_skipped_packet_number: u64::MAX, -1317 prev_skipped_packet_number: None, -1318 exponent: u32::MAX, -1319 } -1320 } -1321 -1322 /// Whether to use the provided packet number (false) or to skip it (true) -1323 pub(super) fn skip_pn(&mut self, n: u64, rng: &mut (impl CryptoRng + ?Sized)) -> bool { -1324 if n != self.next_skipped_packet_number { -1325 return false; -1326 } -1327 -1328 trace!("skipping pn {n}"); -1329 // Skip this packet number, and choose the next one to skip -1330 self.prev_skipped_packet_number = Some(self.next_skipped_packet_number); -1331 let next_exponent = self.exponent.saturating_add(1); -1332 self.next_skipped_packet_number = rng -1333 .random_range(2u64.saturating_pow(self.exponent)..2u64.saturating_pow(next_exponent)); -1334 self.exponent = next_exponent; -1335 true -1336 } -1337} -1338 -1339/// Ensures we can always fit all our ACKs in a single minimum-MTU packet with room to spare -1340const MAX_ACK_BLOCKS: usize = 64; +1277 /// Insert one packet that needs to be acknowledged +1278 pub(super) fn insert_one(&mut self, packet: u64, now: Instant) { +1279 self.ranges.insert_one(packet); +1280 +1281 if self.largest_packet.is_none_or(|(pn, _)| packet > pn) { +1282 self.largest_packet = Some((packet, now)); +1283 } +1284 +1285 if self.ranges.range_count() > MAX_ACK_BLOCKS { +1286 self.ranges.pop_min(); +1287 } +1288 } +1289 +1290 /// Remove ACKs of packets numbered at or below `max` from the set of pending ACKs +1291 pub(super) fn subtract_below(&mut self, max: u64) { +1292 self.ranges.remove(0..(max + 1)); +1293 } +1294 +1295 /// Returns the set of currently pending ACK ranges +1296 pub(super) fn ranges(&self) -> &ArrayRangeSet { +1297 &self.ranges +1298 } +1299 +1300 /// Queue an ACK if a significant number of non-ACK-eliciting packets have not yet been +1301 /// acknowledged +1302 /// +1303 /// Should be called immediately before a non-probing packet is composed, when we've already +1304 /// committed to sending a packet regardless. +1305 pub(super) fn maybe_ack_non_eliciting(&mut self) { +1306 // If we're going to send a packet anyway, and we've received a significant number of +1307 // non-ACK-eliciting packets, then include an ACK to help the peer perform timely loss +1308 // detection even if they're not sending any ACK-eliciting packets themselves. Exact +1309 // threshold chosen somewhat arbitrarily. +1310 const LAZY_ACK_THRESHOLD: u64 = 10; +1311 if self.non_ack_eliciting_since_last_ack_sent > LAZY_ACK_THRESHOLD { +1312 self.immediate_ack_required = true; +1313 } +1314 } +1315} +1316 +1317/// Helper for mitigating [optimistic ACK attacks] +1318/// +1319/// A malicious peer could prompt the local application to begin a large data transfer, and then +1320/// send ACKs without first waiting for data to be received. This could defeat congestion control, +1321/// allowing the connection to consume disproportionate resources. We therefore occasionally skip +1322/// packet numbers, and classify any ACK referencing a skipped packet number as a transport error. +1323/// +1324/// Skipped packet numbers occur only in the application data space (where costly transfers might +1325/// take place) and are distributed exponentially to reflect the reduced likelihood and impact of +1326/// bad behavior from a peer that has been well-behaved for an extended period. +1327/// +1328/// ACKs for packet numbers that have not yet been allocated are also a transport error, but an +1329/// attacker with knowledge of the congestion control algorithm in use could time falsified ACKs to +1330/// arrive after the packets they reference are sent. +1331/// +1332/// [optimistic ACK attacks]: https://www.rfc-editor.org/rfc/rfc9000.html#name-optimistic-ack-attack +1333pub(super) struct PacketNumberFilter { +1334 /// Next outgoing packet number to skip +1335 next_skipped_packet_number: u64, +1336 /// Most recently skipped packet number +1337 prev_skipped_packet_number: Option<u64>, +1338 /// Next packet number to skip is randomly selected from 2^n..2^n+1 +1339 exponent: u32, +1340} 1341 -1342#[cfg(test)] -1343mod test { -1344 use rand::Rng; -1345 use rand::seq::SliceRandom; -1346 -1347 use crate::token::ResetToken; -1348 use crate::{ConnectionIdGenerator, RandomConnectionIdGenerator}; -1349 -1350 use super::*; -1351 -1352 #[test] -1353 fn sanity() { -1354 let mut dedup = Dedup::new(); -1355 assert!(!dedup.insert(0)); -1356 assert_eq!(dedup.next, 1); -1357 assert_eq!(dedup.window, 0b1); -1358 assert!(dedup.insert(0)); -1359 assert_eq!(dedup.next, 1); -1360 assert_eq!(dedup.window, 0b1); -1361 assert!(!dedup.insert(1)); -1362 assert_eq!(dedup.next, 2); -1363 assert_eq!(dedup.window, 0b11); -1364 assert!(!dedup.insert(2)); -1365 assert_eq!(dedup.next, 3); -1366 assert_eq!(dedup.window, 0b111); -1367 assert!(!dedup.insert(4)); -1368 assert_eq!(dedup.next, 5); -1369 assert_eq!(dedup.window, 0b11110); -1370 assert!(!dedup.insert(7)); -1371 assert_eq!(dedup.next, 8); -1372 assert_eq!(dedup.window, 0b1111_0100); -1373 assert!(dedup.insert(4)); -1374 assert!(!dedup.insert(3)); -1375 assert_eq!(dedup.next, 8); -1376 assert_eq!(dedup.window, 0b1111_1100); -1377 assert!(!dedup.insert(6)); -1378 assert_eq!(dedup.next, 8); -1379 assert_eq!(dedup.window, 0b1111_1101); -1380 assert!(!dedup.insert(5)); -1381 assert_eq!(dedup.next, 8); -1382 assert_eq!(dedup.window, 0b1111_1111); -1383 } -1384 -1385 #[test] -1386 fn happypath() { -1387 let mut dedup = Dedup::new(); -1388 for i in 0..(2 * WINDOW_SIZE) { -1389 assert!(!dedup.insert(i)); -1390 for j in 0..=i { -1391 assert!(dedup.insert(j)); -1392 } -1393 } -1394 } -1395 -1396 #[test] -1397 fn jump() { -1398 let mut dedup = Dedup::new(); -1399 dedup.insert(2 * WINDOW_SIZE); -1400 assert!(dedup.insert(WINDOW_SIZE)); -1401 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); -1402 assert_eq!(dedup.window, 0); -1403 assert!(!dedup.insert(WINDOW_SIZE + 1)); -1404 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); -1405 assert_eq!(dedup.window, 1 << (WINDOW_SIZE - 2)); -1406 } -1407 -1408 #[test] -1409 fn dedup_has_missing() { -1410 let mut dedup = Dedup::new(); -1411 -1412 dedup.insert(0); -1413 assert!(!dedup.missing_in_interval(0, 0)); -1414 -1415 dedup.insert(1); -1416 assert!(!dedup.missing_in_interval(0, 1)); -1417 -1418 dedup.insert(3); -1419 assert!(dedup.missing_in_interval(1, 3)); -1420 -1421 dedup.insert(4); -1422 assert!(!dedup.missing_in_interval(3, 4)); -1423 assert!(dedup.missing_in_interval(0, 4)); +1342impl PacketNumberFilter { +1343 pub(super) fn new(rng: &mut (impl CryptoRng + ?Sized)) -> Self { +1344 // First skipped PN is in 0..64 +1345 let exponent = 6; +1346 Self { +1347 next_skipped_packet_number: rng.random_range(0..2u64.saturating_pow(exponent)), +1348 prev_skipped_packet_number: None, +1349 exponent, +1350 } +1351 } +1352 +1353 #[cfg(test)] +1354 pub(super) fn disabled() -> Self { +1355 Self { +1356 next_skipped_packet_number: u64::MAX, +1357 prev_skipped_packet_number: None, +1358 exponent: u32::MAX, +1359 } +1360 } +1361 +1362 /// Whether to use the provided packet number (false) or to skip it (true) +1363 pub(super) fn skip_pn(&mut self, n: u64, rng: &mut (impl CryptoRng + ?Sized)) -> bool { +1364 if n != self.next_skipped_packet_number { +1365 return false; +1366 } +1367 +1368 trace!("skipping pn {n}"); +1369 // Skip this packet number, and choose the next one to skip +1370 self.prev_skipped_packet_number = Some(self.next_skipped_packet_number); +1371 let next_exponent = self.exponent.saturating_add(1); +1372 self.next_skipped_packet_number = rng +1373 .random_range(2u64.saturating_pow(self.exponent)..2u64.saturating_pow(next_exponent)); +1374 self.exponent = next_exponent; +1375 true +1376 } +1377} +1378 +1379/// Ensures we can always fit all our ACKs in a single minimum-MTU packet with room to spare +1380const MAX_ACK_BLOCKS: usize = 64; +1381 +1382#[cfg(test)] +1383mod test { +1384 use rand::Rng; +1385 use rand::seq::SliceRandom; +1386 +1387 use crate::token::ResetToken; +1388 use crate::{ConnectionIdGenerator, RandomConnectionIdGenerator}; +1389 +1390 use super::*; +1391 +1392 #[test] +1393 fn sanity() { +1394 let mut dedup = Dedup::new(); +1395 assert!(!dedup.insert(0)); +1396 assert_eq!(dedup.next, 1); +1397 assert_eq!(dedup.window, 0b1); +1398 assert!(dedup.insert(0)); +1399 assert_eq!(dedup.next, 1); +1400 assert_eq!(dedup.window, 0b1); +1401 assert!(!dedup.insert(1)); +1402 assert_eq!(dedup.next, 2); +1403 assert_eq!(dedup.window, 0b11); +1404 assert!(!dedup.insert(2)); +1405 assert_eq!(dedup.next, 3); +1406 assert_eq!(dedup.window, 0b111); +1407 assert!(!dedup.insert(4)); +1408 assert_eq!(dedup.next, 5); +1409 assert_eq!(dedup.window, 0b11110); +1410 assert!(!dedup.insert(7)); +1411 assert_eq!(dedup.next, 8); +1412 assert_eq!(dedup.window, 0b1111_0100); +1413 assert!(dedup.insert(4)); +1414 assert!(!dedup.insert(3)); +1415 assert_eq!(dedup.next, 8); +1416 assert_eq!(dedup.window, 0b1111_1100); +1417 assert!(!dedup.insert(6)); +1418 assert_eq!(dedup.next, 8); +1419 assert_eq!(dedup.window, 0b1111_1101); +1420 assert!(!dedup.insert(5)); +1421 assert_eq!(dedup.next, 8); +1422 assert_eq!(dedup.window, 0b1111_1111); +1423 } 1424 -1425 dedup.insert(2); -1426 assert!(!dedup.missing_in_interval(0, 4)); -1427 } -1428 -1429 #[test] -1430 fn dedup_outside_of_window_has_missing() { -1431 let mut dedup = Dedup::new(); -1432 -1433 for i in 0..140 { -1434 dedup.insert(i); -1435 } -1436 -1437 // 0 and 4 are outside of the window -1438 assert!(!dedup.missing_in_interval(0, 4)); -1439 dedup.insert(160); -1440 assert!(!dedup.missing_in_interval(0, 4)); -1441 assert!(!dedup.missing_in_interval(0, 140)); -1442 assert!(dedup.missing_in_interval(0, 160)); -1443 } -1444 -1445 #[test] -1446 fn dedup_smallest_missing() { -1447 let mut dedup = Dedup::new(); -1448 -1449 dedup.insert(0); -1450 assert_eq!(dedup.smallest_missing_in_interval(0, 0), None); +1425 #[test] +1426 fn happypath() { +1427 let mut dedup = Dedup::new(); +1428 for i in 0..(2 * WINDOW_SIZE) { +1429 assert!(!dedup.insert(i)); +1430 for j in 0..=i { +1431 assert!(dedup.insert(j)); +1432 } +1433 } +1434 } +1435 +1436 #[test] +1437 fn jump() { +1438 let mut dedup = Dedup::new(); +1439 dedup.insert(2 * WINDOW_SIZE); +1440 assert!(dedup.insert(WINDOW_SIZE)); +1441 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); +1442 assert_eq!(dedup.window, 0); +1443 assert!(!dedup.insert(WINDOW_SIZE + 1)); +1444 assert_eq!(dedup.next, 2 * WINDOW_SIZE + 1); +1445 assert_eq!(dedup.window, 1 << (WINDOW_SIZE - 2)); +1446 } +1447 +1448 #[test] +1449 fn dedup_has_missing() { +1450 let mut dedup = Dedup::new(); 1451 -1452 dedup.insert(1); -1453 assert_eq!(dedup.smallest_missing_in_interval(0, 1), None); +1452 dedup.insert(0); +1453 assert!(!dedup.missing_in_interval(0, 0)); 1454 -1455 dedup.insert(5); -1456 dedup.insert(7); -1457 assert_eq!(dedup.smallest_missing_in_interval(0, 7), Some(2)); -1458 assert_eq!(dedup.smallest_missing_in_interval(5, 7), Some(6)); -1459 -1460 dedup.insert(2); -1461 assert_eq!(dedup.smallest_missing_in_interval(1, 7), Some(3)); -1462 -1463 dedup.insert(170); -1464 dedup.insert(172); -1465 dedup.insert(300); -1466 assert_eq!(dedup.smallest_missing_in_interval(170, 172), None); -1467 -1468 dedup.insert(500); -1469 assert_eq!(dedup.smallest_missing_in_interval(0, 500), Some(372)); -1470 assert_eq!(dedup.smallest_missing_in_interval(0, 373), Some(372)); -1471 assert_eq!(dedup.smallest_missing_in_interval(0, 372), None); -1472 } -1473 -1474 #[test] -1475 fn pending_acks_first_packet_is_not_considered_reordered() { -1476 let mut acks = PendingAcks::new(); -1477 let mut dedup = Dedup::new(); -1478 dedup.insert(0); -1479 acks.packet_received(Instant::now(), 0, true, &dedup); -1480 assert!(!acks.immediate_ack_required); -1481 } -1482 -1483 #[test] -1484 fn pending_acks_after_immediate_ack_set() { -1485 let mut acks = PendingAcks::new(); -1486 let mut dedup = Dedup::new(); -1487 -1488 // Receive ack-eliciting packet -1489 dedup.insert(0); -1490 let now = Instant::now(); -1491 acks.insert_one(0, now); -1492 acks.packet_received(now, 0, true, &dedup); -1493 -1494 // Sanity check -1495 assert!(!acks.ranges.is_empty()); -1496 assert!(!acks.can_send()); -1497 -1498 // Can send ACK after max_ack_delay exceeded -1499 acks.set_immediate_ack_required(); -1500 assert!(acks.can_send()); -1501 } +1455 dedup.insert(1); +1456 assert!(!dedup.missing_in_interval(0, 1)); +1457 +1458 dedup.insert(3); +1459 assert!(dedup.missing_in_interval(1, 3)); +1460 +1461 dedup.insert(4); +1462 assert!(!dedup.missing_in_interval(3, 4)); +1463 assert!(dedup.missing_in_interval(0, 4)); +1464 +1465 dedup.insert(2); +1466 assert!(!dedup.missing_in_interval(0, 4)); +1467 } +1468 +1469 #[test] +1470 fn dedup_outside_of_window_has_missing() { +1471 let mut dedup = Dedup::new(); +1472 +1473 for i in 0..140 { +1474 dedup.insert(i); +1475 } +1476 +1477 // 0 and 4 are outside of the window +1478 assert!(!dedup.missing_in_interval(0, 4)); +1479 dedup.insert(160); +1480 assert!(!dedup.missing_in_interval(0, 4)); +1481 assert!(!dedup.missing_in_interval(0, 140)); +1482 assert!(dedup.missing_in_interval(0, 160)); +1483 } +1484 +1485 #[test] +1486 fn dedup_smallest_missing() { +1487 let mut dedup = Dedup::new(); +1488 +1489 dedup.insert(0); +1490 assert_eq!(dedup.smallest_missing_in_interval(0, 0), None); +1491 +1492 dedup.insert(1); +1493 assert_eq!(dedup.smallest_missing_in_interval(0, 1), None); +1494 +1495 dedup.insert(5); +1496 dedup.insert(7); +1497 assert_eq!(dedup.smallest_missing_in_interval(0, 7), Some(2)); +1498 assert_eq!(dedup.smallest_missing_in_interval(5, 7), Some(6)); +1499 +1500 dedup.insert(2); +1501 assert_eq!(dedup.smallest_missing_in_interval(1, 7), Some(3)); 1502 -1503 #[test] -1504 fn pending_acks_ack_delay() { -1505 let mut acks = PendingAcks::new(); -1506 let mut dedup = Dedup::new(); +1503 dedup.insert(170); +1504 dedup.insert(172); +1505 dedup.insert(300); +1506 assert_eq!(dedup.smallest_missing_in_interval(170, 172), None); 1507 -1508 let t1 = Instant::now(); -1509 let t2 = t1 + Duration::from_millis(2); -1510 let t3 = t2 + Duration::from_millis(5); -1511 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); -1512 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); -1513 assert_eq!(acks.ack_delay(t3), Duration::from_millis(0)); -1514 -1515 // In-order packet -1516 dedup.insert(0); -1517 acks.insert_one(0, t1); -1518 acks.packet_received(t1, 0, true, &dedup); -1519 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); -1520 assert_eq!(acks.ack_delay(t2), Duration::from_millis(2)); -1521 assert_eq!(acks.ack_delay(t3), Duration::from_millis(7)); +1508 dedup.insert(500); +1509 assert_eq!(dedup.smallest_missing_in_interval(0, 500), Some(372)); +1510 assert_eq!(dedup.smallest_missing_in_interval(0, 373), Some(372)); +1511 assert_eq!(dedup.smallest_missing_in_interval(0, 372), None); +1512 } +1513 +1514 #[test] +1515 fn pending_acks_first_packet_is_not_considered_reordered() { +1516 let mut acks = PendingAcks::new(); +1517 let mut dedup = Dedup::new(); +1518 dedup.insert(0); +1519 acks.packet_received(Instant::now(), 0, true, &dedup); +1520 assert!(!acks.immediate_ack_required); +1521 } 1522 -1523 // Out of order (higher than expected) -1524 dedup.insert(3); -1525 acks.insert_one(3, t2); -1526 acks.packet_received(t2, 3, true, &dedup); -1527 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); -1528 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); -1529 -1530 // Out of order (lower than expected, so previous instant is kept) -1531 dedup.insert(2); -1532 acks.insert_one(2, t3); -1533 acks.packet_received(t3, 2, true, &dedup); -1534 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); -1535 } -1536 -1537 #[test] -1538 fn sent_packet_size() { -1539 // The tracking state of sent packets should be minimal, and not grow -1540 // over time. -1541 assert!(std::mem::size_of::<SentPacket>() <= 128); -1542 } -1543 -1544 #[test] -1545 fn pending_new_cids() { -1546 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] -1547 use aws_lc_rs::hmac; -1548 #[cfg(feature = "ring")] -1549 use ring::hmac; -1550 -1551 let mut cid_generator = RandomConnectionIdGenerator::new(8); -1552 let mut reset_key = [0; 64]; -1553 rand::rng().fill_bytes(&mut reset_key); -1554 let hmac = hmac::Key::new(hmac::HMAC_SHA256, &reset_key); -1555 -1556 let cid_a = cid_generator.generate_cid(); -1557 let a = IssuedCid { -1558 path_id: PathId::ZERO, -1559 sequence: 1, -1560 id: cid_a, -1561 reset_token: ResetToken::new(&hmac, cid_a), -1562 }; -1563 let cid_b = cid_generator.generate_cid(); -1564 let b = IssuedCid { -1565 path_id: PathId::ZERO, -1566 sequence: 2, -1567 id: cid_b, -1568 reset_token: ResetToken::new(&hmac, cid_b), -1569 }; -1570 let cid_c = cid_generator.generate_cid(); -1571 let c = IssuedCid { -1572 path_id: PathId(1), -1573 sequence: 1, -1574 id: cid_c, -1575 reset_token: ResetToken::new(&hmac, cid_c), -1576 }; -1577 -1578 let mut pending_cids = PendingNewCids::default(); -1579 -1580 for _ in 0..9 { -1581 // Push CIDs in a random order -1582 let mut input = vec![a, b, c]; -1583 input.shuffle(&mut rand::rng()); -1584 for cid in input { -1585 pending_cids.push(cid); -1586 } -1587 -1588 // Pop order is always the same -1589 assert_eq!(pending_cids.pop().map(|i| i.id), Some(a.id)); -1590 assert_eq!(pending_cids.pop().map(|i| i.id), Some(b.id)); -1591 assert_eq!(pending_cids.pop().map(|i| i.id), Some(c.id)); -1592 assert!(pending_cids.pop().is_none()); -1593 } -1594 } -1595}

\ No newline at end of file +1523 #[test] +1524 fn pending_acks_after_immediate_ack_set() { +1525 let mut acks = PendingAcks::new(); +1526 let mut dedup = Dedup::new(); +1527 +1528 // Receive ack-eliciting packet +1529 dedup.insert(0); +1530 let now = Instant::now(); +1531 acks.insert_one(0, now); +1532 acks.packet_received(now, 0, true, &dedup); +1533 +1534 // Sanity check +1535 assert!(!acks.ranges.is_empty()); +1536 assert!(!acks.can_send()); +1537 +1538 // Can send ACK after max_ack_delay exceeded +1539 acks.set_immediate_ack_required(); +1540 assert!(acks.can_send()); +1541 } +1542 +1543 #[test] +1544 fn pending_acks_ack_delay() { +1545 let mut acks = PendingAcks::new(); +1546 let mut dedup = Dedup::new(); +1547 +1548 let t1 = Instant::now(); +1549 let t2 = t1 + Duration::from_millis(2); +1550 let t3 = t2 + Duration::from_millis(5); +1551 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); +1552 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); +1553 assert_eq!(acks.ack_delay(t3), Duration::from_millis(0)); +1554 +1555 // In-order packet +1556 dedup.insert(0); +1557 acks.insert_one(0, t1); +1558 acks.packet_received(t1, 0, true, &dedup); +1559 assert_eq!(acks.ack_delay(t1), Duration::from_millis(0)); +1560 assert_eq!(acks.ack_delay(t2), Duration::from_millis(2)); +1561 assert_eq!(acks.ack_delay(t3), Duration::from_millis(7)); +1562 +1563 // Out of order (higher than expected) +1564 dedup.insert(3); +1565 acks.insert_one(3, t2); +1566 acks.packet_received(t2, 3, true, &dedup); +1567 assert_eq!(acks.ack_delay(t2), Duration::from_millis(0)); +1568 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); +1569 +1570 // Out of order (lower than expected, so previous instant is kept) +1571 dedup.insert(2); +1572 acks.insert_one(2, t3); +1573 acks.packet_received(t3, 2, true, &dedup); +1574 assert_eq!(acks.ack_delay(t3), Duration::from_millis(5)); +1575 } +1576 +1577 #[test] +1578 fn sent_packet_size() { +1579 // The tracking state of sent packets should be minimal, and not grow +1580 // over time. +1581 assert!(std::mem::size_of::<SentPacket>() <= 128); +1582 } +1583 +1584 #[test] +1585 fn pending_new_cids() { +1586 #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] +1587 use aws_lc_rs::hmac; +1588 #[cfg(feature = "ring")] +1589 use ring::hmac; +1590 +1591 let mut cid_generator = RandomConnectionIdGenerator::new(8); +1592 let mut reset_key = [0; 64]; +1593 rand::rng().fill_bytes(&mut reset_key); +1594 let hmac = hmac::Key::new(hmac::HMAC_SHA256, &reset_key); +1595 +1596 let cid_a = cid_generator.generate_cid(); +1597 let a = IssuedCid { +1598 path_id: PathId::ZERO, +1599 sequence: 1, +1600 id: cid_a, +1601 reset_token: ResetToken::new(&hmac, cid_a), +1602 }; +1603 let cid_b = cid_generator.generate_cid(); +1604 let b = IssuedCid { +1605 path_id: PathId::ZERO, +1606 sequence: 2, +1607 id: cid_b, +1608 reset_token: ResetToken::new(&hmac, cid_b), +1609 }; +1610 let cid_c = cid_generator.generate_cid(); +1611 let c = IssuedCid { +1612 path_id: PathId(1), +1613 sequence: 1, +1614 id: cid_c, +1615 reset_token: ResetToken::new(&hmac, cid_c), +1616 }; +1617 +1618 let mut pending_cids = PendingNewCids::default(); +1619 +1620 for _ in 0..9 { +1621 // Push CIDs in a random order +1622 let mut input = vec![a, b, c]; +1623 input.shuffle(&mut rand::rng()); +1624 for cid in input { +1625 pending_cids.push(cid); +1626 } +1627 +1628 // Pop order is always the same +1629 assert_eq!(pending_cids.pop().map(|i| i.id), Some(a.id)); +1630 assert_eq!(pending_cids.pop().map(|i| i.id), Some(b.id)); +1631 assert_eq!(pending_cids.pop().map(|i| i.id), Some(c.id)); +1632 assert!(pending_cids.pop().is_none()); +1633 } +1634 } +1635}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/state.rs.html b/pr/451/docs/src/noq_proto/connection/state.rs.html index d069d420d..5129e6fca 100644 --- a/pr/451/docs/src/noq_proto/connection/state.rs.html +++ b/pr/451/docs/src/noq_proto/connection/state.rs.html @@ -66,368 +66,375 @@ 66 /// Moves to the drained state. 67 /// 68 /// Panics if the state was already drained. -69 pub(super) fn move_to_drained(&mut self, error: Option<ConnectionError>) { -70 let (error, is_local) = if let Some(error) = error { -71 (Some(error), false) -72 } else { -73 let error = match &mut self.inner { -74 InnerState::Draining { error, .. } => error.take(), -75 InnerState::Drained { .. } => panic!("invalid state transition drained -> drained"), -76 InnerState::Closed { error_read, .. } if *error_read => None, -77 InnerState::Closed { remote_reason, .. } => { -78 let error = match remote_reason.clone().into() { -79 ConnectionError::ConnectionClosed(close) => { -80 if close.error_code == TransportErrorCode::PROTOCOL_VIOLATION { -81 ConnectionError::TransportError(TransportError::new( -82 close.error_code, -83 std::string::String::from_utf8_lossy(&close.reason[..]) -84 .to_string(), -85 )) -86 } else { -87 ConnectionError::ConnectionClosed(close) -88 } -89 } -90 e => e, -91 }; -92 Some(error) -93 } -94 InnerState::Handshake(_) | InnerState::Established => None, -95 }; -96 (error, self.is_local_close()) -97 }; -98 self.inner = InnerState::Drained { error, is_local }; -99 trace!("connection state: drained"); -100 } -101 -102 /// Moves to a draining state. -103 /// -104 /// Panics if the state is already draining or drained. -105 pub(super) fn move_to_draining(&mut self, error: Option<ConnectionError>) { -106 assert!( -107 matches!( -108 self.inner, -109 InnerState::Handshake(_) | InnerState::Established | InnerState::Closed { .. } -110 ), -111 "invalid state transition {:?} -> draining", -112 self.as_type() -113 ); -114 let is_local = self.is_local_close(); -115 -116 // If no error is provided and we were in the `Closed` state with an unread non-local -117 // error, preserve that error so `take_error` can still surface it. Otherwise the -118 // `ConnectionLost` event would never be emitted, breaking the invariant that a -119 // drained noq-proto connection will always produce a `ConnectionLost` event. -120 let error = error.or_else(|| { -121 if let InnerState::Closed { -122 ref remote_reason, -123 error_read: false, -124 is_local: false, -125 } = self.inner -126 { -127 Some(remote_reason.clone().into()) -128 } else { -129 None -130 } -131 }); -132 -133 self.inner = InnerState::Draining { error, is_local }; -134 trace!("connection state: draining"); -135 } -136 -137 fn is_local_close(&self) -> bool { -138 match self.inner { -139 InnerState::Handshake(_) => false, -140 InnerState::Established => false, -141 InnerState::Closed { is_local, .. } => is_local, -142 InnerState::Draining { is_local, .. } => is_local, -143 InnerState::Drained { is_local, .. } => is_local, -144 } -145 } -146 -147 /// Enters the Closing connection state, due to changes in the [`Connection`] state. -148 /// -149 /// This is the closing state from -150 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1> due to the local side -151 /// having initiated immediate close. -152 /// -153 /// Crucially, this is to be used when internal state changes result in initiating an -154 /// immediate close. The resulting error will be surfaced as a [`ConnectionLost`] event -155 /// in [`Connection::poll`]. -156 /// -157 /// # Panics -158 /// -159 /// Panics if the state is later than established. -160 /// -161 /// [`Connection`]: super::Connection -162 /// [`ConnectionLost`]: crate::Event::ConnectionLost -163 /// [`Connection::poll`]: super::Connection::poll -164 pub(super) fn move_to_closed<R: Into<CloseReason>>(&mut self, reason: R) { -165 assert!( -166 matches!( -167 self.inner, -168 InnerState::Handshake(_) | InnerState::Established | InnerState::Closed { .. } -169 ), -170 "invalid state transition {:?} -> closed", -171 self.as_type() -172 ); -173 let remote_reason = reason.into(); -174 let is_local = false; -175 trace!(?remote_reason, ?is_local, "connection state: closed"); -176 self.inner = InnerState::Closed { -177 error_read: false, -178 remote_reason, -179 is_local, -180 }; -181 } -182 -183 /// Enters the Closing connection state, initiated by explicit API calls. -184 /// -185 /// This is the closing state from -186 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1> due to the local side -187 /// having initiated immediate close. -188 /// -189 /// Crucially, this is to be used when immediate close is entered due to an API -190 /// being called. It means the close will NOT surface as a [`ConnectionLost`] event in -191 /// [`Connection::poll`]. -192 /// -193 /// See [`Self::move_to_closed`] for when the internal state changes resulted in -194 /// initiating an immediate close. +69 /// +70 /// Returns whether we were in the draining state before. +71 pub(super) fn move_to_drained(&mut self, error: Option<ConnectionError>) -> bool { +72 let (error, is_local, was_draining) = if let Some(error) = error { +73 ( +74 Some(error), +75 false, +76 matches!(self.inner, InnerState::Draining { .. }), +77 ) +78 } else { +79 let (error, was_draining) = match &mut self.inner { +80 InnerState::Draining { error, .. } => (error.take(), true), +81 InnerState::Drained { .. } => panic!("invalid state transition drained -> drained"), +82 InnerState::Closed { error_read, .. } if *error_read => (None, false), +83 InnerState::Closed { remote_reason, .. } => { +84 let error = match remote_reason.clone().into() { +85 ConnectionError::ConnectionClosed(close) => { +86 if close.error_code == TransportErrorCode::PROTOCOL_VIOLATION { +87 ConnectionError::TransportError(TransportError::new( +88 close.error_code, +89 std::string::String::from_utf8_lossy(&close.reason[..]) +90 .to_string(), +91 )) +92 } else { +93 ConnectionError::ConnectionClosed(close) +94 } +95 } +96 e => e, +97 }; +98 (Some(error), false) +99 } +100 InnerState::Handshake(_) | InnerState::Established => (None, false), +101 }; +102 (error, self.is_local_close(), was_draining) +103 }; +104 self.inner = InnerState::Drained { error, is_local }; +105 trace!("connection state: drained"); +106 was_draining +107 } +108 +109 /// Moves to a draining state. +110 /// +111 /// Panics if the state is already draining or drained. +112 pub(super) fn move_to_draining(&mut self, error: Option<ConnectionError>) { +113 assert!( +114 matches!( +115 self.inner, +116 InnerState::Handshake(_) | InnerState::Established | InnerState::Closed { .. } +117 ), +118 "invalid state transition {:?} -> draining", +119 self.as_type() +120 ); +121 let is_local = self.is_local_close(); +122 +123 // If no error is provided and we were in the `Closed` state with an unread non-local +124 // error, preserve that error so `take_error` can still surface it. Otherwise the +125 // `ConnectionLost` event would never be emitted, breaking the invariant that a +126 // drained noq-proto connection will always produce a `ConnectionLost` event. +127 let error = error.or_else(|| { +128 if let InnerState::Closed { +129 ref remote_reason, +130 error_read: false, +131 is_local: false, +132 } = self.inner +133 { +134 Some(remote_reason.clone().into()) +135 } else { +136 None +137 } +138 }); +139 +140 self.inner = InnerState::Draining { error, is_local }; +141 trace!("connection state: draining"); +142 } +143 +144 fn is_local_close(&self) -> bool { +145 match self.inner { +146 InnerState::Handshake(_) => false, +147 InnerState::Established => false, +148 InnerState::Closed { is_local, .. } => is_local, +149 InnerState::Draining { is_local, .. } => is_local, +150 InnerState::Drained { is_local, .. } => is_local, +151 } +152 } +153 +154 /// Enters the Closing connection state, due to changes in the [`Connection`] state. +155 /// +156 /// This is the closing state from +157 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1> due to the local side +158 /// having initiated immediate close. +159 /// +160 /// Crucially, this is to be used when internal state changes result in initiating an +161 /// immediate close. The resulting error will be surfaced as a [`ConnectionLost`] event +162 /// in [`Connection::poll`]. +163 /// +164 /// # Panics +165 /// +166 /// Panics if the state is later than established. +167 /// +168 /// [`Connection`]: super::Connection +169 /// [`ConnectionLost`]: crate::Event::ConnectionLost +170 /// [`Connection::poll`]: super::Connection::poll +171 pub(super) fn move_to_closed<R: Into<CloseReason>>(&mut self, reason: R) { +172 assert!( +173 matches!( +174 self.inner, +175 InnerState::Handshake(_) | InnerState::Established | InnerState::Closed { .. } +176 ), +177 "invalid state transition {:?} -> closed", +178 self.as_type() +179 ); +180 let remote_reason = reason.into(); +181 let is_local = false; +182 trace!(?remote_reason, ?is_local, "connection state: closed"); +183 self.inner = InnerState::Closed { +184 error_read: false, +185 remote_reason, +186 is_local, +187 }; +188 } +189 +190 /// Enters the Closing connection state, initiated by explicit API calls. +191 /// +192 /// This is the closing state from +193 /// <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1> due to the local side +194 /// having initiated immediate close. 195 /// -196 /// # Panics -197 /// -198 /// Panics if the state is later than established. +196 /// Crucially, this is to be used when immediate close is entered due to an API +197 /// being called. It means the close will NOT surface as a [`ConnectionLost`] event in +198 /// [`Connection::poll`]. 199 /// -200 /// [`ConnectionLost`]: crate::Event::ConnectionLost -201 /// [`Connection::poll`]: super::Connection::poll -202 pub(super) fn move_to_closed_local<R: Into<CloseReason>>(&mut self, reason: R) { -203 assert!( -204 matches!( -205 self.inner, -206 InnerState::Handshake(_) | InnerState::Established | InnerState::Closed { .. } -207 ), -208 "invalid state transition {:?} -> closed (local)", -209 self.as_type() -210 ); -211 let remote_reason = reason.into(); -212 let is_local = true; -213 trace!(?remote_reason, ?is_local, "connection state: closed"); -214 self.inner = InnerState::Closed { -215 error_read: false, -216 remote_reason, -217 is_local, -218 }; -219 } -220 -221 pub(super) fn is_handshake(&self) -> bool { -222 matches!(self.inner, InnerState::Handshake(_)) -223 } -224 -225 pub(super) fn is_established(&self) -> bool { -226 matches!(self.inner, InnerState::Established) -227 } -228 -229 pub(super) fn is_closed(&self) -> bool { -230 matches!( -231 self.inner, -232 InnerState::Closed { .. } | InnerState::Draining { .. } | InnerState::Drained { .. } -233 ) +200 /// See [`Self::move_to_closed`] for when the internal state changes resulted in +201 /// initiating an immediate close. +202 /// +203 /// # Panics +204 /// +205 /// Panics if the state is later than established. +206 /// +207 /// [`ConnectionLost`]: crate::Event::ConnectionLost +208 /// [`Connection::poll`]: super::Connection::poll +209 pub(super) fn move_to_closed_local<R: Into<CloseReason>>(&mut self, reason: R) { +210 assert!( +211 matches!( +212 self.inner, +213 InnerState::Handshake(_) | InnerState::Established | InnerState::Closed { .. } +214 ), +215 "invalid state transition {:?} -> closed (local)", +216 self.as_type() +217 ); +218 let remote_reason = reason.into(); +219 let is_local = true; +220 trace!(?remote_reason, ?is_local, "connection state: closed"); +221 self.inner = InnerState::Closed { +222 error_read: false, +223 remote_reason, +224 is_local, +225 }; +226 } +227 +228 pub(super) fn is_handshake(&self) -> bool { +229 matches!(self.inner, InnerState::Handshake(_)) +230 } +231 +232 pub(super) fn is_established(&self) -> bool { +233 matches!(self.inner, InnerState::Established) 234 } 235 -236 pub(super) fn is_drained(&self) -> bool { -237 matches!(self.inner, InnerState::Drained { .. }) -238 } -239 -240 pub(super) fn take_error(&mut self) -> Option<ConnectionError> { -241 match &mut self.inner { -242 InnerState::Draining { error, is_local } => { -243 if !*is_local { -244 error.take() -245 } else { -246 None -247 } -248 } -249 InnerState::Drained { error, is_local } => { +236 pub(super) fn is_closed(&self) -> bool { +237 matches!( +238 self.inner, +239 InnerState::Closed { .. } | InnerState::Draining { .. } | InnerState::Drained { .. } +240 ) +241 } +242 +243 pub(super) fn is_drained(&self) -> bool { +244 matches!(self.inner, InnerState::Drained { .. }) +245 } +246 +247 pub(super) fn take_error(&mut self) -> Option<ConnectionError> { +248 match &mut self.inner { +249 InnerState::Draining { error, is_local } => { 250 if !*is_local { 251 error.take() 252 } else { 253 None 254 } 255 } -256 InnerState::Closed { -257 remote_reason, -258 is_local: local_reason, -259 error_read, -260 } => { -261 if *error_read { -262 None -263 } else { -264 *error_read = true; -265 if *local_reason { -266 None -267 } else { -268 Some(remote_reason.clone().into()) -269 } -270 } -271 } -272 InnerState::Handshake(_) | InnerState::Established => None, -273 } -274 } -275 -276 pub(super) fn as_type(&self) -> StateType { -277 match self.inner { -278 InnerState::Handshake(_) => StateType::Handshake, -279 InnerState::Established => StateType::Established, -280 InnerState::Closed { .. } => StateType::Closed, -281 InnerState::Draining { .. } => StateType::Draining, -282 InnerState::Drained { .. } => StateType::Drained, -283 } -284 } -285} -286 -287/// The state a [`Connection`] can be in. -288/// -289/// [`Connection`]: super::Connection -290#[derive(Debug, Clone)] -291pub(super) enum StateType { -292 /// Before the handshake is *confirmed*. -293 Handshake, -294 /// Once the handshake is *confirmed*. -295 Established, -296 /// The connection is closed, waiting for remote to confirm close. -297 /// -298 /// Specifically <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1>. -299 /// -300 /// So the side that initiates an immediate close will stay in this state while it is -301 /// waiting for the remote to also send a CONNECTION_CLOSE. The side that receives a -302 /// connection close will skip straight to [`StateType::Draining`]. -303 Closed, -304 /// The connection is draining, giving time to gracefully discard any in-flight packets. -305 /// -306 /// Specifically <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.2>. -307 /// -308 /// See [`StateType::Closed`] above for more details. -309 Draining, -310 /// The connection is drained, waiting for the application to drop us. -311 /// -312 /// This is a terminal state in which the connection does nothing and can never do -313 /// anything again. Waiting for the application to drop the [`Connection`] struct. +256 InnerState::Drained { error, is_local } => { +257 if !*is_local { +258 error.take() +259 } else { +260 None +261 } +262 } +263 InnerState::Closed { +264 remote_reason, +265 is_local: local_reason, +266 error_read, +267 } => { +268 if *error_read { +269 None +270 } else { +271 *error_read = true; +272 if *local_reason { +273 None +274 } else { +275 Some(remote_reason.clone().into()) +276 } +277 } +278 } +279 InnerState::Handshake(_) | InnerState::Established => None, +280 } +281 } +282 +283 pub(super) fn as_type(&self) -> StateType { +284 match self.inner { +285 InnerState::Handshake(_) => StateType::Handshake, +286 InnerState::Established => StateType::Established, +287 InnerState::Closed { .. } => StateType::Closed, +288 InnerState::Draining { .. } => StateType::Draining, +289 InnerState::Drained { .. } => StateType::Drained, +290 } +291 } +292} +293 +294/// The state a [`Connection`] can be in. +295/// +296/// [`Connection`]: super::Connection +297#[derive(Debug, Clone)] +298pub(super) enum StateType { +299 /// Before the handshake is *confirmed*. +300 Handshake, +301 /// Once the handshake is *confirmed*. +302 Established, +303 /// The connection is closed, waiting for remote to confirm close. +304 /// +305 /// Specifically <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1>. +306 /// +307 /// So the side that initiates an immediate close will stay in this state while it is +308 /// waiting for the remote to also send a CONNECTION_CLOSE. The side that receives a +309 /// connection close will skip straight to [`StateType::Draining`]. +310 Closed, +311 /// The connection is draining, giving time to gracefully discard any in-flight packets. +312 /// +313 /// Specifically <https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.2>. 314 /// -315 /// [`Connection`]: super::Connection -316 Drained, -317} -318 -319#[derive(Debug, Clone)] -320pub(super) enum CloseReason { -321 TransportError(TransportError), -322 Connection(ConnectionClose), -323 Application(ApplicationClose), +315 /// See [`StateType::Closed`] above for more details. +316 Draining, +317 /// The connection is drained, waiting for the application to drop us. +318 /// +319 /// This is a terminal state in which the connection does nothing and can never do +320 /// anything again. Waiting for the application to drop the [`Connection`] struct. +321 /// +322 /// [`Connection`]: super::Connection +323 Drained, 324} 325 -326impl From<TransportError> for CloseReason { -327 fn from(x: TransportError) -> Self { -328 Self::TransportError(x) -329 } -330} -331impl From<ConnectionClose> for CloseReason { -332 fn from(x: ConnectionClose) -> Self { -333 Self::Connection(x) -334 } -335} -336impl From<ApplicationClose> for CloseReason { -337 fn from(x: ApplicationClose) -> Self { -338 Self::Application(x) -339 } -340} -341 -342impl From<Close> for CloseReason { -343 fn from(value: Close) -> Self { -344 match value { -345 Close::Application(reason) => Self::Application(reason), -346 Close::Connection(reason) => Self::Connection(reason), -347 } -348 } -349} -350 -351impl From<CloseReason> for ConnectionError { -352 fn from(value: CloseReason) -> Self { -353 match value { -354 CloseReason::TransportError(err) => Self::TransportError(err), -355 CloseReason::Connection(reason) => Self::ConnectionClosed(reason), -356 CloseReason::Application(reason) => Self::ApplicationClosed(reason), -357 } -358 } -359} -360 -361impl From<CloseReason> for Close { -362 fn from(value: CloseReason) -> Self { -363 match value { -364 CloseReason::TransportError(err) => Self::Connection(err.into()), -365 CloseReason::Connection(reason) => Self::Connection(reason), -366 CloseReason::Application(reason) => Self::Application(reason), -367 } -368 } -369} -370 -371#[derive(Debug, Clone)] -372enum InnerState { -373 /// See [`StateType::Handshake`]. -374 Handshake(Handshake), -375 /// See [`StateType::Established`]. -376 Established, -377 /// See [`StateType::Closed`]. -378 Closed { -379 /// The reason the remote closed the connection, or the reason we are sending to the remote. -380 remote_reason: CloseReason, -381 /// Set to true if we closed the connection locally. -382 is_local: bool, -383 /// Did we read this as error already? -384 error_read: bool, -385 }, -386 /// See [`StateType::Draining`]. -387 Draining { -388 /// Why the connection was lost, if it has been. -389 error: Option<ConnectionError>, -390 /// Set to true if we closed the connection locally. -391 is_local: bool, +326#[derive(Debug, Clone)] +327pub(super) enum CloseReason { +328 TransportError(TransportError), +329 Connection(ConnectionClose), +330 Application(ApplicationClose), +331} +332 +333impl From<TransportError> for CloseReason { +334 fn from(x: TransportError) -> Self { +335 Self::TransportError(x) +336 } +337} +338impl From<ConnectionClose> for CloseReason { +339 fn from(x: ConnectionClose) -> Self { +340 Self::Connection(x) +341 } +342} +343impl From<ApplicationClose> for CloseReason { +344 fn from(x: ApplicationClose) -> Self { +345 Self::Application(x) +346 } +347} +348 +349impl From<Close> for CloseReason { +350 fn from(value: Close) -> Self { +351 match value { +352 Close::Application(reason) => Self::Application(reason), +353 Close::Connection(reason) => Self::Connection(reason), +354 } +355 } +356} +357 +358impl From<CloseReason> for ConnectionError { +359 fn from(value: CloseReason) -> Self { +360 match value { +361 CloseReason::TransportError(err) => Self::TransportError(err), +362 CloseReason::Connection(reason) => Self::ConnectionClosed(reason), +363 CloseReason::Application(reason) => Self::ApplicationClosed(reason), +364 } +365 } +366} +367 +368impl From<CloseReason> for Close { +369 fn from(value: CloseReason) -> Self { +370 match value { +371 CloseReason::TransportError(err) => Self::Connection(err.into()), +372 CloseReason::Connection(reason) => Self::Connection(reason), +373 CloseReason::Application(reason) => Self::Application(reason), +374 } +375 } +376} +377 +378#[derive(Debug, Clone)] +379enum InnerState { +380 /// See [`StateType::Handshake`]. +381 Handshake(Handshake), +382 /// See [`StateType::Established`]. +383 Established, +384 /// See [`StateType::Closed`]. +385 Closed { +386 /// The reason the remote closed the connection, or the reason we are sending to the remote. +387 remote_reason: CloseReason, +388 /// Set to true if we closed the connection locally. +389 is_local: bool, +390 /// Did we read this as error already? +391 error_read: bool, 392 }, -393 /// See [`StateType::Drained`]. -394 /// Waiting for application to call close so we can dispose of the resources. -395 Drained { -396 /// Why the connection was lost, if it has been. -397 error: Option<ConnectionError>, -398 /// Set to true if we closed the connection locally. -399 is_local: bool, -400 }, -401} -402 -403#[allow(unreachable_pub)] // fuzzing only -404#[derive(Debug, Clone)] -405pub struct Handshake { -406 /// Whether the remote CID has been set by the peer yet. -407 /// -408 /// Always set for servers. -409 pub(super) remote_cid_set: bool, -410 /// Stateless retry token received in the first Initial by a server. -411 /// -412 /// Must be present in every Initial. Always empty for clients. -413 pub(super) expected_token: Bytes, -414 /// First cryptographic message. -415 /// -416 /// Only set for clients. -417 pub(super) client_hello: Option<Bytes>, -418 /// Whether the server address is allowed to migrate. -419 /// -420 /// We allow the server to migrate during the handshake as long as we have not -421 /// received an authenticated handshake packet: it can send a response from a -422 /// different address than we sent the initial to. This allows us to send the -423 /// initial packet over multiple paths - by means of an IPv6 ULA address that copies -424 /// the packets sent to it to multiple destinations - and accept one response. -425 /// -426 /// This is only ever set to true if for a client which hasn't yet received an -427 /// authenticated handshake packet. It is set back to false in -428 /// [`super::Connection::on_packet_authenticated`]. -429 /// -430 /// THIS IS NOT RFC 9000 COMPLIANT! A server is not allowed to migrate addresses, -431 /// other than using the preferred-address transport parameter. -432 pub(super) allow_server_migration: bool, -433}
\ No newline at end of file +393 /// See [`StateType::Draining`]. +394 Draining { +395 /// Why the connection was lost, if it has been. +396 error: Option<ConnectionError>, +397 /// Set to true if we closed the connection locally. +398 is_local: bool, +399 }, +400 /// See [`StateType::Drained`]. +401 /// Waiting for application to call close so we can dispose of the resources. +402 Drained { +403 /// Why the connection was lost, if it has been. +404 error: Option<ConnectionError>, +405 /// Set to true if we closed the connection locally. +406 is_local: bool, +407 }, +408} +409 +410#[allow(unreachable_pub)] // fuzzing only +411#[derive(Debug, Clone)] +412pub struct Handshake { +413 /// Whether the remote CID has been set by the peer yet. +414 /// +415 /// Always set for servers. +416 pub(super) remote_cid_set: bool, +417 /// Stateless retry token received in the first Initial by a server. +418 /// +419 /// Must be present in every Initial. Always empty for clients. +420 pub(super) expected_token: Bytes, +421 /// First cryptographic message. +422 /// +423 /// Only set for clients. +424 pub(super) client_hello: Option<Bytes>, +425 /// Whether the server address is allowed to migrate. +426 /// +427 /// We allow the server to migrate during the handshake as long as we have not +428 /// received an authenticated handshake packet: it can send a response from a +429 /// different address than we sent the initial to. This allows us to send the +430 /// initial packet over multiple paths - by means of an IPv6 ULA address that copies +431 /// the packets sent to it to multiple destinations - and accept one response. +432 /// +433 /// This is only ever set to true if for a client which hasn't yet received an +434 /// authenticated handshake packet. It is set back to false in +435 /// [`super::Connection::on_packet_authenticated`]. +436 /// +437 /// THIS IS NOT RFC 9000 COMPLIANT! A server is not allowed to migrate addresses, +438 /// other than using the preferred-address transport parameter. +439 pub(super) allow_server_migration: bool, +440}

\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/stats.rs.html b/pr/451/docs/src/noq_proto/connection/stats.rs.html index 365b026db..f229556a5 100644 --- a/pr/451/docs/src/noq_proto/connection/stats.rs.html +++ b/pr/451/docs/src/noq_proto/connection/stats.rs.html @@ -20,348 +20,363 @@ 20 pub bytes: u64, 21 /// The number of I/O operations executed. 22 /// -23 /// Can be less than `datagrams` when GSO, GRO, and/or batched system calls are in use. -24 pub ios: u64, -25} -26 -27impl UdpStats { -28 pub(crate) fn on_sent(&mut self, datagrams: u64, bytes: usize) { -29 self.datagrams += datagrams; -30 self.bytes += bytes as u64; -31 self.ios += 1; -32 } -33} -34 -35/// Number of frames transmitted or received of each frame type. -36#[derive(Default, Copy, Clone, PartialEq, Eq, derive_more::Add, derive_more::AddAssign)] -37#[non_exhaustive] -38#[allow(missing_docs)] -39pub struct FrameStats { -40 pub acks: u64, -41 pub path_acks: u64, -42 pub ack_frequency: u64, -43 pub crypto: u64, -44 pub connection_close: u64, -45 pub data_blocked: u64, -46 pub datagram: u64, -47 pub handshake_done: u8, -48 pub immediate_ack: u64, -49 pub max_data: u64, -50 pub max_stream_data: u64, -51 pub max_streams_bidi: u64, -52 pub max_streams_uni: u64, -53 pub new_connection_id: u64, -54 pub path_new_connection_id: u64, -55 pub new_token: u64, -56 pub path_challenge: u64, -57 pub path_response: u64, -58 pub ping: u64, -59 pub reset_stream: u64, -60 pub retire_connection_id: u64, -61 pub path_retire_connection_id: u64, -62 pub stream_data_blocked: u64, -63 pub streams_blocked_bidi: u64, -64 pub streams_blocked_uni: u64, -65 pub stop_sending: u64, -66 pub stream: u64, -67 pub observed_addr: u64, -68 pub path_abandon: u64, -69 pub path_status_available: u64, -70 pub path_status_backup: u64, -71 pub max_path_id: u64, -72 pub paths_blocked: u64, -73 pub path_cids_blocked: u64, -74 pub add_address: u64, -75 pub reach_out: u64, -76 pub remove_address: u64, -77} -78 -79impl FrameStats { -80 pub(crate) fn record(&mut self, frame_type: FrameType) { -81 use FrameType::*; -82 // Increments the field. Added for readability -83 macro_rules! inc { -84 ($field_name: ident) => {{ self.$field_name = self.$field_name.saturating_add(1) }}; -85 } -86 match frame_type { -87 Padding => {} -88 Ping => inc!(ping), -89 Ack | AckEcn => inc!(acks), -90 PathAck | PathAckEcn => inc!(path_acks), -91 ResetStream => inc!(reset_stream), -92 StopSending => inc!(stop_sending), -93 Crypto => inc!(crypto), -94 Datagram(_) => inc!(datagram), -95 NewToken => inc!(new_token), -96 MaxData => inc!(max_data), -97 MaxStreamData => inc!(max_stream_data), -98 MaxStreamsBidi => inc!(max_streams_bidi), -99 MaxStreamsUni => inc!(max_streams_uni), -100 DataBlocked => inc!(data_blocked), -101 Stream(_) => inc!(stream), -102 StreamDataBlocked => inc!(stream_data_blocked), -103 StreamsBlockedUni => inc!(streams_blocked_uni), -104 StreamsBlockedBidi => inc!(streams_blocked_bidi), -105 NewConnectionId => inc!(new_connection_id), -106 PathNewConnectionId => inc!(path_new_connection_id), -107 RetireConnectionId => inc!(retire_connection_id), -108 PathRetireConnectionId => inc!(path_retire_connection_id), -109 PathChallenge => inc!(path_challenge), -110 PathResponse => inc!(path_response), -111 ConnectionClose | ApplicationClose => inc!(connection_close), -112 AckFrequency => inc!(ack_frequency), -113 ImmediateAck => inc!(immediate_ack), -114 HandshakeDone => inc!(handshake_done), -115 ObservedIpv4Addr | ObservedIpv6Addr => inc!(observed_addr), -116 PathAbandon => inc!(path_abandon), -117 PathStatusAvailable => inc!(path_status_available), -118 PathStatusBackup => inc!(path_status_backup), -119 MaxPathId => inc!(max_path_id), -120 PathsBlocked => inc!(paths_blocked), -121 PathCidsBlocked => inc!(path_cids_blocked), -122 AddIpv4Address | AddIpv6Address => inc!(add_address), -123 ReachOutAtIpv4 | ReachOutAtIpv6 => inc!(reach_out), -124 RemoveAddress => inc!(remove_address), -125 }; -126 } -127} -128 -129impl std::fmt::Debug for FrameStats { -130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -131 let Self { -132 acks, -133 path_acks, -134 ack_frequency, -135 crypto, -136 connection_close, -137 data_blocked, -138 datagram, -139 handshake_done, -140 immediate_ack, -141 max_data, -142 max_stream_data, -143 max_streams_bidi, -144 max_streams_uni, -145 new_connection_id, -146 path_new_connection_id, -147 new_token, -148 path_challenge, -149 path_response, -150 ping, -151 reset_stream, -152 retire_connection_id, -153 path_retire_connection_id, -154 stream_data_blocked, -155 streams_blocked_bidi, -156 streams_blocked_uni, -157 stop_sending, -158 stream, -159 observed_addr, -160 path_abandon, -161 path_status_available, -162 path_status_backup, -163 max_path_id, -164 paths_blocked, -165 path_cids_blocked, -166 add_address, -167 reach_out, -168 remove_address, -169 } = self; -170 f.debug_struct("FrameStats") -171 .field("ACK", acks) -172 .field("ACK_FREQUENCY", ack_frequency) -173 .field("CONNECTION_CLOSE", connection_close) -174 .field("CRYPTO", crypto) -175 .field("DATA_BLOCKED", data_blocked) -176 .field("DATAGRAM", datagram) -177 .field("HANDSHAKE_DONE", handshake_done) -178 .field("IMMEDIATE_ACK", immediate_ack) -179 .field("MAX_DATA", max_data) -180 .field("MAX_PATH_ID", max_path_id) -181 .field("MAX_STREAM_DATA", max_stream_data) -182 .field("MAX_STREAMS_BIDI", max_streams_bidi) -183 .field("MAX_STREAMS_UNI", max_streams_uni) -184 .field("NEW_CONNECTION_ID", new_connection_id) -185 .field("NEW_TOKEN", new_token) -186 .field("PATHS_BLOCKED", paths_blocked) -187 .field("PATH_ABANDON", path_abandon) -188 .field("PATH_ACK", path_acks) -189 .field("PATH_STATUS_AVAILABLE", path_status_available) -190 .field("PATH_STATUS_BACKUP", path_status_backup) -191 .field("PATH_CHALLENGE", path_challenge) -192 .field("PATH_CIDS_BLOCKED", path_cids_blocked) -193 .field("PATH_NEW_CONNECTION_ID", path_new_connection_id) -194 .field("PATH_RESPONSE", path_response) -195 .field("PATH_RETIRE_CONNECTION_ID", path_retire_connection_id) -196 .field("PING", ping) -197 .field("RESET_STREAM", reset_stream) -198 .field("RETIRE_CONNECTION_ID", retire_connection_id) -199 .field("STREAM_DATA_BLOCKED", stream_data_blocked) -200 .field("STREAMS_BLOCKED_BIDI", streams_blocked_bidi) -201 .field("STREAMS_BLOCKED_UNI", streams_blocked_uni) -202 .field("STOP_SENDING", stop_sending) -203 .field("STREAM", stream) -204 .field("OBSERVED_ADDRESS", observed_addr) -205 .field("ADD_ADDRESS", add_address) -206 .field("REACH_OUT", reach_out) -207 .field("REMOVE_ADDRESS", remove_address) -208 .finish() -209 } -210} -211 -212/// Statistics related to a transmission path. -213#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] -214#[non_exhaustive] -215pub struct PathStats { -216 /// Current best estimate of this connection's latency (round-trip-time). -217 pub rtt: Duration, -218 /// Statistics about datagrams and bytes sent on this path. -219 pub udp_tx: UdpStats, -220 /// Statistics about datagrams and bytes received on this path. -221 pub udp_rx: UdpStats, -222 /// Statistics about frames transmitted on this path. -223 pub frame_tx: FrameStats, -224 /// Statistics about frames received on this path. -225 pub frame_rx: FrameStats, -226 /// Current congestion window of the connection. -227 pub cwnd: u64, -228 /// Congestion events on the connection. -229 pub congestion_events: u64, -230 /// Spurious congestion events on the connection. -231 pub spurious_congestion_events: u64, -232 /// The number of packets lost on this path. -233 pub lost_packets: u64, -234 /// The number of bytes lost on this path. -235 pub lost_bytes: u64, -236 /// The number of PLPMTUD probe packets sent on this path. -237 /// -238 /// These are also counted by [`UdpStats::datagrams`]. -239 pub sent_plpmtud_probes: u64, -240 /// The number of PLPMTUD probe packets lost on this path. -241 /// -242 /// These are not included in [`Self::lost_packets`] and [`Self::lost_bytes`]. -243 pub lost_plpmtud_probes: u64, -244 /// The number of times a black hole was detected in the path. -245 pub black_holes_detected: u64, -246 /// Largest UDP payload size the path currently supports. -247 pub current_mtu: u16, -248} -249 -250/// Connection statistics. -251/// -252/// The fields here are a sum of the respective fields in the [`PathStats`] for all the -253/// paths that exist as well as all paths that previously existed. -254#[derive(Debug, Default, Clone)] -255#[non_exhaustive] -256pub struct ConnectionStats { -257 /// Statistics about UDP datagrams transmitted on the connection. -258 pub udp_tx: UdpStats, -259 /// Statistics about UDP datagrams received on the connection. -260 pub udp_rx: UdpStats, -261 /// Statistics about frames transmitted on the connection. -262 pub frame_tx: FrameStats, -263 /// Statistics about frames received on the connection. -264 pub frame_rx: FrameStats, -265 /// The number of packets lost on the connection. -266 pub lost_packets: u64, -267 /// The number of bytes lost on the connection. -268 pub lost_bytes: u64, -269} -270 -271impl std::ops::Add<PathStats> for ConnectionStats { -272 type Output = Self; -273 -274 fn add(self, rhs: PathStats) -> Self::Output { -275 // Be aware that Connection::stats() relies on the fact this function ignores the -276 // rtt, cwnd and current_mtu fields. -277 let PathStats { -278 rtt: _, -279 udp_tx, -280 udp_rx, -281 frame_tx, -282 frame_rx, -283 cwnd: _, -284 congestion_events: _, -285 spurious_congestion_events: _, -286 lost_packets, -287 lost_bytes, -288 sent_plpmtud_probes: _, -289 lost_plpmtud_probes: _, -290 black_holes_detected: _, -291 current_mtu: _, -292 } = rhs; -293 Self { -294 udp_tx: self.udp_tx + udp_tx, -295 udp_rx: self.udp_rx + udp_rx, -296 frame_tx: self.frame_tx + frame_tx, -297 frame_rx: self.frame_rx + frame_rx, -298 lost_packets: self.lost_packets + lost_packets, -299 lost_bytes: self.lost_bytes + lost_bytes, -300 } -301 } -302} -303 -304impl std::ops::AddAssign<PathStats> for ConnectionStats { -305 fn add_assign(&mut self, rhs: PathStats) { -306 // Be aware that Connection::stats() relies on the fact this function ignores the -307 // rtt, cwnd and current_mtu fields. -308 let PathStats { -309 rtt: _, -310 udp_tx: path_udp_tx, -311 udp_rx: path_udp_rx, -312 frame_tx: path_frame_tx, -313 frame_rx: path_frame_rx, -314 cwnd: _, -315 congestion_events: _, -316 spurious_congestion_events: _, -317 lost_packets: path_lost_packets, -318 lost_bytes: path_lost_bytes, -319 sent_plpmtud_probes: _, -320 lost_plpmtud_probes: _, -321 black_holes_detected: _, -322 current_mtu: _, -323 } = rhs; -324 let Self { -325 udp_tx, -326 udp_rx, -327 frame_tx, -328 frame_rx, -329 lost_packets, -330 lost_bytes, -331 } = self; -332 *udp_tx += path_udp_tx; -333 *udp_rx += path_udp_rx; -334 *frame_tx += path_frame_tx; -335 *frame_rx += path_frame_rx; -336 *lost_packets += path_lost_packets; -337 *lost_bytes += path_lost_bytes; -338 } -339} -340 -341/// Helper to make [`PathStats`] infallibly available. -342/// -343/// This helper also helps with borrowing issues compared to having the [`Self::for_path`] -344/// function as a helper directly on [`Connection`]. -345/// -346/// [`Connection`]: super::Connection -347#[derive(Debug, Default)] -348pub(super) struct PathStatsMap(FxHashMap<PathId, PathStats>); -349 -350impl PathStatsMap { -351 /// Returns the [`PathStats`] for the path. -352 pub(super) fn for_path(&mut self, path_id: PathId) -> &mut PathStats { -353 self.0.entry(path_id).or_default() -354 } -355 -356 /// An iterator over all contained [`PathStats`]. -357 pub(super) fn iter_stats(&self) -> impl Iterator<Item = &PathStats> { -358 self.0.values() -359 } +23 /// This can't be measured from this crate and will always be 0 +24 #[deprecated( +25 since = "1.0.2", +26 note = "IO counting can't be meaningfully measured from this crate. See <https://github.com/n0-computer/noq/issues/727>" +27 )] +28 pub ios: u64, +29} +30 +31impl UdpStats { +32 pub(crate) fn on_sent(&mut self, datagrams: u64, bytes: usize) { +33 self.datagrams += datagrams; +34 self.bytes += bytes as u64; +35 } +36} +37 +38/// Number of frames transmitted or received of each frame type. +39#[derive(Default, Copy, Clone, PartialEq, Eq, derive_more::Add, derive_more::AddAssign)] +40#[non_exhaustive] +41#[allow(missing_docs)] +42pub struct FrameStats { +43 pub acks: u64, +44 pub path_acks: u64, +45 pub ack_frequency: u64, +46 pub crypto: u64, +47 pub connection_close: u64, +48 pub data_blocked: u64, +49 pub datagram: u64, +50 pub handshake_done: u8, +51 pub immediate_ack: u64, +52 pub max_data: u64, +53 pub max_stream_data: u64, +54 pub max_streams_bidi: u64, +55 pub max_streams_uni: u64, +56 pub new_connection_id: u64, +57 pub path_new_connection_id: u64, +58 pub new_token: u64, +59 pub path_challenge: u64, +60 pub path_response: u64, +61 pub ping: u64, +62 pub reset_stream: u64, +63 pub retire_connection_id: u64, +64 pub path_retire_connection_id: u64, +65 pub stream_data_blocked: u64, +66 pub streams_blocked_bidi: u64, +67 pub streams_blocked_uni: u64, +68 pub stop_sending: u64, +69 pub stream: u64, +70 pub observed_addr: u64, +71 pub path_abandon: u64, +72 pub path_status_available: u64, +73 pub path_status_backup: u64, +74 pub max_path_id: u64, +75 pub paths_blocked: u64, +76 pub path_cids_blocked: u64, +77 pub add_address: u64, +78 pub reach_out: u64, +79 pub remove_address: u64, +80} +81 +82impl FrameStats { +83 pub(crate) fn record(&mut self, frame_type: FrameType) { +84 use FrameType::*; +85 // Increments the field. Added for readability +86 macro_rules! inc { +87 ($field_name: ident) => {{ self.$field_name = self.$field_name.saturating_add(1) }}; +88 } +89 match frame_type { +90 Padding => {} +91 Ping => inc!(ping), +92 Ack | AckEcn => inc!(acks), +93 PathAck | PathAckEcn => inc!(path_acks), +94 ResetStream => inc!(reset_stream), +95 StopSending => inc!(stop_sending), +96 Crypto => inc!(crypto), +97 Datagram(_) => inc!(datagram), +98 NewToken => inc!(new_token), +99 MaxData => inc!(max_data), +100 MaxStreamData => inc!(max_stream_data), +101 MaxStreamsBidi => inc!(max_streams_bidi), +102 MaxStreamsUni => inc!(max_streams_uni), +103 DataBlocked => inc!(data_blocked), +104 Stream(_) => inc!(stream), +105 StreamDataBlocked => inc!(stream_data_blocked), +106 StreamsBlockedUni => inc!(streams_blocked_uni), +107 StreamsBlockedBidi => inc!(streams_blocked_bidi), +108 NewConnectionId => inc!(new_connection_id), +109 PathNewConnectionId => inc!(path_new_connection_id), +110 RetireConnectionId => inc!(retire_connection_id), +111 PathRetireConnectionId => inc!(path_retire_connection_id), +112 PathChallenge => inc!(path_challenge), +113 PathResponse => inc!(path_response), +114 ConnectionClose | ApplicationClose => inc!(connection_close), +115 AckFrequency => inc!(ack_frequency), +116 ImmediateAck => inc!(immediate_ack), +117 HandshakeDone => inc!(handshake_done), +118 ObservedIpv4Addr | ObservedIpv6Addr => inc!(observed_addr), +119 PathAbandon => inc!(path_abandon), +120 PathStatusAvailable => inc!(path_status_available), +121 PathStatusBackup => inc!(path_status_backup), +122 MaxPathId => inc!(max_path_id), +123 PathsBlocked => inc!(paths_blocked), +124 PathCidsBlocked => inc!(path_cids_blocked), +125 AddIpv4Address | AddIpv6Address => inc!(add_address), +126 ReachOutAtIpv4 | ReachOutAtIpv6 => inc!(reach_out), +127 RemoveAddress => inc!(remove_address), +128 }; +129 } +130} +131 +132impl std::fmt::Debug for FrameStats { +133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +134 let Self { +135 acks, +136 path_acks, +137 ack_frequency, +138 crypto, +139 connection_close, +140 data_blocked, +141 datagram, +142 handshake_done, +143 immediate_ack, +144 max_data, +145 max_stream_data, +146 max_streams_bidi, +147 max_streams_uni, +148 new_connection_id, +149 path_new_connection_id, +150 new_token, +151 path_challenge, +152 path_response, +153 ping, +154 reset_stream, +155 retire_connection_id, +156 path_retire_connection_id, +157 stream_data_blocked, +158 streams_blocked_bidi, +159 streams_blocked_uni, +160 stop_sending, +161 stream, +162 observed_addr, +163 path_abandon, +164 path_status_available, +165 path_status_backup, +166 max_path_id, +167 paths_blocked, +168 path_cids_blocked, +169 add_address, +170 reach_out, +171 remove_address, +172 } = self; +173 f.debug_struct("FrameStats") +174 .field("ACK", acks) +175 .field("ACK_FREQUENCY", ack_frequency) +176 .field("CONNECTION_CLOSE", connection_close) +177 .field("CRYPTO", crypto) +178 .field("DATA_BLOCKED", data_blocked) +179 .field("DATAGRAM", datagram) +180 .field("HANDSHAKE_DONE", handshake_done) +181 .field("IMMEDIATE_ACK", immediate_ack) +182 .field("MAX_DATA", max_data) +183 .field("MAX_PATH_ID", max_path_id) +184 .field("MAX_STREAM_DATA", max_stream_data) +185 .field("MAX_STREAMS_BIDI", max_streams_bidi) +186 .field("MAX_STREAMS_UNI", max_streams_uni) +187 .field("NEW_CONNECTION_ID", new_connection_id) +188 .field("NEW_TOKEN", new_token) +189 .field("PATHS_BLOCKED", paths_blocked) +190 .field("PATH_ABANDON", path_abandon) +191 .field("PATH_ACK", path_acks) +192 .field("PATH_STATUS_AVAILABLE", path_status_available) +193 .field("PATH_STATUS_BACKUP", path_status_backup) +194 .field("PATH_CHALLENGE", path_challenge) +195 .field("PATH_CIDS_BLOCKED", path_cids_blocked) +196 .field("PATH_NEW_CONNECTION_ID", path_new_connection_id) +197 .field("PATH_RESPONSE", path_response) +198 .field("PATH_RETIRE_CONNECTION_ID", path_retire_connection_id) +199 .field("PING", ping) +200 .field("RESET_STREAM", reset_stream) +201 .field("RETIRE_CONNECTION_ID", retire_connection_id) +202 .field("STREAM_DATA_BLOCKED", stream_data_blocked) +203 .field("STREAMS_BLOCKED_BIDI", streams_blocked_bidi) +204 .field("STREAMS_BLOCKED_UNI", streams_blocked_uni) +205 .field("STOP_SENDING", stop_sending) +206 .field("STREAM", stream) +207 .field("OBSERVED_ADDRESS", observed_addr) +208 .field("ADD_ADDRESS", add_address) +209 .field("REACH_OUT", reach_out) +210 .field("REMOVE_ADDRESS", remove_address) +211 .finish() +212 } +213} +214 +215/// Statistics related to a transmission path. +216#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +217#[non_exhaustive] +218pub struct PathStats { +219 /// Current best estimate of this connection's latency (round-trip-time). +220 pub rtt: Duration, +221 /// Statistics about datagrams and bytes sent on this path. +222 pub udp_tx: UdpStats, +223 /// Statistics about datagrams and bytes received on this path. +224 pub udp_rx: UdpStats, +225 /// Statistics about frames transmitted on this path. +226 pub frame_tx: FrameStats, +227 /// Statistics about frames received on this path. +228 pub frame_rx: FrameStats, +229 /// Current congestion window of the connection. +230 pub cwnd: u64, +231 /// Congestion events on the connection. +232 pub congestion_events: u64, +233 /// Spurious congestion events on the connection. +234 pub spurious_congestion_events: u64, +235 /// The number of packets lost on this path. +236 pub lost_packets: u64, +237 /// The number of bytes lost on this path. +238 pub lost_bytes: u64, +239 /// The number of PLPMTUD probe packets sent on this path. +240 /// +241 /// These are also counted by [`UdpStats::datagrams`]. +242 pub sent_plpmtud_probes: u64, +243 /// The number of PLPMTUD probe packets lost on this path. +244 /// +245 /// These are not included in [`Self::lost_packets`] and [`Self::lost_bytes`]. +246 pub lost_plpmtud_probes: u64, +247 /// The number of times a black hole was detected in the path. +248 pub black_holes_detected: u64, +249 /// Largest UDP payload size the path currently supports. +250 pub current_mtu: u16, +251} +252 +253/// Connection statistics. +254/// +255/// The fields here are a sum of the respective fields in the [`PathStats`] for all the +256/// paths that exist as well as all paths that previously existed. +257#[derive(Debug, Default, Clone)] +258#[non_exhaustive] +259pub struct ConnectionStats { +260 /// Statistics about UDP datagrams transmitted on the connection. +261 pub udp_tx: UdpStats, +262 /// Statistics about UDP datagrams received on the connection. +263 pub udp_rx: UdpStats, +264 /// Statistics about frames transmitted on the connection. +265 pub frame_tx: FrameStats, +266 /// Statistics about frames received on the connection. +267 pub frame_rx: FrameStats, +268 /// The number of packets lost on the connection. +269 pub lost_packets: u64, +270 /// The number of bytes lost on the connection. +271 pub lost_bytes: u64, +272 +273 /// Number of [`super::Transmit`] produced by this connection. +274 #[cfg(test)] +275 pub(crate) transmits_tx: u64, +276} +277 +278impl std::ops::Add<PathStats> for ConnectionStats { +279 type Output = Self; +280 +281 fn add(self, rhs: PathStats) -> Self::Output { +282 // Be aware that Connection::stats() relies on the fact this function ignores the +283 // rtt, cwnd and current_mtu fields. +284 let PathStats { +285 rtt: _, +286 udp_tx, +287 udp_rx, +288 frame_tx, +289 frame_rx, +290 cwnd: _, +291 congestion_events: _, +292 spurious_congestion_events: _, +293 lost_packets, +294 lost_bytes, +295 sent_plpmtud_probes: _, +296 lost_plpmtud_probes: _, +297 black_holes_detected: _, +298 current_mtu: _, +299 } = rhs; +300 Self { +301 udp_tx: self.udp_tx + udp_tx, +302 udp_rx: self.udp_rx + udp_rx, +303 frame_tx: self.frame_tx + frame_tx, +304 frame_rx: self.frame_rx + frame_rx, +305 lost_packets: self.lost_packets + lost_packets, +306 lost_bytes: self.lost_bytes + lost_bytes, +307 #[cfg(test)] +308 transmits_tx: self.transmits_tx, +309 } +310 } +311} +312 +313impl std::ops::AddAssign<PathStats> for ConnectionStats { +314 fn add_assign(&mut self, rhs: PathStats) { +315 // Be aware that Connection::stats() relies on the fact this function ignores the +316 // rtt, cwnd and current_mtu fields. +317 let PathStats { +318 rtt: _, +319 udp_tx: path_udp_tx, +320 udp_rx: path_udp_rx, +321 frame_tx: path_frame_tx, +322 frame_rx: path_frame_rx, +323 cwnd: _, +324 congestion_events: _, +325 spurious_congestion_events: _, +326 lost_packets: path_lost_packets, +327 lost_bytes: path_lost_bytes, +328 sent_plpmtud_probes: _, +329 lost_plpmtud_probes: _, +330 black_holes_detected: _, +331 current_mtu: _, +332 } = rhs; +333 let Self { +334 udp_tx, +335 udp_rx, +336 frame_tx, +337 frame_rx, +338 lost_packets, +339 lost_bytes, +340 #[cfg(test)] +341 transmits_tx: _, +342 } = self; +343 *udp_tx += path_udp_tx; +344 *udp_rx += path_udp_rx; +345 *frame_tx += path_frame_tx; +346 *frame_rx += path_frame_rx; +347 *lost_packets += path_lost_packets; +348 *lost_bytes += path_lost_bytes; +349 } +350} +351 +352/// Helper to make [`PathStats`] infallibly available. +353/// +354/// This helper also helps with borrowing issues compared to having the [`Self::get_mut`] +355/// function as a helper directly on [`Connection`]. +356/// +357/// [`Connection`]: super::Connection +358#[derive(Debug, Default)] +359pub(super) struct PathStatsMap(FxHashMap<PathId, PathStats>); 360 -361 /// Removes the stats for a given path. -362 /// -363 /// Only do this once you are discarding the path. -364 pub(super) fn discard(&mut self, path_id: &PathId) -> PathStats { -365 self.0.remove(path_id).unwrap_or_default() -366 } -367}
\ No newline at end of file +361impl PathStatsMap { +362 /// Returns the [`PathStats`] for the path. +363 pub(super) fn get_mut(&mut self, path_id: PathId) -> &mut PathStats { +364 self.0.entry(path_id).or_default() +365 } +366 +367 pub(super) fn get(&self, path_id: PathId) -> Option<PathStats> { +368 self.0.get(&path_id).copied() +369 } +370 +371 /// An iterator over all contained [`PathStats`]. +372 pub(super) fn iter_stats(&self) -> impl Iterator<Item = &PathStats> { +373 self.0.values() +374 } +375 +376 /// Removes the stats for a given path. +377 /// +378 /// Only do this once you are discarding the path. +379 pub(super) fn discard(&mut self, path_id: &PathId) { +380 self.0.remove(path_id); +381 } +382}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/streams/mod.rs.html b/pr/451/docs/src/noq_proto/connection/streams/mod.rs.html index d24e9083d..acd731b17 100644 --- a/pr/451/docs/src/noq_proto/connection/streams/mod.rs.html +++ b/pr/451/docs/src/noq_proto/connection/streams/mod.rs.html @@ -19,555 +19,554 @@ 19pub use recv::{Chunks, ReadError, ReadableError}; 20 21mod send; -22pub(crate) use send::{ByteSlice, BytesArray}; +22pub(crate) use send::{ByteSlice, BytesArray, Written}; 23use send::{BytesSource, Send, SendState}; 24pub use send::{FinishError, WriteError}; -25pub(crate) use send::Written; -26 -27mod state; -28#[allow(unreachable_pub)] // fuzzing only -29pub use state::StreamsState; -30 -31/// Access to streams -32pub struct Streams<'a> { -33 pub(super) state: &'a mut StreamsState, -34 pub(super) conn_state: &'a super::State, -35} -36 -37#[allow(clippy::needless_lifetimes)] // Needed for cfg(fuzzing) -38impl<'a> Streams<'a> { -39 #[cfg(fuzzing)] -40 pub fn new(state: &'a mut StreamsState, conn_state: &'a super::State) -> Self { -41 Self { state, conn_state } -42 } -43 -44 /// Open a single stream if possible -45 /// -46 /// Returns `None` if the streams in the given direction are currently exhausted. -47 pub fn open(&mut self, dir: Dir) -> Option<StreamId> { -48 if self.conn_state.is_closed() { -49 return None; -50 } -51 -52 if self.state.next[dir as usize] >= self.state.max[dir as usize] { -53 self.state.streams_blocked[dir as usize] = true; -54 return None; -55 } -56 -57 self.state.next[dir as usize] += 1; -58 let id = StreamId::new(self.state.side, dir, self.state.next[dir as usize] - 1); -59 self.state.insert(false, id); -60 self.state.send_streams += 1; -61 Some(id) -62 } -63 -64 /// Accept a remotely initiated stream of a certain directionality, if possible -65 /// -66 /// Returns `None` if there are no new incoming streams for this connection. -67 /// Has no impact on the data flow-control or stream concurrency limits. -68 pub fn accept(&mut self, dir: Dir) -> Option<StreamId> { -69 if self.state.next_remote[dir as usize] == self.state.next_reported_remote[dir as usize] { -70 return None; -71 } -72 -73 let x = self.state.next_reported_remote[dir as usize]; -74 self.state.next_reported_remote[dir as usize] = x + 1; -75 if dir == Dir::Bi { -76 self.state.send_streams += 1; -77 } -78 -79 Some(StreamId::new(!self.state.side, dir, x)) -80 } -81 -82 #[cfg(fuzzing)] -83 pub fn state(&mut self) -> &mut StreamsState { -84 self.state -85 } -86 -87 /// The number of streams that may have unacknowledged data. -88 pub fn send_streams(&self) -> usize { -89 self.state.send_streams -90 } -91 -92 /// The number of remotely initiated open streams of a certain directionality. -93 /// -94 /// Includes remotely initiated streams, which have not been accepted via [`accept`](Self::accept). -95 /// These streams count against the respective concurrency limit reported by -96 /// [`Connection::max_concurrent_streams`](super::Connection::max_concurrent_streams). -97 pub fn remote_open_streams(&self, dir: Dir) -> u64 { -98 // total opened - total closed = total opened - ( total permitted - total permitted unclosed ) -99 self.state.next_remote[dir as usize] -100 - (self.state.max_remote[dir as usize] -101 - self.state.allocated_remote_count[dir as usize]) -102 } -103} -104 -105/// Access to streams -106pub struct RecvStream<'a> { -107 pub(super) id: StreamId, -108 pub(super) state: &'a mut StreamsState, -109 pub(super) pending: &'a mut Retransmits, -110} -111 -112impl RecvStream<'_> { -113 /// Read from the given recv stream -114 /// -115 /// `max_length` limits the maximum size of the returned `Bytes` value; passing `usize::MAX` -116 /// will yield the best performance. `ordered` will make sure the returned chunk's offset will -117 /// have an offset exactly equal to the previously returned offset plus the previously returned -118 /// bytes' length. -119 /// -120 /// Yields `Ok(None)` if the stream was finished. Otherwise, yields a segment of data and its -121 /// offset in the stream. If `ordered` is `false`, segments may be received in any order, and -122 /// the `Chunk`'s `offset` field can be used to determine ordering in the caller. -123 /// -124 /// While most applications will prefer to consume stream data in order, unordered reads can -125 /// improve performance when packet loss occurs and data cannot be retransmitted before the flow -126 /// control window is filled. On any given stream, you can switch from ordered to unordered -127 /// reads, but ordered reads on streams that have seen previous unordered reads will return -128 /// `ReadError::IllegalOrderedRead`. -129 pub fn read(&mut self, ordered: bool) -> Result<Chunks<'_>, ReadableError> { -130 Chunks::new(self.id, ordered, self.state, self.pending) -131 } -132 -133 /// Stop accepting data on the given receive stream -134 /// -135 /// Discards unread data and notifies the peer to stop transmitting. Once stopped, further -136 /// attempts to operate on a stream will yield `ClosedStream` errors. -137 pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { -138 let mut entry = match self.state.recv.entry(self.id) { -139 hash_map::Entry::Occupied(s) => s, -140 hash_map::Entry::Vacant(_) => return Err(ClosedStream { _private: () }), -141 }; -142 let stream = get_or_insert_recv(self.state.stream_receive_window)(entry.get_mut()); -143 -144 let (read_credits, stop_sending) = stream.stop()?; -145 if stop_sending.should_transmit() { -146 self.pending.stop_sending.push(frame::StopSending { -147 id: self.id, -148 error_code, -149 }); -150 } -151 -152 // We need to keep stopped streams around until they're finished or reset so we can update -153 // connection-level flow control to account for discarded data. Otherwise, we can discard -154 // state immediately. -155 if !stream.final_offset_unknown() { -156 let recv = entry.remove().expect("must have recv when stopping"); -157 self.state.stream_recv_freed(self.id, recv); -158 } -159 -160 if self.state.add_read_credits(read_credits).should_transmit() { -161 self.pending.max_data = true; -162 } -163 -164 Ok(()) -165 } -166 -167 /// Returns the number of bytes read from this stream. -168 /// -169 /// This is the offset of the next byte to be read, i.e. the length of the contiguous -170 /// prefix of the stream consumed by the application. -171 pub fn bytes_read(&self) -> Result<u64, ClosedStream> { -172 let recv = self -173 .state -174 .recv -175 .get(&self.id) -176 .and_then(|s| s.as_ref()) -177 .and_then(|s| s.as_open_recv()) -178 .ok_or(ClosedStream { _private: () })?; -179 Ok(recv.assembler.bytes_read()) -180 } -181 -182 /// Check whether this stream has been reset by the peer, returning the reset error code if so -183 /// -184 /// After returning `Ok(Some(_))` once, stream state will be discarded and all future calls will -185 /// return `Err(ClosedStream)`. -186 pub fn received_reset(&mut self) -> Result<Option<VarInt>, ClosedStream> { -187 let hash_map::Entry::Occupied(entry) = self.state.recv.entry(self.id) else { -188 return Err(ClosedStream { _private: () }); -189 }; -190 let Some(s) = entry.get().as_ref().and_then(|s| s.as_open_recv()) else { -191 return Ok(None); -192 }; -193 if s.stopped { -194 return Err(ClosedStream { _private: () }); -195 } -196 let Some(code) = s.reset_code() else { -197 return Ok(None); -198 }; -199 -200 // Clean up state after application observes the reset, since there's no reason for the -201 // application to attempt to read or stop the stream once it knows it's reset -202 let (_, recv) = entry.remove_entry(); -203 self.state -204 .stream_recv_freed(self.id, recv.expect("must have recv on reset")); -205 self.state.queue_max_stream_id(self.pending); -206 -207 Ok(Some(code)) -208 } -209} -210 -211/// Access to streams -212pub struct SendStream<'a> { -213 pub(super) id: StreamId, -214 pub(super) state: &'a mut StreamsState, -215 pub(super) pending: &'a mut Retransmits, -216 pub(super) conn_state: &'a super::State, -217} -218 -219#[allow(clippy::needless_lifetimes)] // Needed for cfg(fuzzing) -220impl<'a> SendStream<'a> { -221 #[cfg(fuzzing)] -222 pub fn new( -223 id: StreamId, -224 state: &'a mut StreamsState, -225 pending: &'a mut Retransmits, -226 conn_state: &'a super::State, -227 ) -> Self { -228 Self { -229 id, -230 state, -231 pending, -232 conn_state, -233 } -234 } -235 -236 /// Send data on the given stream -237 /// -238 /// Returns the number of bytes successfully written. -239 pub fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> { -240 Ok(self.write_source(&mut ByteSlice::from_slice(data))?.bytes) -241 } -242 -243 /// Send data on the given stream -244 /// -245 /// Returns the number of bytes written and advances the provided `Bytes` -246 /// slice, removing all completely written chunks. -247 /// -248 /// Note that this method might also write a partial chunk. In this case -249 /// the chunk will be advanced and contain only non-written data after the call. -250 pub fn write_chunks(&mut self, data: &mut &mut [Bytes]) -> Result<usize, WriteError> { -251 let written = self.write_source(&mut BytesArray::from_chunks(data))?; -252 *data = &mut std::mem::take(data)[written.chunks..]; -253 Ok(written.bytes) -254 } -255 -256 fn write_source<'b, B: BytesSource<'b>>( -257 &mut self, -258 source: &'b mut B, -259 ) -> Result<Written, WriteError> { -260 if self.conn_state.is_closed() { -261 trace!(%self.id, "write blocked; connection draining"); -262 return Err(WriteError::Blocked); -263 } -264 -265 let limit = self.state.write_limit(); -266 -267 let max_send_data = self.state.max_send_data(self.id); -268 -269 let stream = self -270 .state -271 .send -272 .get_mut(&self.id) -273 .map(get_or_insert_send(max_send_data)) -274 .ok_or(WriteError::ClosedStream)?; -275 -276 if limit == 0 { -277 trace!( -278 stream = %self.id, max_data = self.state.max_data, data_sent = self.state.data_sent, -279 "write blocked by connection-level flow control or send window" -280 ); -281 if !stream.connection_blocked { -282 stream.connection_blocked = true; -283 self.state.connection_blocked.push(self.id); -284 } -285 return Err(WriteError::Blocked); -286 } -287 -288 let was_pending = stream.is_pending(); -289 let written = stream.write(source, limit)?; -290 self.state.data_sent += written.bytes as u64; -291 self.state.unacked_data += written.bytes as u64; -292 trace!(stream = %self.id, "wrote {} bytes", written.bytes); -293 if !was_pending { -294 self.state.pending.push_pending(self.id, stream.priority); -295 } -296 Ok(written) -297 } -298 -299 /// Check if this stream was stopped, get the reason if it was -300 pub fn stopped(&self) -> Result<Option<VarInt>, ClosedStream> { -301 match self.state.send.get(&self.id).as_ref() { -302 Some(Some(s)) => Ok(s.stop_reason), -303 Some(None) => Ok(None), -304 None => Err(ClosedStream { _private: () }), -305 } -306 } -307 -308 /// Finish a send stream, signalling that no more data will be sent. -309 /// -310 /// If this fails, no [`StreamEvent::Finished`] will be generated. -311 /// -312 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished -313 pub fn finish(&mut self) -> Result<(), FinishError> { -314 let max_send_data = self.state.max_send_data(self.id); -315 let stream = self -316 .state -317 .send -318 .get_mut(&self.id) -319 .map(get_or_insert_send(max_send_data)) -320 .ok_or(FinishError::ClosedStream)?; -321 -322 let was_pending = stream.is_pending(); -323 stream.finish()?; -324 if !was_pending { -325 self.state.pending.push_pending(self.id, stream.priority); -326 } -327 -328 Ok(()) -329 } -330 -331 /// Abandon transmitting data on a stream -332 /// -333 /// # Panics -334 /// - when applied to a receive stream -335 pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { -336 let max_send_data = self.state.max_send_data(self.id); -337 let stream = self -338 .state -339 .send -340 .get_mut(&self.id) -341 .map(get_or_insert_send(max_send_data)) -342 .ok_or(ClosedStream { _private: () })?; -343 -344 if matches!(stream.state, SendState::ResetSent) { -345 // Redundant reset call -346 return Err(ClosedStream { _private: () }); -347 } -348 -349 // Restore the portion of the send window consumed by the data that we aren't about to -350 // send. We leave flow control alone because the peer's responsible for issuing additional -351 // credit based on the final offset communicated in the RESET_STREAM frame we send. -352 self.state.unacked_data -= stream.pending.unacked(); -353 stream.reset(); -354 self.pending.reset_stream.push((self.id, error_code)); -355 -356 // Don't reopen an already-closed stream we haven't forgotten yet -357 Ok(()) -358 } -359 -360 /// Set the priority of a stream -361 /// -362 /// # Panics -363 /// - when applied to a receive stream -364 pub fn set_priority(&mut self, priority: i32) -> Result<(), ClosedStream> { -365 let max_send_data = self.state.max_send_data(self.id); -366 let stream = self -367 .state -368 .send -369 .get_mut(&self.id) -370 .map(get_or_insert_send(max_send_data)) -371 .ok_or(ClosedStream { _private: () })?; -372 -373 stream.priority = priority; -374 Ok(()) -375 } -376 -377 /// Get the priority of a stream -378 /// -379 /// # Panics -380 /// - when applied to a receive stream -381 pub fn priority(&self) -> Result<i32, ClosedStream> { -382 let stream = self -383 .state -384 .send -385 .get(&self.id) -386 .ok_or(ClosedStream { _private: () })?; -387 -388 Ok(stream.as_ref().map(|s| s.priority).unwrap_or_default()) -389 } -390} -391 -392/// A queue of streams with pending outgoing data, sorted by priority -393struct PendingStreamsQueue { -394 streams: BinaryHeap<PendingStream>, -395 /// The next stream to write out. This is `Some` when `TransportConfig::send_fairness(false)` and writing a stream is -396 /// interrupted while the stream still has some pending data. See `reinsert_pending()`. -397 next: Option<PendingStream>, -398 /// A monotonically decreasing counter, used to implement round-robin scheduling for streams of the same priority. -399 /// Underflowing is not a practical concern, as it is initialized to u64::MAX and only decremented by 1 in `push_pending` -400 recency: u64, -401} -402 -403impl PendingStreamsQueue { -404 fn new() -> Self { -405 Self { -406 streams: BinaryHeap::new(), -407 next: None, -408 recency: u64::MAX, -409 } -410 } -411 -412 /// Reinsert a stream that was pending and still contains unsent data. -413 fn reinsert_pending(&mut self, id: StreamId, priority: i32) { -414 assert!(self.next.is_none()); -415 -416 self.next = Some(PendingStream { -417 priority, -418 recency: self.recency, // the value here doesn't really matter -419 id, -420 }); -421 } -422 -423 /// Push a pending stream ID with the given priority, queued after any already-queued streams for the priority -424 fn push_pending(&mut self, id: StreamId, priority: i32) { -425 // Note that in the case where fairness is disabled, if we have a reinserted stream we don't -426 // bump it even if priority > next.priority. In order to minimize fragmentation we -427 // always try to complete a stream once part of it has been written. -428 -429 // As the recency counter is monotonically decreasing, we know that using its value to sort this stream will queue it -430 // after all other queued streams of the same priority. -431 // This is enough to implement round-robin scheduling for streams that are still pending even after being handled, -432 // as in that case they are removed from the `BinaryHeap`, handled, and then immediately reinserted. -433 self.recency -= 1; -434 self.streams.push(PendingStream { -435 priority, -436 recency: self.recency, -437 id, -438 }); -439 } -440 -441 fn pop(&mut self) -> Option<PendingStream> { -442 self.next.take().or_else(|| self.streams.pop()) -443 } -444 -445 fn clear(&mut self) { -446 self.next = None; -447 self.streams.clear(); -448 } -449 -450 fn iter(&self) -> impl Iterator<Item = &PendingStream> { -451 self.next.iter().chain(self.streams.iter()) -452 } -453 -454 #[cfg(test)] -455 fn len(&self) -> usize { -456 self.streams.len() + self.next.is_some() as usize -457 } -458} -459 -460/// The [`StreamId`] of a stream with pending data queued, ordered by its priority and recency -461#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -462struct PendingStream { -463 /// The priority of the stream -464 // Note that this field should be kept above the `recency` field, in order for the `Ord` derive to be correct -465 // (See https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html#derivable) -466 priority: i32, -467 /// A tie-breaker for streams of the same priority, used to improve fairness by implementing round-robin scheduling: -468 /// Larger values are prioritized, so it is initialised to `u64::MAX`, and when a stream writes data, we know -469 /// that it currently has the highest recency value, so it is deprioritized by setting its recency to 1 less than the -470 /// previous lowest recency value, such that all other streams of this priority will get processed once before we get back -471 /// round to this one -472 recency: u64, -473 /// The ID of the stream -474 // The way this type is used ensures that every instance has a unique `recency` value, so this field should be kept below -475 // the `priority` and `recency` fields, so that it does not interfere with the behaviour of the `Ord` derive -476 id: StreamId, -477} -478 -479/// Application events about streams -480#[derive(Debug, PartialEq, Eq)] -481pub enum StreamEvent { -482 /// One or more new streams has been opened and might be readable -483 Opened { -484 /// Directionality for which streams have been opened -485 dir: Dir, -486 }, -487 /// A currently open stream likely has data or errors waiting to be read -488 Readable { -489 /// Which stream is now readable -490 id: StreamId, -491 }, -492 /// A formerly write-blocked stream might be ready for a write or have been stopped -493 /// -494 /// Only generated for streams that are currently open. -495 Writable { -496 /// Which stream is now writable -497 id: StreamId, -498 }, -499 /// A finished stream has been fully acknowledged or stopped -500 Finished { -501 /// Which stream has been finished -502 id: StreamId, -503 }, -504 /// The peer asked us to stop sending on an outgoing stream -505 Stopped { -506 /// Which stream has been stopped -507 id: StreamId, -508 /// Error code supplied by the peer -509 error_code: VarInt, -510 }, -511 /// At least one new stream of a certain directionality may be opened -512 Available { -513 /// Directionality for which streams are newly available -514 dir: Dir, -515 }, -516} -517 -518/// Indicates whether a frame needs to be transmitted -519/// -520/// This type wraps around bool and uses the `#[must_use]` attribute in order -521/// to prevent accidental loss of the frame transmission requirement. -522#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -523#[must_use = "A frame might need to be enqueued"] -524pub struct ShouldTransmit(bool); -525 -526impl ShouldTransmit { -527 /// Returns whether a frame should be transmitted -528 pub fn should_transmit(self) -> bool { -529 self.0 -530 } -531} -532 -533/// Error indicating that a stream has not been opened or has already been finished or reset -534#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] -535#[error("closed stream")] -536pub struct ClosedStream { -537 _private: (), -538} -539 -540impl From<ClosedStream> for io::Error { -541 fn from(x: ClosedStream) -> Self { -542 Self::new(io::ErrorKind::NotConnected, x) -543 } -544} -545 -546#[derive(Debug, Copy, Clone, Eq, PartialEq)] -547enum StreamHalf { -548 Send, -549 Recv, -550} -551 -552/// A helper trait to unify Bytes, `Vec<u8>` and `&[u8]` as sources of bytes -553pub(super) trait BytesOrSlice<'a>: AsRef<[u8]> + 'a { -554 fn len(&self) -> usize { -555 self.as_ref().len() -556 } -557 fn is_empty(&self) -> bool { -558 self.as_ref().is_empty() -559 } -560 fn into_bytes(self) -> Bytes; -561} -562 -563impl BytesOrSlice<'_> for Bytes { -564 fn into_bytes(self) -> Bytes { -565 self -566 } -567} -568 -569impl<'a> BytesOrSlice<'a> for &'a [u8] { -570 fn into_bytes(self) -> Bytes { -571 Bytes::copy_from_slice(self) -572 } -573}
\ No newline at end of file +25 +26mod state; +27#[allow(unreachable_pub)] // fuzzing only +28pub use state::StreamsState; +29 +30/// Access to streams +31pub struct Streams<'a> { +32 pub(super) state: &'a mut StreamsState, +33 pub(super) conn_state: &'a super::State, +34} +35 +36#[allow(clippy::needless_lifetimes)] // Needed for cfg(fuzzing) +37impl<'a> Streams<'a> { +38 #[cfg(fuzzing)] +39 pub fn new(state: &'a mut StreamsState, conn_state: &'a super::State) -> Self { +40 Self { state, conn_state } +41 } +42 +43 /// Open a single stream if possible +44 /// +45 /// Returns `None` if the streams in the given direction are currently exhausted. +46 pub fn open(&mut self, dir: Dir) -> Option<StreamId> { +47 if self.conn_state.is_closed() { +48 return None; +49 } +50 +51 if self.state.next[dir as usize] >= self.state.max[dir as usize] { +52 self.state.streams_blocked[dir as usize] = true; +53 return None; +54 } +55 +56 self.state.next[dir as usize] += 1; +57 let id = StreamId::new(self.state.side, dir, self.state.next[dir as usize] - 1); +58 self.state.insert_local(id); +59 self.state.send_streams += 1; +60 Some(id) +61 } +62 +63 /// Accept a remotely initiated stream of a certain directionality, if possible +64 /// +65 /// Returns `None` if there are no new incoming streams for this connection. +66 /// Has no impact on the data flow-control or stream concurrency limits. +67 pub fn accept(&mut self, dir: Dir) -> Option<StreamId> { +68 if self.state.next_remote[dir as usize] == self.state.next_reported_remote[dir as usize] { +69 return None; +70 } +71 +72 let x = self.state.next_reported_remote[dir as usize]; +73 self.state.next_reported_remote[dir as usize] = x + 1; +74 if dir == Dir::Bi { +75 self.state.send_streams += 1; +76 } +77 +78 Some(StreamId::new(!self.state.side, dir, x)) +79 } +80 +81 #[cfg(fuzzing)] +82 pub fn state(&mut self) -> &mut StreamsState { +83 self.state +84 } +85 +86 /// The number of streams that may have unacknowledged data. +87 pub fn send_streams(&self) -> usize { +88 self.state.send_streams +89 } +90 +91 /// The number of remotely initiated open streams of a certain directionality. +92 /// +93 /// Includes remotely initiated streams, which have not been accepted via [`accept`](Self::accept). +94 /// These streams count against the respective concurrency limit reported by +95 /// [`Connection::max_concurrent_streams`](super::Connection::max_concurrent_streams). +96 pub fn remote_open_streams(&self, dir: Dir) -> u64 { +97 // total opened - total closed = total opened - ( total permitted - total permitted unclosed ) +98 self.state.next_remote[dir as usize] +99 - (self.state.max_remote[dir as usize] +100 - self.state.allocated_remote_count[dir as usize]) +101 } +102} +103 +104/// Access to streams +105pub struct RecvStream<'a> { +106 pub(super) id: StreamId, +107 pub(super) state: &'a mut StreamsState, +108 pub(super) pending: &'a mut Retransmits, +109} +110 +111impl RecvStream<'_> { +112 /// Read from the given recv stream +113 /// +114 /// `max_length` limits the maximum size of the returned `Bytes` value; passing `usize::MAX` +115 /// will yield the best performance. `ordered` will make sure the returned chunk's offset will +116 /// have an offset exactly equal to the previously returned offset plus the previously returned +117 /// bytes' length. +118 /// +119 /// Yields `Ok(None)` if the stream was finished. Otherwise, yields a segment of data and its +120 /// offset in the stream. If `ordered` is `false`, segments may be received in any order, and +121 /// the `Chunk`'s `offset` field can be used to determine ordering in the caller. +122 /// +123 /// While most applications will prefer to consume stream data in order, unordered reads can +124 /// improve performance when packet loss occurs and data cannot be retransmitted before the flow +125 /// control window is filled. On any given stream, you can switch from ordered to unordered +126 /// reads, but ordered reads on streams that have seen previous unordered reads will return +127 /// `ReadError::IllegalOrderedRead`. +128 pub fn read(&mut self, ordered: bool) -> Result<Chunks<'_>, ReadableError> { +129 Chunks::new(self.id, ordered, self.state, self.pending) +130 } +131 +132 /// Stop accepting data on the given receive stream +133 /// +134 /// Discards unread data and notifies the peer to stop transmitting. Once stopped, further +135 /// attempts to operate on a stream will yield `ClosedStream` errors. +136 pub fn stop(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { +137 let mut entry = match self.state.recv.entry(self.id) { +138 hash_map::Entry::Occupied(s) => s, +139 hash_map::Entry::Vacant(_) => return Err(ClosedStream { _private: () }), +140 }; +141 let stream = get_or_insert_recv(self.state.stream_receive_window)(entry.get_mut()); +142 +143 let (read_credits, stop_sending) = stream.stop()?; +144 if stop_sending.should_transmit() { +145 self.pending.stop_sending.push(frame::StopSending { +146 id: self.id, +147 error_code, +148 }); +149 } +150 +151 // We need to keep stopped streams around until they're finished or reset so we can update +152 // connection-level flow control to account for discarded data. Otherwise, we can discard +153 // state immediately. +154 if !stream.final_offset_unknown() { +155 let recv = entry.remove().expect("must have recv when stopping"); +156 self.state.stream_recv_freed(self.id, recv); +157 } +158 +159 if self.state.add_read_credits(read_credits).should_transmit() { +160 self.pending.max_data = true; +161 } +162 +163 Ok(()) +164 } +165 +166 /// Returns the number of bytes read from this stream. +167 /// +168 /// This is the offset of the next byte to be read, i.e. the length of the contiguous +169 /// prefix of the stream consumed by the application. +170 pub fn bytes_read(&self) -> Result<u64, ClosedStream> { +171 let recv = self +172 .state +173 .recv +174 .get(&self.id) +175 .and_then(|s| s.as_ref()) +176 .and_then(|s| s.as_open_recv()) +177 .ok_or(ClosedStream { _private: () })?; +178 Ok(recv.assembler.bytes_read()) +179 } +180 +181 /// Check whether this stream has been reset by the peer, returning the reset error code if so +182 /// +183 /// After returning `Ok(Some(_))` once, stream state will be discarded and all future calls will +184 /// return `Err(ClosedStream)`. +185 pub fn received_reset(&mut self) -> Result<Option<VarInt>, ClosedStream> { +186 let hash_map::Entry::Occupied(entry) = self.state.recv.entry(self.id) else { +187 return Err(ClosedStream { _private: () }); +188 }; +189 let Some(s) = entry.get().as_ref().and_then(|s| s.as_open_recv()) else { +190 return Ok(None); +191 }; +192 if s.stopped { +193 return Err(ClosedStream { _private: () }); +194 } +195 let Some(code) = s.reset_code() else { +196 return Ok(None); +197 }; +198 +199 // Clean up state after application observes the reset, since there's no reason for the +200 // application to attempt to read or stop the stream once it knows it's reset +201 let (_, recv) = entry.remove_entry(); +202 self.state +203 .stream_recv_freed(self.id, recv.expect("must have recv on reset")); +204 self.state.queue_max_stream_id(self.pending); +205 +206 Ok(Some(code)) +207 } +208} +209 +210/// Access to streams +211pub struct SendStream<'a> { +212 pub(super) id: StreamId, +213 pub(super) state: &'a mut StreamsState, +214 pub(super) pending: &'a mut Retransmits, +215 pub(super) conn_state: &'a super::State, +216} +217 +218#[allow(clippy::needless_lifetimes)] // Needed for cfg(fuzzing) +219impl<'a> SendStream<'a> { +220 #[cfg(fuzzing)] +221 pub fn new( +222 id: StreamId, +223 state: &'a mut StreamsState, +224 pending: &'a mut Retransmits, +225 conn_state: &'a super::State, +226 ) -> Self { +227 Self { +228 id, +229 state, +230 pending, +231 conn_state, +232 } +233 } +234 +235 /// Send data on the given stream +236 /// +237 /// Returns the number of bytes successfully written. +238 pub fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> { +239 Ok(self.write_source(&mut ByteSlice::from_slice(data))?.bytes) +240 } +241 +242 /// Send data on the given stream +243 /// +244 /// Returns the number of bytes written and advances the provided `Bytes` +245 /// slice, removing all completely written chunks. +246 /// +247 /// Note that this method might also write a partial chunk. In this case +248 /// the chunk will be advanced and contain only non-written data after the call. +249 pub fn write_chunks(&mut self, data: &mut &mut [Bytes]) -> Result<usize, WriteError> { +250 let written = self.write_source(&mut BytesArray::from_chunks(data))?; +251 *data = &mut std::mem::take(data)[written.chunks..]; +252 Ok(written.bytes) +253 } +254 +255 fn write_source<'b, B: BytesSource<'b>>( +256 &mut self, +257 source: &'b mut B, +258 ) -> Result<Written, WriteError> { +259 if self.conn_state.is_closed() { +260 trace!(%self.id, "write blocked; connection draining"); +261 return Err(WriteError::Blocked); +262 } +263 +264 let limit = self.state.write_limit(); +265 +266 let max_send_data = self.state.max_send_data(self.id); +267 +268 let stream = self +269 .state +270 .send +271 .get_mut(&self.id) +272 .map(get_or_insert_send(max_send_data)) +273 .ok_or(WriteError::ClosedStream)?; +274 +275 if limit == 0 { +276 trace!( +277 stream = %self.id, max_data = self.state.max_data, data_sent = self.state.data_sent, +278 "write blocked by connection-level flow control or send window" +279 ); +280 if !stream.connection_blocked { +281 stream.connection_blocked = true; +282 self.state.connection_blocked.push(self.id); +283 } +284 return Err(WriteError::Blocked); +285 } +286 +287 let was_pending = stream.is_pending(); +288 let written = stream.write(source, limit)?; +289 self.state.data_sent += written.bytes as u64; +290 self.state.unacked_data += written.bytes as u64; +291 trace!(stream = %self.id, "wrote {} bytes", written.bytes); +292 if !was_pending { +293 self.state.pending.push_pending(self.id, stream.priority); +294 } +295 Ok(written) +296 } +297 +298 /// Check if this stream was stopped, get the reason if it was +299 pub fn stopped(&self) -> Result<Option<VarInt>, ClosedStream> { +300 match self.state.send.get(&self.id).as_ref() { +301 Some(Some(s)) => Ok(s.stop_reason), +302 Some(None) => Ok(None), +303 None => Err(ClosedStream { _private: () }), +304 } +305 } +306 +307 /// Finish a send stream, signalling that no more data will be sent. +308 /// +309 /// If this fails, no [`StreamEvent::Finished`] will be generated. +310 /// +311 /// [`StreamEvent::Finished`]: crate::StreamEvent::Finished +312 pub fn finish(&mut self) -> Result<(), FinishError> { +313 let max_send_data = self.state.max_send_data(self.id); +314 let stream = self +315 .state +316 .send +317 .get_mut(&self.id) +318 .map(get_or_insert_send(max_send_data)) +319 .ok_or(FinishError::ClosedStream)?; +320 +321 let was_pending = stream.is_pending(); +322 stream.finish()?; +323 if !was_pending { +324 self.state.pending.push_pending(self.id, stream.priority); +325 } +326 +327 Ok(()) +328 } +329 +330 /// Abandon transmitting data on a stream +331 /// +332 /// # Panics +333 /// - when applied to a receive stream +334 pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream> { +335 let max_send_data = self.state.max_send_data(self.id); +336 let stream = self +337 .state +338 .send +339 .get_mut(&self.id) +340 .map(get_or_insert_send(max_send_data)) +341 .ok_or(ClosedStream { _private: () })?; +342 +343 if matches!(stream.state, SendState::ResetSent) { +344 // Redundant reset call +345 return Err(ClosedStream { _private: () }); +346 } +347 +348 // Restore the portion of the send window consumed by the data that we aren't about to +349 // send. We leave flow control alone because the peer's responsible for issuing additional +350 // credit based on the final offset communicated in the RESET_STREAM frame we send. +351 self.state.unacked_data -= stream.pending.unacked(); +352 stream.reset(); +353 self.pending.reset_stream.push((self.id, error_code)); +354 +355 // Don't reopen an already-closed stream we haven't forgotten yet +356 Ok(()) +357 } +358 +359 /// Set the priority of a stream +360 /// +361 /// # Panics +362 /// - when applied to a receive stream +363 pub fn set_priority(&mut self, priority: i32) -> Result<(), ClosedStream> { +364 let max_send_data = self.state.max_send_data(self.id); +365 let stream = self +366 .state +367 .send +368 .get_mut(&self.id) +369 .map(get_or_insert_send(max_send_data)) +370 .ok_or(ClosedStream { _private: () })?; +371 +372 stream.priority = priority; +373 Ok(()) +374 } +375 +376 /// Get the priority of a stream +377 /// +378 /// # Panics +379 /// - when applied to a receive stream +380 pub fn priority(&self) -> Result<i32, ClosedStream> { +381 let stream = self +382 .state +383 .send +384 .get(&self.id) +385 .ok_or(ClosedStream { _private: () })?; +386 +387 Ok(stream.as_ref().map(|s| s.priority).unwrap_or_default()) +388 } +389} +390 +391/// A queue of streams with pending outgoing data, sorted by priority +392struct PendingStreamsQueue { +393 streams: BinaryHeap<PendingStream>, +394 /// The next stream to write out. This is `Some` when `TransportConfig::send_fairness(false)` and writing a stream is +395 /// interrupted while the stream still has some pending data. See `reinsert_pending()`. +396 next: Option<PendingStream>, +397 /// A monotonically decreasing counter, used to implement round-robin scheduling for streams of the same priority. +398 /// Underflowing is not a practical concern, as it is initialized to u64::MAX and only decremented by 1 in `push_pending` +399 recency: u64, +400} +401 +402impl PendingStreamsQueue { +403 fn new() -> Self { +404 Self { +405 streams: BinaryHeap::new(), +406 next: None, +407 recency: u64::MAX, +408 } +409 } +410 +411 /// Reinsert a stream that was pending and still contains unsent data. +412 fn reinsert_pending(&mut self, id: StreamId, priority: i32) { +413 assert!(self.next.is_none()); +414 +415 self.next = Some(PendingStream { +416 priority, +417 recency: self.recency, // the value here doesn't really matter +418 id, +419 }); +420 } +421 +422 /// Push a pending stream ID with the given priority, queued after any already-queued streams for the priority +423 fn push_pending(&mut self, id: StreamId, priority: i32) { +424 // Note that in the case where fairness is disabled, if we have a reinserted stream we don't +425 // bump it even if priority > next.priority. In order to minimize fragmentation we +426 // always try to complete a stream once part of it has been written. +427 +428 // As the recency counter is monotonically decreasing, we know that using its value to sort this stream will queue it +429 // after all other queued streams of the same priority. +430 // This is enough to implement round-robin scheduling for streams that are still pending even after being handled, +431 // as in that case they are removed from the `BinaryHeap`, handled, and then immediately reinserted. +432 self.recency -= 1; +433 self.streams.push(PendingStream { +434 priority, +435 recency: self.recency, +436 id, +437 }); +438 } +439 +440 fn pop(&mut self) -> Option<PendingStream> { +441 self.next.take().or_else(|| self.streams.pop()) +442 } +443 +444 fn clear(&mut self) { +445 self.next = None; +446 self.streams.clear(); +447 } +448 +449 fn iter(&self) -> impl Iterator<Item = &PendingStream> { +450 self.next.iter().chain(self.streams.iter()) +451 } +452 +453 #[cfg(test)] +454 fn len(&self) -> usize { +455 self.streams.len() + self.next.is_some() as usize +456 } +457} +458 +459/// The [`StreamId`] of a stream with pending data queued, ordered by its priority and recency +460#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +461struct PendingStream { +462 /// The priority of the stream +463 // Note that this field should be kept above the `recency` field, in order for the `Ord` derive to be correct +464 // (See https://doc.rust-lang.org/stable/std/cmp/trait.Ord.html#derivable) +465 priority: i32, +466 /// A tie-breaker for streams of the same priority, used to improve fairness by implementing round-robin scheduling: +467 /// Larger values are prioritized, so it is initialised to `u64::MAX`, and when a stream writes data, we know +468 /// that it currently has the highest recency value, so it is deprioritized by setting its recency to 1 less than the +469 /// previous lowest recency value, such that all other streams of this priority will get processed once before we get back +470 /// round to this one +471 recency: u64, +472 /// The ID of the stream +473 // The way this type is used ensures that every instance has a unique `recency` value, so this field should be kept below +474 // the `priority` and `recency` fields, so that it does not interfere with the behaviour of the `Ord` derive +475 id: StreamId, +476} +477 +478/// Application events about streams +479#[derive(Debug, PartialEq, Eq)] +480pub enum StreamEvent { +481 /// One or more new streams has been opened and might be readable +482 Opened { +483 /// Directionality for which streams have been opened +484 dir: Dir, +485 }, +486 /// A currently open stream likely has data or errors waiting to be read +487 Readable { +488 /// Which stream is now readable +489 id: StreamId, +490 }, +491 /// A formerly write-blocked stream might be ready for a write or have been stopped +492 /// +493 /// Only generated for streams that are currently open. +494 Writable { +495 /// Which stream is now writable +496 id: StreamId, +497 }, +498 /// A finished stream has been fully acknowledged or stopped +499 Finished { +500 /// Which stream has been finished +501 id: StreamId, +502 }, +503 /// The peer asked us to stop sending on an outgoing stream +504 Stopped { +505 /// Which stream has been stopped +506 id: StreamId, +507 /// Error code supplied by the peer +508 error_code: VarInt, +509 }, +510 /// At least one new stream of a certain directionality may be opened +511 Available { +512 /// Directionality for which streams are newly available +513 dir: Dir, +514 }, +515} +516 +517/// Indicates whether a frame needs to be transmitted +518/// +519/// This type wraps around bool and uses the `#[must_use]` attribute in order +520/// to prevent accidental loss of the frame transmission requirement. +521#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +522#[must_use = "A frame might need to be enqueued"] +523pub struct ShouldTransmit(bool); +524 +525impl ShouldTransmit { +526 /// Returns whether a frame should be transmitted +527 pub fn should_transmit(self) -> bool { +528 self.0 +529 } +530} +531 +532/// Error indicating that a stream has not been opened or has already been finished or reset +533#[derive(Debug, Default, Error, Clone, PartialEq, Eq)] +534#[error("closed stream")] +535pub struct ClosedStream { +536 _private: (), +537} +538 +539impl From<ClosedStream> for io::Error { +540 fn from(x: ClosedStream) -> Self { +541 Self::new(io::ErrorKind::NotConnected, x) +542 } +543} +544 +545#[derive(Debug, Copy, Clone, Eq, PartialEq)] +546enum StreamHalf { +547 Send, +548 Recv, +549} +550 +551/// A helper trait to unify Bytes, `Vec<u8>` and `&[u8]` as sources of bytes +552pub(super) trait BytesOrSlice<'a>: AsRef<[u8]> + 'a { +553 fn len(&self) -> usize { +554 self.as_ref().len() +555 } +556 fn is_empty(&self) -> bool { +557 self.as_ref().is_empty() +558 } +559 fn into_bytes(self) -> Bytes; +560} +561 +562impl BytesOrSlice<'_> for Bytes { +563 fn into_bytes(self) -> Bytes { +564 self +565 } +566} +567 +568impl<'a> BytesOrSlice<'a> for &'a [u8] { +569 fn into_bytes(self) -> Bytes { +570 Bytes::copy_from_slice(self) +571 } +572}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/streams/state.rs.html b/pr/451/docs/src/noq_proto/connection/streams/state.rs.html index b888816cf..eb0ee432a 100644 --- a/pr/451/docs/src/noq_proto/connection/streams/state.rs.html +++ b/pr/451/docs/src/noq_proto/connection/streams/state.rs.html @@ -149,7 +149,7 @@ 149 receive_window: VarInt, 150 stream_receive_window: VarInt, 151 ) -> Self { -152 let mut this = Self { +152 Self { 153 side, 154 send: FxHashMap::default(), 155 recv: FxHashMap::default(), @@ -182,134 +182,134 @@ 182 initial_max_stream_data_bidi_remote: 0u32.into(), 183 receive_window_shrink_debt: 0, 184 streams_blocked: [false, false], -185 }; -186 -187 for dir in Dir::iter() { -188 for i in 0..this.max_remote[dir as usize] { -189 this.insert(true, StreamId::new(!side, dir, i)); -190 } -191 } -192 -193 this -194 } -195 -196 pub(crate) fn set_params(&mut self, params: &TransportParameters) { -197 self.initial_max_stream_data_uni = params.initial_max_stream_data_uni; -198 self.initial_max_stream_data_bidi_local = params.initial_max_stream_data_bidi_local; -199 self.initial_max_stream_data_bidi_remote = params.initial_max_stream_data_bidi_remote; -200 self.max[Dir::Bi as usize] = params.initial_max_streams_bidi.into(); -201 self.max[Dir::Uni as usize] = params.initial_max_streams_uni.into(); -202 self.received_max_data(params.initial_max_data); -203 for i in 0..self.max_remote[Dir::Bi as usize] { -204 let id = StreamId::new(!self.side, Dir::Bi, i); -205 if let Some(s) = self.send.get_mut(&id).and_then(|s| s.as_mut()) { -206 s.max_data = params.initial_max_stream_data_bidi_local.into(); -207 } -208 } -209 } -210 -211 /// Ensure we have space for at least a full flow control window of remotely-initiated streams -212 /// to be open, and notify the peer if the window has moved -213 fn ensure_remote_streams(&mut self, dir: Dir) { -214 let new_count = self.max_concurrent_remote_count[dir as usize] -215 .saturating_sub(self.allocated_remote_count[dir as usize]); -216 for i in 0..new_count { -217 let id = StreamId::new(!self.side, dir, self.max_remote[dir as usize] + i); -218 self.insert(true, id); -219 } -220 self.allocated_remote_count[dir as usize] += new_count; -221 self.max_remote[dir as usize] += new_count; -222 } -223 -224 pub(crate) fn zero_rtt_rejected(&mut self) { -225 // Revert to initial state for outgoing streams -226 for dir in Dir::iter() { -227 for i in 0..self.next[dir as usize] { -228 // We don't bother calling `stream_freed` here because we explicitly reset affected -229 // counters below. -230 let id = StreamId::new(self.side, dir, i); -231 self.send.remove(&id).unwrap(); -232 if let Dir::Bi = dir { -233 self.recv.remove(&id).unwrap(); -234 } -235 } -236 self.next[dir as usize] = 0; -237 -238 // If 0-RTT was rejected, any flow control frames we sent were lost. -239 if self.flow_control_adjusted { -240 // Conservative approximation of whatever we sent in transport parameters -241 self.sent_max_remote[dir as usize] = 0; -242 } -243 } -244 -245 self.pending.clear(); -246 self.send_streams = 0; -247 self.data_sent = 0; -248 self.connection_blocked.clear(); -249 } -250 -251 /// Process incoming stream frame -252 /// -253 /// If successful, returns whether a `MAX_DATA` frame needs to be transmitted -254 pub(crate) fn received( -255 &mut self, -256 frame: frame::Stream, -257 payload_len: usize, -258 ) -> Result<ShouldTransmit, TransportError> { -259 let id = frame.id; -260 self.validate_receive_id(id).inspect_err(|_e| { -261 debug!("received illegal STREAM frame"); -262 })?; -263 -264 let Some(rs) = self -265 .recv -266 .get_mut(&id) -267 .map(get_or_insert_recv(self.stream_receive_window)) -268 else { -269 trace!("dropping frame for closed stream"); -270 return Ok(ShouldTransmit(false)); -271 }; -272 -273 if !rs.is_receiving() { -274 trace!("dropping frame for finished stream"); -275 return Ok(ShouldTransmit(false)); -276 } -277 -278 let (new_bytes, closed) = -279 rs.ingest(frame, payload_len, self.data_recvd, self.local_max_data)?; -280 self.data_recvd = self.data_recvd.saturating_add(new_bytes); -281 -282 if !rs.stopped { -283 self.on_stream_frame(true, id); -284 return Ok(ShouldTransmit(false)); -285 } -286 -287 // Stopped streams become closed instantly on FIN, so check whether we need to clean up -288 if closed { -289 let rs = self.recv.remove(&id).flatten().unwrap(); -290 self.stream_recv_freed(id, rs); -291 } -292 -293 // We don't buffer data on stopped streams, so issue flow control credit immediately -294 Ok(self.add_read_credits(new_bytes)) -295 } -296 -297 /// Process incoming RESET_STREAM frame -298 /// -299 /// If successful, returns whether a `MAX_DATA` frame needs to be transmitted -300 #[allow(unreachable_pub)] // fuzzing only -301 pub fn received_reset( -302 &mut self, -303 frame: frame::ResetStream, -304 ) -> Result<ShouldTransmit, TransportError> { -305 let frame::ResetStream { -306 id, -307 error_code, -308 final_offset, -309 } = frame; -310 self.validate_receive_id(id).inspect_err(|_e| { -311 debug!("received illegal RESET_STREAM frame"); -312 })?; +185 } +186 } +187 +188 pub(crate) fn set_params(&mut self, params: &TransportParameters) { +189 self.initial_max_stream_data_uni = params.initial_max_stream_data_uni; +190 self.initial_max_stream_data_bidi_local = params.initial_max_stream_data_bidi_local; +191 self.initial_max_stream_data_bidi_remote = params.initial_max_stream_data_bidi_remote; +192 self.max[Dir::Bi as usize] = params.initial_max_streams_bidi.into(); +193 self.max[Dir::Uni as usize] = params.initial_max_streams_uni.into(); +194 self.received_max_data(params.initial_max_data); +195 for (&id, slot) in self.send.iter_mut() { +196 if id.initiator() != self.side +197 && id.dir() == Dir::Bi +198 && let Some(s) = slot.as_mut() +199 { +200 s.max_data = params.initial_max_stream_data_bidi_local.into(); +201 } +202 } +203 } +204 +205 /// Ensure we have space for at least a full flow control window of remotely-initiated streams +206 /// to be open, and notify the peer if the window has moved +207 fn ensure_remote_streams(&mut self, dir: Dir) { +208 let new_count = self.max_concurrent_remote_count[dir as usize] +209 .saturating_sub(self.allocated_remote_count[dir as usize]); +210 self.allocated_remote_count[dir as usize] += new_count; +211 self.max_remote[dir as usize] += new_count; +212 } +213 +214 pub(crate) fn zero_rtt_rejected(&mut self) { +215 // Revert to initial state for outgoing streams +216 for dir in Dir::iter() { +217 for i in 0..self.next[dir as usize] { +218 // We don't bother calling `stream_freed` here because we explicitly reset affected +219 // counters below. +220 let id = StreamId::new(self.side, dir, i); +221 self.send.remove(&id).unwrap(); +222 if let Dir::Bi = dir { +223 self.recv.remove(&id).unwrap(); +224 } +225 } +226 self.next[dir as usize] = 0; +227 +228 // If 0-RTT was rejected, any flow control frames we sent were lost. +229 if self.flow_control_adjusted { +230 // Conservative approximation of whatever we sent in transport parameters +231 self.sent_max_remote[dir as usize] = 0; +232 } +233 } +234 +235 self.pending.clear(); +236 self.send_streams = 0; +237 self.data_sent = 0; +238 self.connection_blocked.clear(); +239 } +240 +241 /// Process incoming stream frame +242 /// +243 /// If successful, returns whether a `MAX_DATA` frame needs to be transmitted +244 pub(crate) fn received( +245 &mut self, +246 frame: frame::Stream, +247 payload_len: usize, +248 ) -> Result<ShouldTransmit, TransportError> { +249 let id = frame.id; +250 self.validate_receive_id(id).inspect_err(|_e| { +251 debug!("received illegal STREAM frame"); +252 })?; +253 +254 // Create state for this stream if the remote peer created it. +255 let newly_created = self.ensure_remote(id); +256 +257 let Some(rs) = self +258 .recv +259 .get_mut(&id) +260 .map(get_or_insert_recv(self.stream_receive_window)) +261 else { +262 trace!("dropping frame for closed stream"); +263 return Ok(ShouldTransmit(false)); +264 }; +265 +266 if !rs.is_receiving() { +267 trace!("dropping frame for finished stream"); +268 return Ok(ShouldTransmit(false)); +269 } +270 +271 let (new_bytes, closed) = +272 rs.ingest(frame, payload_len, self.data_recvd, self.local_max_data)?; +273 self.data_recvd = self.data_recvd.saturating_add(new_bytes); +274 +275 if !rs.stopped { +276 // Newly opened streams are inherently readable; the app discovers them via +277 // `StreamEvent::Opened` and a separate `Readable` would be redundant. +278 if !newly_created { +279 self.events.push_back(StreamEvent::Readable { id }); +280 } +281 return Ok(ShouldTransmit(false)); +282 } +283 +284 // Stopped streams become closed instantly on FIN, so check whether we need to clean up +285 if closed { +286 let rs = self.recv.remove(&id).flatten().unwrap(); +287 self.stream_recv_freed(id, rs); +288 } +289 +290 // We don't buffer data on stopped streams, so issue flow control credit immediately +291 Ok(self.add_read_credits(new_bytes)) +292 } +293 +294 /// Process incoming RESET_STREAM frame +295 /// +296 /// If successful, returns whether a `MAX_DATA` frame needs to be transmitted +297 #[allow(unreachable_pub)] // fuzzing only +298 pub fn received_reset( +299 &mut self, +300 frame: frame::ResetStream, +301 ) -> Result<ShouldTransmit, TransportError> { +302 let frame::ResetStream { +303 id, +304 error_code, +305 final_offset, +306 } = frame; +307 self.validate_receive_id(id).inspect_err(|_e| { +308 debug!("received illegal RESET_STREAM frame"); +309 })?; +310 +311 // Create state for this stream if the remote peer created it. +312 let newly_created = self.ensure_remote(id); 313 314 let Some(rs) = self 315 .recv @@ -337,1716 +337,1894 @@ 337 // Stopped streams should be disposed immediately on reset 338 let rs = self.recv.remove(&id).flatten().unwrap(); 339 self.stream_recv_freed(id, rs); -340 } -341 self.on_stream_frame(!stopped, id); -342 -343 // Update connection-level flow control -344 Ok(if bytes_read != final_offset.into_inner() { -345 // bytes_read is always <= end, so this won't underflow. -346 self.data_recvd = self -347 .data_recvd -348 .saturating_add(u64::from(final_offset) - end); -349 self.add_read_credits(u64::from(final_offset) - bytes_read) -350 } else { -351 ShouldTransmit(false) -352 }) -353 } -354 -355 /// Process incoming `STOP_SENDING` frame -356 #[allow(unreachable_pub)] // fuzzing only -357 pub fn received_stop_sending(&mut self, id: StreamId, error_code: VarInt) { -358 let max_send_data = self.max_send_data(id); -359 let Some(stream) = self -360 .send -361 .get_mut(&id) -362 .map(get_or_insert_send(max_send_data)) -363 else { -364 return; -365 }; -366 -367 if stream.try_stop(error_code) { -368 self.events -369 .push_back(StreamEvent::Stopped { id, error_code }); -370 self.on_stream_frame(false, id); -371 } -372 } -373 -374 pub(crate) fn reset_acked(&mut self, id: StreamId) { -375 match self.send.entry(id) { -376 hash_map::Entry::Vacant(_) => {} -377 hash_map::Entry::Occupied(e) => { -378 if let Some(SendState::ResetSent) = e.get().as_ref().map(|s| s.state) { -379 e.remove_entry(); -380 self.stream_freed(id, StreamHalf::Send); -381 } -382 } -383 } -384 } -385 -386 /// Whether any stream data is queued, regardless of control frames -387 pub(crate) fn can_send_stream_data(&self) -> bool { -388 // Reset streams may linger in the pending stream list, but will never produce stream frames -389 self.pending.iter().any(|stream| { -390 self.send -391 .get(&stream.id) -392 .and_then(|s| s.as_ref()) -393 .is_some_and(|s| !s.is_reset()) -394 }) -395 } -396 -397 /// Whether MAX_STREAM_DATA frames could be sent for stream `id` -398 pub(crate) fn can_send_flow_control(&self, id: StreamId) -> bool { -399 self.recv -400 .get(&id) -401 .and_then(|s| s.as_ref()) -402 .and_then(|s| s.as_open_recv()) -403 .is_some_and(|s| s.can_send_flow_control()) -404 } -405 -406 pub(in crate::connection) fn write_control_frames<'a, 'b>( -407 &mut self, -408 builder: &mut PacketBuilder<'a, 'b>, -409 pending: &mut Retransmits, -410 stats: &mut FrameStats, -411 ) { -412 // RESET_STREAM -413 while builder.frame_space_remaining() > frame::ResetStream::SIZE_BOUND { -414 let Some((id, error_code)) = pending.reset_stream.pop() else { -415 break; -416 }; -417 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { -418 continue; -419 }; -420 let frame = frame::ResetStream { -421 id, -422 error_code, -423 final_offset: VarInt::try_from(stream.offset()).expect("impossibly large offset"), +340 } else if !newly_created { +341 // Newly opened streams are inherently readable; the app discovers them via +342 // `StreamEvent::Opened` and a separate `Readable` would be redundant. +343 self.events.push_back(StreamEvent::Readable { id }); +344 } +345 +346 // Update connection-level flow control +347 Ok(if bytes_read != final_offset.into_inner() { +348 // bytes_read is always <= end, so this won't underflow. +349 self.data_recvd = self +350 .data_recvd +351 .saturating_add(u64::from(final_offset) - end); +352 self.add_read_credits(u64::from(final_offset) - bytes_read) +353 } else { +354 ShouldTransmit(false) +355 }) +356 } +357 +358 /// Process incoming `STOP_SENDING` frame +359 #[allow(unreachable_pub)] // fuzzing only +360 pub fn received_stop_sending(&mut self, id: StreamId, error_code: VarInt) { +361 // Create state for this stream if the remote peer created it. +362 self.ensure_remote(id); +363 +364 let max_send_data = self.max_send_data(id); +365 let Some(stream) = self +366 .send +367 .get_mut(&id) +368 .map(get_or_insert_send(max_send_data)) +369 else { +370 return; +371 }; +372 +373 if stream.try_stop(error_code) { +374 self.events +375 .push_back(StreamEvent::Stopped { id, error_code }); +376 } +377 } +378 +379 pub(crate) fn reset_acked(&mut self, id: StreamId) { +380 match self.send.entry(id) { +381 hash_map::Entry::Vacant(_) => {} +382 hash_map::Entry::Occupied(e) => { +383 if let Some(SendState::ResetSent) = e.get().as_ref().map(|s| s.state) { +384 e.remove_entry(); +385 self.stream_freed(id, StreamHalf::Send); +386 } +387 } +388 } +389 } +390 +391 /// Whether any stream data is queued, regardless of control frames +392 pub(crate) fn can_send_stream_data(&self) -> bool { +393 // Reset streams may linger in the pending stream list, but will never produce stream frames +394 self.pending.iter().any(|stream| { +395 self.send +396 .get(&stream.id) +397 .and_then(|s| s.as_ref()) +398 .is_some_and(|s| !s.is_reset()) +399 }) +400 } +401 +402 /// Whether MAX_STREAM_DATA frames could be sent for stream `id` +403 pub(crate) fn can_send_flow_control(&self, id: StreamId) -> bool { +404 self.recv +405 .get(&id) +406 .and_then(|s| s.as_ref()) +407 .and_then(|s| s.as_open_recv()) +408 .is_some_and(|s| s.can_send_flow_control()) +409 } +410 +411 pub(in crate::connection) fn write_control_frames<'a, 'b>( +412 &mut self, +413 builder: &mut PacketBuilder<'a, 'b>, +414 pending: &mut Retransmits, +415 stats: &mut FrameStats, +416 ) { +417 // RESET_STREAM +418 while builder.frame_space_remaining() > frame::ResetStream::SIZE_BOUND { +419 let Some((id, error_code)) = pending.reset_stream.pop() else { +420 break; +421 }; +422 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { +423 continue; 424 }; -425 builder.write_frame(frame, stats); -426 } -427 -428 // STOP_SENDING -429 while builder.frame_space_remaining() > frame::StopSending::SIZE_BOUND { -430 let Some(frame) = pending.stop_sending.pop() else { -431 break; -432 }; -433 // We may need to transmit STOP_SENDING even for streams whose state we have discarded, -434 // because we are able to discard local state for stopped streams immediately upon -435 // receiving FIN, even if the peer still has arbitrarily large amounts of data to -436 // (re)transmit due to loss or unconventional sending strategy. We could fine-tune this -437 // a little by dropping the frame if we specifically know the stream's been reset by the -438 // peer, but we discard that information as soon as the application consumes it, so it -439 // can't be relied upon regardless. -440 builder.write_frame(frame, stats); -441 } -442 -443 // MAX_DATA -444 if pending.max_data && builder.frame_space_remaining() > 9 { -445 pending.max_data = false; -446 -447 // `local_max_data` can grow bigger than `VarInt`. -448 // For transmission inside QUIC frames we need to clamp it to the -449 // maximum allowed `VarInt` size. -450 let max = VarInt::try_from(self.local_max_data).unwrap_or(VarInt::MAX); +425 let frame = frame::ResetStream { +426 id, +427 error_code, +428 final_offset: VarInt::try_from(stream.offset()).expect("impossibly large offset"), +429 }; +430 builder.write_frame(frame, stats); +431 } +432 +433 // STOP_SENDING +434 while builder.frame_space_remaining() > frame::StopSending::SIZE_BOUND { +435 let Some(frame) = pending.stop_sending.pop() else { +436 break; +437 }; +438 // We may need to transmit STOP_SENDING even for streams whose state we have discarded, +439 // because we are able to discard local state for stopped streams immediately upon +440 // receiving FIN, even if the peer still has arbitrarily large amounts of data to +441 // (re)transmit due to loss or unconventional sending strategy. We could fine-tune this +442 // a little by dropping the frame if we specifically know the stream's been reset by the +443 // peer, but we discard that information as soon as the application consumes it, so it +444 // can't be relied upon regardless. +445 builder.write_frame(frame, stats); +446 } +447 +448 // MAX_DATA +449 if pending.max_data && builder.frame_space_remaining() > 9 { +450 pending.max_data = false; 451 -452 if max > self.sent_max_data { -453 // Record that a `MAX_DATA` announcing a certain window was sent. This will -454 // suppress enqueuing further `MAX_DATA` frames unless either the previous -455 // transmission was not acknowledged or the window further increased. -456 self.sent_max_data = max; -457 } -458 -459 builder.write_frame(frame::MaxData(max), stats); -460 } -461 -462 // MAX_STREAM_DATA -463 while builder.frame_space_remaining() > 17 { -464 let id = match pending.max_stream_data.iter().next() { -465 Some(x) => *x, -466 None => break, -467 }; -468 pending.max_stream_data.remove(&id); -469 let Some(rs) = self -470 .recv -471 .get_mut(&id) -472 .and_then(|s| s.as_mut()) -473 .and_then(|s| s.as_open_recv_mut()) -474 else { -475 continue; -476 }; -477 if !rs.can_send_flow_control() { -478 continue; -479 } -480 -481 let (max, _) = rs.max_stream_data(self.stream_receive_window); -482 rs.record_sent_max_stream_data(max); -483 builder.write_frame(frame::MaxStreamData { id, offset: max }, stats); -484 } +452 // `local_max_data` can grow bigger than `VarInt`. +453 // For transmission inside QUIC frames we need to clamp it to the +454 // maximum allowed `VarInt` size. +455 let max = VarInt::try_from(self.local_max_data).unwrap_or(VarInt::MAX); +456 +457 if max > self.sent_max_data { +458 // Record that a `MAX_DATA` announcing a certain window was sent. This will +459 // suppress enqueuing further `MAX_DATA` frames unless either the previous +460 // transmission was not acknowledged or the window further increased. +461 self.sent_max_data = max; +462 } +463 +464 builder.write_frame(frame::MaxData(max), stats); +465 } +466 +467 // MAX_STREAM_DATA +468 while builder.frame_space_remaining() > 17 { +469 let id = match pending.max_stream_data.iter().next() { +470 Some(x) => *x, +471 None => break, +472 }; +473 pending.max_stream_data.remove(&id); +474 let Some(rs) = self +475 .recv +476 .get_mut(&id) +477 .and_then(|s| s.as_mut()) +478 .and_then(|s| s.as_open_recv_mut()) +479 else { +480 continue; +481 }; +482 if !rs.can_send_flow_control() { +483 continue; +484 } 485 -486 // MAX_STREAMS -487 for dir in Dir::iter() { -488 if !pending.max_stream_id[dir as usize] || builder.frame_space_remaining() <= 9 { -489 continue; -490 } -491 -492 pending.max_stream_id[dir as usize] = false; -493 self.sent_max_remote[dir as usize] = self.max_remote[dir as usize]; -494 let count = self.max_remote[dir as usize]; -495 builder.write_frame(frame::MaxStreams { dir, count }, stats); -496 } -497 -498 // STREAMS_BLOCKED -499 for dir in Dir::iter() { -500 if self.streams_blocked[dir as usize] { -501 pending.streams_blocked[dir as usize] = true; -502 self.streams_blocked[dir as usize] = false; -503 } -504 -505 if !pending.streams_blocked[dir as usize] || builder.frame_space_remaining() <= 9 { -506 continue; -507 } -508 -509 pending.streams_blocked[dir as usize] = false; -510 let limit = self.max[dir as usize]; -511 builder.write_frame(frame::StreamsBlocked { dir, limit }, stats); -512 } -513 } -514 -515 pub(in crate::connection) fn write_stream_frames<'a, 'b>( -516 &mut self, -517 builder: &mut PacketBuilder<'a, 'b>, -518 fair: bool, -519 stats: &mut FrameStats, -520 ) { -521 while builder.frame_space_remaining() > frame::Stream::SIZE_BOUND { -522 // Pop the stream of the highest priority that currently has pending data. If -523 // the stream still has some pending data left after writing, it will be -524 // reinserted, otherwise not -525 let Some(stream) = self.pending.pop() else { -526 break; -527 }; -528 -529 let id = stream.id; -530 -531 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { -532 // Stream was reset with pending data and the reset was acknowledged -533 continue; -534 }; +486 let (max, _) = rs.max_stream_data(self.stream_receive_window); +487 rs.record_sent_max_stream_data(max); +488 builder.write_frame(frame::MaxStreamData { id, offset: max }, stats); +489 } +490 +491 // MAX_STREAMS +492 for dir in Dir::iter() { +493 if !pending.max_stream_id[dir as usize] || builder.frame_space_remaining() <= 9 { +494 continue; +495 } +496 +497 pending.max_stream_id[dir as usize] = false; +498 self.sent_max_remote[dir as usize] = self.max_remote[dir as usize]; +499 let count = self.max_remote[dir as usize]; +500 builder.write_frame(frame::MaxStreams { dir, count }, stats); +501 } +502 +503 // STREAMS_BLOCKED +504 for dir in Dir::iter() { +505 if self.streams_blocked[dir as usize] { +506 pending.streams_blocked[dir as usize] = true; +507 self.streams_blocked[dir as usize] = false; +508 } +509 +510 if !pending.streams_blocked[dir as usize] || builder.frame_space_remaining() <= 9 { +511 continue; +512 } +513 +514 pending.streams_blocked[dir as usize] = false; +515 let limit = self.max[dir as usize]; +516 builder.write_frame(frame::StreamsBlocked { dir, limit }, stats); +517 } +518 } +519 +520 pub(in crate::connection) fn write_stream_frames<'a, 'b>( +521 &mut self, +522 builder: &mut PacketBuilder<'a, 'b>, +523 fair: bool, +524 stats: &mut FrameStats, +525 ) { +526 while builder.frame_space_remaining() > frame::Stream::SIZE_BOUND { +527 // Pop the stream of the highest priority that currently has pending data. If +528 // the stream still has some pending data left after writing, it will be +529 // reinserted, otherwise not +530 let Some(stream) = self.pending.pop() else { +531 break; +532 }; +533 +534 let id = stream.id; 535 -536 // Reset streams aren't removed from the pending list and still exist while the peer -537 // hasn't acknowledged the reset, but should not generate STREAM frames, so we need to -538 // check for them explicitly. -539 if stream.is_reset() { -540 continue; -541 } -542 -543 // Now that we know the `StreamId`, we can better account for how many bytes -544 // are required to encode it. -545 let max_buf_size = builder.frame_space_remaining() - 1 - VarInt::size(id.into()); -546 let (offsets, encode_length) = stream.pending.poll_transmit(max_buf_size); -547 let fin = offsets.end == stream.pending.offset() -548 && matches!(stream.state, SendState::DataSent { .. }); -549 if fin { -550 stream.fin_pending = false; -551 } -552 -553 if stream.is_pending() { -554 // If the stream still has pending data, reinsert it, possibly with an updated priority value -555 // Fairness with other streams is achieved by implementing round-robin scheduling, -556 // so that the other streams will have a chance to write data -557 // before we touch this stream again. -558 if fair { -559 self.pending.push_pending(id, stream.priority); -560 } else { -561 self.pending.reinsert_pending(id, stream.priority); -562 } -563 } -564 -565 let range = offsets.clone(); -566 let meta = frame::StreamMeta { id, offsets, fin }; -567 builder.write_frame(meta.encoder(encode_length), stats); -568 stream.pending.get_into(range, builder.buf); -569 } -570 } -571 -572 #[cfg(test)] -573 fn write_frames_for_test(&mut self, capacity: usize, fair: bool) -> frame::StreamMetaVec { -574 let buf = &mut Vec::with_capacity(capacity); -575 let mut tbuf = crate::connection::TransmitBuf::new(buf, std::num::NonZeroUsize::MIN, 1_200); -576 tbuf.start_new_datagram_with_size(capacity); -577 let builder = &mut PacketBuilder::simple_data_buf(&mut tbuf); -578 let stats = &mut FrameStats::default(); -579 self.write_stream_frames(builder, fair, stats); -580 builder.sent_frames().stream_frames.clone() -581 } -582 -583 /// Notify the application that new streams were opened or a stream became readable. -584 fn on_stream_frame(&mut self, notify_readable: bool, stream: StreamId) { -585 if stream.initiator() == self.side { -586 // Notifying about the opening of locally-initiated streams would be redundant. -587 if notify_readable { -588 self.events.push_back(StreamEvent::Readable { id: stream }); -589 } -590 return; -591 } -592 let next = &mut self.next_remote[stream.dir() as usize]; -593 if stream.index() >= *next { -594 *next = stream.index() + 1; -595 self.opened[stream.dir() as usize] = true; -596 } else if notify_readable { -597 self.events.push_back(StreamEvent::Readable { id: stream }); -598 } -599 } -600 -601 pub(crate) fn received_ack_of(&mut self, frame: frame::StreamMeta) { -602 let mut entry = match self.send.entry(frame.id) { -603 hash_map::Entry::Vacant(_) => return, -604 hash_map::Entry::Occupied(e) => e, -605 }; -606 -607 let Some(stream) = entry.get_mut().as_mut() else { -608 // Because we only call this after sending data on this stream, -609 // this closure should be unreachable. If we did somehow screw that up, -610 // then we might hit an underflow below with unpredictable effects down -611 // the line. Best to short-circuit. -612 return; -613 }; -614 -615 if stream.is_reset() { -616 // We account for outstanding data on reset streams at time of reset -617 return; -618 } -619 let id = frame.id; -620 self.unacked_data -= frame.offsets.end - frame.offsets.start; -621 if !stream.ack(frame) { -622 // The stream is unfinished or may still need retransmits -623 return; -624 } -625 -626 entry.remove_entry(); -627 self.stream_freed(id, StreamHalf::Send); -628 self.events.push_back(StreamEvent::Finished { id }); -629 } -630 -631 pub(crate) fn retransmit(&mut self, frame: frame::StreamMeta) { -632 let Some(stream) = self.send.get_mut(&frame.id).and_then(|s| s.as_mut()) else { -633 // Loss of data on a closed stream is a noop -634 return; -635 }; -636 if !stream.is_pending() { -637 self.pending.push_pending(frame.id, stream.priority); -638 } -639 stream.fin_pending |= frame.fin; -640 stream.pending.retransmit(frame.offsets); -641 } -642 -643 pub(crate) fn retransmit_all_for_0rtt(&mut self) { -644 for dir in Dir::iter() { -645 for index in 0..self.next[dir as usize] { -646 let id = StreamId::new(Side::Client, dir, index); -647 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { -648 continue; -649 }; -650 if stream.pending.is_fully_acked() && !stream.fin_pending { -651 // Stream data can't be acked in 0-RTT, so we must not have sent anything on -652 // this stream -653 continue; -654 } -655 if !stream.is_pending() { -656 self.pending.push_pending(id, stream.priority); -657 } -658 stream.pending.retransmit_all_for_0rtt(); -659 } -660 } -661 } -662 -663 pub(crate) fn received_max_streams( -664 &mut self, -665 dir: Dir, -666 count: u64, -667 ) -> Result<(), TransportError> { -668 if count > MAX_STREAM_COUNT { -669 return Err(TransportError::FRAME_ENCODING_ERROR( -670 "unrepresentable stream limit", -671 )); -672 } -673 -674 let current = &mut self.max[dir as usize]; -675 if count > *current { -676 *current = count; -677 self.streams_blocked[dir as usize] = false; -678 self.events.push_back(StreamEvent::Available { dir }); -679 } -680 -681 Ok(()) -682 } -683 -684 /// Handle increase to connection-level flow control limit -685 pub(crate) fn received_max_data(&mut self, n: VarInt) { -686 self.max_data = self.max_data.max(n.into()); -687 } -688 -689 pub(crate) fn received_max_stream_data( -690 &mut self, -691 id: StreamId, -692 offset: u64, -693 ) -> Result<(), TransportError> { -694 if id.initiator() != self.side && id.dir() == Dir::Uni { -695 debug!("got MAX_STREAM_DATA on recv-only {}", id); -696 return Err(TransportError::STREAM_STATE_ERROR( -697 "MAX_STREAM_DATA on recv-only stream", -698 )); -699 } -700 -701 let write_limit = self.write_limit(); -702 let max_send_data = self.max_send_data(id); -703 if let Some(ss) = self -704 .send -705 .get_mut(&id) -706 .map(get_or_insert_send(max_send_data)) -707 { -708 if ss.increase_max_data(offset) { -709 if write_limit > 0 { -710 self.events.push_back(StreamEvent::Writable { id }); -711 } else if !ss.connection_blocked { -712 // The stream is still blocked on the connection flow control -713 // window. In order to get unblocked when the window relaxes -714 // it needs to be in the connection blocked list. -715 ss.connection_blocked = true; -716 self.connection_blocked.push(id); -717 } -718 } -719 } else if id.initiator() == self.side && self.is_local_unopened(id) { -720 debug!("got MAX_STREAM_DATA on unopened {}", id); -721 return Err(TransportError::STREAM_STATE_ERROR( -722 "MAX_STREAM_DATA on unopened stream", -723 )); -724 } +536 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { +537 // Stream was reset with pending data and the reset was acknowledged +538 continue; +539 }; +540 +541 // Reset streams aren't removed from the pending list and still exist while the peer +542 // hasn't acknowledged the reset, but should not generate STREAM frames, so we need to +543 // check for them explicitly. +544 if stream.is_reset() { +545 continue; +546 } +547 +548 // Now that we know the `StreamId`, we can better account for how many bytes +549 // are required to encode it. +550 let max_buf_size = builder.frame_space_remaining() - 1 - VarInt::size(id.into()); +551 let (offsets, encode_length) = stream.pending.poll_transmit(max_buf_size); +552 let fin = offsets.end == stream.pending.offset() +553 && matches!(stream.state, SendState::DataSent { .. }); +554 if fin { +555 stream.fin_pending = false; +556 } +557 +558 if stream.is_pending() { +559 // If the stream still has pending data, reinsert it, possibly with an updated priority value +560 // Fairness with other streams is achieved by implementing round-robin scheduling, +561 // so that the other streams will have a chance to write data +562 // before we touch this stream again. +563 if fair { +564 self.pending.push_pending(id, stream.priority); +565 } else { +566 self.pending.reinsert_pending(id, stream.priority); +567 } +568 } +569 +570 let range = offsets.clone(); +571 let meta = frame::StreamMeta { id, offsets, fin }; +572 builder.write_frame(meta.encoder(encode_length), stats); +573 stream.pending.get_into(range, builder.buf); +574 } +575 } +576 +577 #[cfg(test)] +578 fn write_frames_for_test(&mut self, capacity: usize, fair: bool) -> frame::StreamMetaVec { +579 let buf = &mut Vec::with_capacity(capacity); +580 let mut tbuf = crate::connection::TransmitBuf::new(buf, std::num::NonZeroUsize::MIN, 1_200); +581 tbuf.start_new_datagram_with_size(capacity); +582 let builder = &mut PacketBuilder::simple_data_buf(&mut tbuf); +583 let stats = &mut FrameStats::default(); +584 self.write_stream_frames(builder, fair, stats); +585 builder.sent_frames().stream_frames.clone() +586 } +587 +588 pub(crate) fn received_ack_of(&mut self, frame: frame::StreamMeta) { +589 let mut entry = match self.send.entry(frame.id) { +590 hash_map::Entry::Vacant(_) => return, +591 hash_map::Entry::Occupied(e) => e, +592 }; +593 +594 let Some(stream) = entry.get_mut().as_mut() else { +595 // Because we only call this after sending data on this stream, +596 // this closure should be unreachable. If we did somehow screw that up, +597 // then we might hit an underflow below with unpredictable effects down +598 // the line. Best to short-circuit. +599 return; +600 }; +601 +602 if stream.is_reset() { +603 // We account for outstanding data on reset streams at time of reset +604 return; +605 } +606 let id = frame.id; +607 self.unacked_data -= frame.offsets.end - frame.offsets.start; +608 if !stream.ack(frame) { +609 // The stream is unfinished or may still need retransmits +610 return; +611 } +612 +613 entry.remove_entry(); +614 self.stream_freed(id, StreamHalf::Send); +615 self.events.push_back(StreamEvent::Finished { id }); +616 } +617 +618 pub(crate) fn retransmit(&mut self, frame: frame::StreamMeta) { +619 let Some(stream) = self.send.get_mut(&frame.id).and_then(|s| s.as_mut()) else { +620 // Loss of data on a closed stream is a noop +621 return; +622 }; +623 if !stream.is_pending() { +624 self.pending.push_pending(frame.id, stream.priority); +625 } +626 stream.fin_pending |= frame.fin; +627 stream.pending.retransmit(frame.offsets); +628 } +629 +630 pub(crate) fn retransmit_all_for_0rtt(&mut self) { +631 for dir in Dir::iter() { +632 for index in 0..self.next[dir as usize] { +633 let id = StreamId::new(Side::Client, dir, index); +634 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { +635 continue; +636 }; +637 if stream.pending.is_fully_acked() && !stream.fin_pending { +638 // Stream data can't be acked in 0-RTT, so we must not have sent anything on +639 // this stream +640 continue; +641 } +642 if !stream.is_pending() { +643 self.pending.push_pending(id, stream.priority); +644 } +645 stream.pending.retransmit_all_for_0rtt(); +646 } +647 } +648 } +649 +650 pub(crate) fn received_max_streams( +651 &mut self, +652 dir: Dir, +653 count: u64, +654 ) -> Result<(), TransportError> { +655 if count > MAX_STREAM_COUNT { +656 return Err(TransportError::FRAME_ENCODING_ERROR( +657 "unrepresentable stream limit", +658 )); +659 } +660 +661 let current = &mut self.max[dir as usize]; +662 if count > *current { +663 *current = count; +664 self.streams_blocked[dir as usize] = false; +665 self.events.push_back(StreamEvent::Available { dir }); +666 } +667 +668 Ok(()) +669 } +670 +671 /// Handle increase to connection-level flow control limit +672 pub(crate) fn received_max_data(&mut self, n: VarInt) { +673 self.max_data = self.max_data.max(n.into()); +674 } +675 +676 pub(crate) fn received_max_stream_data( +677 &mut self, +678 id: StreamId, +679 offset: u64, +680 ) -> Result<(), TransportError> { +681 if id.initiator() != self.side && id.dir() == Dir::Uni { +682 debug!("got MAX_STREAM_DATA on recv-only {}", id); +683 return Err(TransportError::STREAM_STATE_ERROR( +684 "MAX_STREAM_DATA on recv-only stream", +685 )); +686 } +687 +688 // Create state for this stream if the remote peer created it. +689 self.ensure_remote(id); +690 +691 let write_limit = self.write_limit(); +692 let max_send_data = self.max_send_data(id); +693 if let Some(ss) = self +694 .send +695 .get_mut(&id) +696 .map(get_or_insert_send(max_send_data)) +697 { +698 if ss.increase_max_data(offset) { +699 if write_limit > 0 { +700 self.events.push_back(StreamEvent::Writable { id }); +701 } else if !ss.connection_blocked { +702 // The stream is still blocked on the connection flow control +703 // window. In order to get unblocked when the window relaxes +704 // it needs to be in the connection blocked list. +705 ss.connection_blocked = true; +706 self.connection_blocked.push(id); +707 } +708 } +709 } else if id.initiator() == self.side && self.is_local_unopened(id) { +710 debug!("got MAX_STREAM_DATA on unopened {}", id); +711 return Err(TransportError::STREAM_STATE_ERROR( +712 "MAX_STREAM_DATA on unopened stream", +713 )); +714 } +715 +716 Ok(()) +717 } +718 +719 /// Returns the maximum amount of data this is allowed to be written on the connection +720 pub(crate) fn write_limit(&self) -> u64 { +721 (self.max_data - self.data_sent) +722 // `send_window` can be set after construction to something *less* than `unacked_data` +723 .min(self.send_window.saturating_sub(self.unacked_data)) +724 } 725 -726 self.on_stream_frame(false, id); -727 Ok(()) -728 } -729 -730 /// Returns the maximum amount of data this is allowed to be written on the connection -731 pub(crate) fn write_limit(&self) -> u64 { -732 (self.max_data - self.data_sent) -733 // `send_window` can be set after construction to something *less* than `unacked_data` -734 .min(self.send_window.saturating_sub(self.unacked_data)) -735 } -736 -737 /// Yield stream events -738 pub(crate) fn poll(&mut self) -> Option<StreamEvent> { -739 if let Some(dir) = Dir::iter().find(|&i| mem::replace(&mut self.opened[i as usize], false)) -740 { -741 return Some(StreamEvent::Opened { dir }); -742 } -743 -744 if self.write_limit() > 0 { -745 while let Some(id) = self.connection_blocked.pop() { -746 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { -747 continue; -748 }; +726 /// Yield stream events +727 pub(crate) fn poll(&mut self) -> Option<StreamEvent> { +728 if let Some(dir) = Dir::iter().find(|&i| mem::replace(&mut self.opened[i as usize], false)) +729 { +730 return Some(StreamEvent::Opened { dir }); +731 } +732 +733 if self.write_limit() > 0 { +734 while let Some(id) = self.connection_blocked.pop() { +735 let Some(stream) = self.send.get_mut(&id).and_then(|s| s.as_mut()) else { +736 continue; +737 }; +738 +739 debug_assert!(stream.connection_blocked); +740 stream.connection_blocked = false; +741 +742 // If it's no longer sensible to write to a stream (even to detect an error) then don't +743 // report it. +744 if stream.is_writable() && stream.max_data > stream.offset() { +745 return Some(StreamEvent::Writable { id }); +746 } +747 } +748 } 749 -750 debug_assert!(stream.connection_blocked); -751 stream.connection_blocked = false; +750 self.events.pop_front() +751 } 752 -753 // If it's no longer sensible to write to a stream (even to detect an error) then don't -754 // report it. -755 if stream.is_writable() && stream.max_data > stream.offset() { -756 return Some(StreamEvent::Writable { id }); -757 } -758 } -759 } -760 -761 self.events.pop_front() -762 } -763 -764 /// Queues MAX_STREAM_ID frames in `pending` if needed -765 /// -766 /// Returns whether any frames were queued. -767 pub(crate) fn queue_max_stream_id(&mut self, pending: &mut Retransmits) -> bool { -768 let mut queued = false; -769 for dir in Dir::iter() { -770 let diff = self.max_remote[dir as usize] - self.sent_max_remote[dir as usize]; -771 // To reduce traffic, only announce updates if at least 1/8 of the flow control window -772 // has been consumed. -773 if diff > self.max_concurrent_remote_count[dir as usize] / 8 { -774 pending.max_stream_id[dir as usize] = true; -775 queued = true; -776 } -777 } -778 queued -779 } -780 -781 /// Check for errors entailed by the peer's use of `id` as a send stream -782 fn validate_receive_id(&mut self, id: StreamId) -> Result<(), TransportError> { -783 if self.side == id.initiator() { -784 match id.dir() { -785 Dir::Uni => { -786 return Err(TransportError::STREAM_STATE_ERROR( -787 "illegal operation on send-only stream", -788 )); -789 } -790 Dir::Bi if id.index() >= self.next[Dir::Bi as usize] => { -791 return Err(TransportError::STREAM_STATE_ERROR( -792 "operation on unopened stream", -793 )); -794 } -795 Dir::Bi => {} -796 }; -797 } else { -798 let limit = self.max_remote[id.dir() as usize]; -799 if id.index() >= limit { -800 return Err(TransportError::STREAM_LIMIT_ERROR("")); -801 } -802 } -803 Ok(()) +753 /// Queues MAX_STREAM_ID frames in `pending` if needed +754 /// +755 /// Returns whether any frames were queued. +756 pub(crate) fn queue_max_stream_id(&mut self, pending: &mut Retransmits) -> bool { +757 let mut queued = false; +758 for dir in Dir::iter() { +759 let diff = self.max_remote[dir as usize] - self.sent_max_remote[dir as usize]; +760 // To reduce traffic, only announce updates if at least 1/8 of the flow control window +761 // has been consumed. +762 if diff > self.max_concurrent_remote_count[dir as usize] / 8 { +763 pending.max_stream_id[dir as usize] = true; +764 queued = true; +765 } +766 } +767 queued +768 } +769 +770 /// Check for errors entailed by the peer's use of `id` as a send stream +771 fn validate_receive_id(&mut self, id: StreamId) -> Result<(), TransportError> { +772 if self.side == id.initiator() { +773 match id.dir() { +774 Dir::Uni => { +775 return Err(TransportError::STREAM_STATE_ERROR( +776 "illegal operation on send-only stream", +777 )); +778 } +779 Dir::Bi if id.index() >= self.next[Dir::Bi as usize] => { +780 return Err(TransportError::STREAM_STATE_ERROR( +781 "operation on unopened stream", +782 )); +783 } +784 Dir::Bi => {} +785 }; +786 } else { +787 let limit = self.max_remote[id.dir() as usize]; +788 if id.index() >= limit { +789 return Err(TransportError::STREAM_LIMIT_ERROR("")); +790 } +791 } +792 Ok(()) +793 } +794 +795 /// Whether a locally initiated stream has never been open +796 pub(crate) fn is_local_unopened(&self, id: StreamId) -> bool { +797 id.index() >= self.next[id.dir() as usize] +798 } +799 +800 pub(crate) fn set_max_concurrent(&mut self, dir: Dir, count: VarInt) { +801 self.flow_control_adjusted = true; +802 self.max_concurrent_remote_count[dir as usize] = count.into(); +803 self.ensure_remote_streams(dir); 804 } 805 -806 /// Whether a locally initiated stream has never been open -807 pub(crate) fn is_local_unopened(&self, id: StreamId) -> bool { -808 id.index() >= self.next[id.dir() as usize] -809 } -810 -811 pub(crate) fn set_max_concurrent(&mut self, dir: Dir, count: VarInt) { -812 self.flow_control_adjusted = true; -813 self.max_concurrent_remote_count[dir as usize] = count.into(); -814 self.ensure_remote_streams(dir); -815 } -816 -817 pub(crate) fn max_concurrent(&self, dir: Dir) -> u64 { -818 self.allocated_remote_count[dir as usize] -819 } -820 -821 pub(crate) fn set_send_window(&mut self, send_window: u64) { -822 self.send_window = send_window; -823 } -824 -825 /// Set the receive_window and returns whether the receive_window has been -826 /// expanded or shrunk: true if expanded, false if shrunk. -827 pub(crate) fn set_receive_window(&mut self, receive_window: VarInt) -> bool { -828 let receive_window = receive_window.into(); -829 let mut expanded = false; -830 if receive_window > self.receive_window { -831 self.local_max_data = self -832 .local_max_data -833 .saturating_add(receive_window - self.receive_window); -834 expanded = true; -835 } else { -836 let diff = self.receive_window - receive_window; -837 self.receive_window_shrink_debt = self.receive_window_shrink_debt.saturating_add(diff); -838 } -839 self.receive_window = receive_window; -840 expanded +806 pub(crate) fn max_concurrent(&self, dir: Dir) -> u64 { +807 self.allocated_remote_count[dir as usize] +808 } +809 +810 pub(crate) fn set_send_window(&mut self, send_window: u64) { +811 self.send_window = send_window; +812 } +813 +814 /// Set the receive_window and returns whether the receive_window has been +815 /// expanded or shrunk: true if expanded, false if shrunk. +816 pub(crate) fn set_receive_window(&mut self, receive_window: VarInt) -> bool { +817 let receive_window = receive_window.into(); +818 let mut expanded = false; +819 if receive_window > self.receive_window { +820 self.local_max_data = self +821 .local_max_data +822 .saturating_add(receive_window - self.receive_window); +823 expanded = true; +824 } else { +825 let diff = self.receive_window - receive_window; +826 self.receive_window_shrink_debt = self.receive_window_shrink_debt.saturating_add(diff); +827 } +828 self.receive_window = receive_window; +829 expanded +830 } +831 +832 /// Insert `(id, None)` placeholders for a locally-initiated stream into `send` (and `recv` +833 /// for bidi). Called from `Streams::open`; the caller guarantees the id is fresh. +834 pub(super) fn insert_local(&mut self, id: StreamId) { +835 debug_assert_eq!(id.initiator(), self.side); +836 assert!(self.send.insert(id, None).is_none()); +837 if id.dir() == Dir::Bi { +838 let recv = self.free_recv.pop(); +839 assert!(self.recv.insert(id, recv).is_none()); +840 } 841 } 842 -843 pub(super) fn insert(&mut self, remote: bool, id: StreamId) { -844 let bi = id.dir() == Dir::Bi; -845 // bidirectional OR (unidirectional AND NOT remote) -846 if bi || !remote { -847 assert!(self.send.insert(id, None).is_none()); -848 } -849 // bidirectional OR (unidirectional AND remote) -850 if bi || remote { -851 let recv = self.free_recv.pop(); -852 assert!(self.recv.insert(id, recv).is_none()); -853 } -854 } -855 -856 /// Adds credits to the connection flow control window -857 /// -858 /// Returns whether a `MAX_DATA` frame should be enqueued as soon as possible. -859 /// This will only be the case if the window update would is significant -860 /// enough. As soon as a window update with a `MAX_DATA` frame has been -861 /// queued, the [`Recv::record_sent_max_stream_data`] function should be called to -862 /// suppress sending further updates until the window increases significantly -863 /// again. -864 pub(super) fn add_read_credits(&mut self, credits: u64) -> ShouldTransmit { -865 if credits > self.receive_window_shrink_debt { -866 let net_credits = credits - self.receive_window_shrink_debt; -867 self.local_max_data = self.local_max_data.saturating_add(net_credits); -868 self.receive_window_shrink_debt = 0; -869 } else { -870 self.receive_window_shrink_debt -= credits; +843 /// Allocate any new remote streams when a packet arrives for a stream id. +844 /// Any streams above `next_remote` are considered in the default state, avoiding allocations. +845 /// Once we receive a packet for a new remote stream, we advance `next_remote` and allocate actual state. +846 /// If there's a gap, we insert `None` placeholders for the missing streams. +847 /// Returns `true` if `id` was a newly allocated remote stream. +848 fn ensure_remote(&mut self, id: StreamId) -> bool { +849 let dir = id.dir(); +850 let dir_idx = dir as usize; +851 +852 // If we initiated this stream, nothing to do +853 if id.initiator() == self.side +854 // If this stream is larger than the max allowed, return. +855 // NOTE: STREAM/RESET_STREAM already enforces this, however STOP_SENDING/MAX_STREAM_DATA do not +856 || id.index() >= self.max_remote[dir_idx] +857 // If this stream has already been opened, nothing to do +858 || id.index() < self.next_remote[dir_idx] +859 { +860 return false; +861 } +862 +863 // Create all of the streams between the largest opened and this stream. +864 for i in self.next_remote[dir_idx]..=id.index() { +865 let id = StreamId::new(!self.side, dir, i); +866 let recv = self.free_recv.pop(); +867 assert!(self.recv.insert(id, recv).is_none()); +868 if dir == Dir::Bi { +869 assert!(self.send.insert(id, None).is_none()); +870 } 871 } -872 -873 if self.local_max_data > VarInt::MAX.into_inner() { -874 return ShouldTransmit(false); -875 } +872 self.next_remote[dir_idx] = id.index() + 1; +873 self.opened[dir_idx] = true; +874 true +875 } 876 -877 // Only announce a window update if it's significant enough -878 // to make it worthwhile sending a MAX_DATA frame. -879 // We use a fraction of the configured connection receive window to make -880 // the decision, to accommodate for connection using bigger windows requiring -881 // less updates. -882 let diff = self.local_max_data - self.sent_max_data.into_inner(); -883 ShouldTransmit(diff >= (self.receive_window / 8)) -884 } -885 -886 /// Update counters for removal of a stream -887 pub(super) fn stream_freed(&mut self, id: StreamId, half: StreamHalf) { -888 if id.initiator() != self.side { -889 let fully_free = id.dir() == Dir::Uni -890 || match half { -891 StreamHalf::Send => !self.recv.contains_key(&id), -892 StreamHalf::Recv => !self.send.contains_key(&id), -893 }; -894 if fully_free { -895 self.allocated_remote_count[id.dir() as usize] -= 1; -896 self.ensure_remote_streams(id.dir()); -897 } -898 } -899 if half == StreamHalf::Send { -900 self.send_streams -= 1; -901 } -902 } -903 -904 pub(super) fn stream_recv_freed(&mut self, id: StreamId, recv: StreamRecv) { -905 self.free_recv.push(recv.free(self.stream_receive_window)); -906 self.stream_freed(id, StreamHalf::Recv); -907 } -908 -909 pub(super) fn max_send_data(&self, id: StreamId) -> VarInt { -910 let remote = self.side != id.initiator(); -911 match id.dir() { -912 Dir::Uni => self.initial_max_stream_data_uni, -913 // Remote/local appear reversed here because the transport parameters are named from -914 // the perspective of the peer. -915 Dir::Bi if remote => self.initial_max_stream_data_bidi_local, -916 Dir::Bi => self.initial_max_stream_data_bidi_remote, -917 } -918 } -919} -920 -921#[inline] -922pub(super) fn get_or_insert_send( -923 max_data: VarInt, -924) -> impl Fn(&mut Option<Box<Send>>) -> &mut Box<Send> { -925 move |opt| opt.get_or_insert_with(|| Send::new(max_data)) -926} -927 -928#[inline] -929pub(super) fn get_or_insert_recv( -930 initial_max_data: u64, -931) -> impl FnMut(&mut Option<StreamRecv>) -> &mut Recv { -932 move |opt| { -933 *opt = opt.take().map(|s| match s { -934 StreamRecv::Free(recv) => StreamRecv::Open(recv), -935 s => s, -936 }); -937 opt.get_or_insert_with(|| StreamRecv::Open(Recv::new(initial_max_data))) -938 .as_open_recv_mut() -939 .unwrap() -940 } -941} -942 -943#[cfg(test)] -944mod tests { -945 use super::*; -946 use crate::{ -947 ReadableError, RecvStream, SendStream, TransportErrorCode, WriteError, -948 connection::State as ConnState, connection::Streams, -949 }; -950 use bytes::Bytes; -951 -952 fn make(side: Side) -> StreamsState { -953 StreamsState::new( -954 side, -955 128u32.into(), -956 128u32.into(), -957 1024 * 1024, -958 (1024 * 1024u32).into(), -959 (1024 * 1024u32).into(), -960 ) +877 /// Adds credits to the connection flow control window +878 /// +879 /// Returns whether a `MAX_DATA` frame should be enqueued as soon as possible. +880 /// This will only be the case if the window update would is significant +881 /// enough. As soon as a window update with a `MAX_DATA` frame has been +882 /// queued, the [`Recv::record_sent_max_stream_data`] function should be called to +883 /// suppress sending further updates until the window increases significantly +884 /// again. +885 pub(super) fn add_read_credits(&mut self, credits: u64) -> ShouldTransmit { +886 if credits > self.receive_window_shrink_debt { +887 let net_credits = credits - self.receive_window_shrink_debt; +888 self.local_max_data = self.local_max_data.saturating_add(net_credits); +889 self.receive_window_shrink_debt = 0; +890 } else { +891 self.receive_window_shrink_debt -= credits; +892 } +893 +894 if self.local_max_data > VarInt::MAX.into_inner() { +895 return ShouldTransmit(false); +896 } +897 +898 // Only announce a window update if it's significant enough +899 // to make it worthwhile sending a MAX_DATA frame. +900 // We use a fraction of the configured connection receive window to make +901 // the decision, to accommodate for connection using bigger windows requiring +902 // less updates. +903 let diff = self.local_max_data - self.sent_max_data.into_inner(); +904 ShouldTransmit(diff >= (self.receive_window / 8)) +905 } +906 +907 /// Update counters for removal of a stream +908 pub(super) fn stream_freed(&mut self, id: StreamId, half: StreamHalf) { +909 if id.initiator() != self.side { +910 let fully_free = id.dir() == Dir::Uni +911 || match half { +912 StreamHalf::Send => !self.recv.contains_key(&id), +913 StreamHalf::Recv => !self.send.contains_key(&id), +914 }; +915 if fully_free { +916 self.allocated_remote_count[id.dir() as usize] -= 1; +917 self.ensure_remote_streams(id.dir()); +918 } +919 } +920 if half == StreamHalf::Send { +921 self.send_streams -= 1; +922 } +923 } +924 +925 pub(super) fn stream_recv_freed(&mut self, id: StreamId, recv: StreamRecv) { +926 self.free_recv.push(recv.free(self.stream_receive_window)); +927 self.stream_freed(id, StreamHalf::Recv); +928 } +929 +930 pub(super) fn max_send_data(&self, id: StreamId) -> VarInt { +931 let remote = self.side != id.initiator(); +932 match id.dir() { +933 Dir::Uni => self.initial_max_stream_data_uni, +934 // Remote/local appear reversed here because the transport parameters are named from +935 // the perspective of the peer. +936 Dir::Bi if remote => self.initial_max_stream_data_bidi_local, +937 Dir::Bi => self.initial_max_stream_data_bidi_remote, +938 } +939 } +940} +941 +942#[inline] +943pub(super) fn get_or_insert_send( +944 max_data: VarInt, +945) -> impl Fn(&mut Option<Box<Send>>) -> &mut Box<Send> { +946 move |opt| opt.get_or_insert_with(|| Send::new(max_data)) +947} +948 +949#[inline] +950pub(super) fn get_or_insert_recv( +951 initial_max_data: u64, +952) -> impl FnMut(&mut Option<StreamRecv>) -> &mut Recv { +953 move |opt| { +954 *opt = opt.take().map(|s| match s { +955 StreamRecv::Free(recv) => StreamRecv::Open(recv), +956 s => s, +957 }); +958 opt.get_or_insert_with(|| StreamRecv::Open(Recv::new(initial_max_data))) +959 .as_open_recv_mut() +960 .unwrap() 961 } -962 -963 #[test] -964 fn trivial_flow_control() { -965 let mut client = StreamsState::new( -966 Side::Client, -967 1u32.into(), -968 1u32.into(), -969 1024 * 1024, -970 (1024 * 1024u32).into(), -971 (1024 * 1024u32).into(), -972 ); -973 let id = StreamId::new(Side::Server, Dir::Uni, 0); -974 let initial_max = client.local_max_data; -975 const MESSAGE_SIZE: usize = 2048; -976 assert_eq!( -977 client -978 .received( -979 frame::Stream { -980 id, -981 offset: 0, -982 fin: true, -983 data: Bytes::from_static(&[0; MESSAGE_SIZE]), -984 }, -985 2048 -986 ) -987 .unwrap(), -988 ShouldTransmit(false) -989 ); -990 assert_eq!(client.data_recvd, 2048); -991 assert_eq!(client.local_max_data - initial_max, 0); -992 -993 let mut pending = Retransmits::default(); -994 let mut recv = RecvStream { -995 id, -996 state: &mut client, -997 pending: &mut pending, -998 }; -999 -1000 let mut chunks = recv.read(true).unwrap(); -1001 assert_eq!( -1002 chunks.next(MESSAGE_SIZE).unwrap().unwrap().bytes.len(), -1003 MESSAGE_SIZE -1004 ); -1005 assert!(chunks.next(0).unwrap().is_none()); -1006 let should_transmit = chunks.finalize(); -1007 assert!(should_transmit.0); -1008 assert!(pending.max_stream_id[Dir::Uni as usize]); -1009 assert_eq!(client.local_max_data - initial_max, MESSAGE_SIZE as u64); -1010 } -1011 -1012 #[test] -1013 fn reset_flow_control() { -1014 let mut client = make(Side::Client); -1015 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1016 let initial_max = client.local_max_data; -1017 assert_eq!( -1018 client -1019 .received( -1020 frame::Stream { -1021 id, -1022 offset: 0, -1023 fin: false, -1024 data: Bytes::from_static(&[0; 2048]), -1025 }, -1026 2048 -1027 ) -1028 .unwrap(), -1029 ShouldTransmit(false) -1030 ); -1031 assert_eq!(client.data_recvd, 2048); -1032 assert_eq!(client.local_max_data - initial_max, 0); -1033 -1034 let mut pending = Retransmits::default(); -1035 let mut recv = RecvStream { -1036 id, -1037 state: &mut client, -1038 pending: &mut pending, -1039 }; -1040 -1041 let mut chunks = recv.read(true).unwrap(); -1042 chunks.next(1024).unwrap(); -1043 let _ = chunks.finalize(); -1044 assert_eq!(client.local_max_data - initial_max, 1024); -1045 assert_eq!( -1046 client -1047 .received_reset(frame::ResetStream { -1048 id, -1049 error_code: 0u32.into(), -1050 final_offset: 4096u32.into(), -1051 }) -1052 .unwrap(), -1053 ShouldTransmit(false) -1054 ); -1055 -1056 assert_eq!(client.data_recvd, 4096); -1057 assert_eq!(client.local_max_data - initial_max, 4096); -1058 -1059 // Ensure reading after a reset doesn't issue redundant credit -1060 let mut recv = RecvStream { -1061 id, -1062 state: &mut client, -1063 pending: &mut pending, -1064 }; -1065 let mut chunks = recv.read(true).unwrap(); +962} +963 +964#[cfg(test)] +965mod tests { +966 use super::*; +967 use crate::{ +968 ReadableError, RecvStream, SendStream, TransportErrorCode, WriteError, +969 connection::State as ConnState, connection::Streams, +970 }; +971 use bytes::Bytes; +972 +973 fn make(side: Side) -> StreamsState { +974 StreamsState::new( +975 side, +976 128u32.into(), +977 128u32.into(), +978 1024 * 1024, +979 (1024 * 1024u32).into(), +980 (1024 * 1024u32).into(), +981 ) +982 } +983 +984 #[test] +985 fn trivial_flow_control() { +986 let mut client = StreamsState::new( +987 Side::Client, +988 1u32.into(), +989 1u32.into(), +990 1024 * 1024, +991 (1024 * 1024u32).into(), +992 (1024 * 1024u32).into(), +993 ); +994 let id = StreamId::new(Side::Server, Dir::Uni, 0); +995 let initial_max = client.local_max_data; +996 const MESSAGE_SIZE: usize = 2048; +997 assert_eq!( +998 client +999 .received( +1000 frame::Stream { +1001 id, +1002 offset: 0, +1003 fin: true, +1004 data: Bytes::from_static(&[0; MESSAGE_SIZE]), +1005 }, +1006 2048 +1007 ) +1008 .unwrap(), +1009 ShouldTransmit(false) +1010 ); +1011 assert_eq!(client.data_recvd, 2048); +1012 assert_eq!(client.local_max_data - initial_max, 0); +1013 +1014 let mut pending = Retransmits::default(); +1015 let mut recv = RecvStream { +1016 id, +1017 state: &mut client, +1018 pending: &mut pending, +1019 }; +1020 +1021 let mut chunks = recv.read(true).unwrap(); +1022 assert_eq!( +1023 chunks.next(MESSAGE_SIZE).unwrap().unwrap().bytes.len(), +1024 MESSAGE_SIZE +1025 ); +1026 assert!(chunks.next(0).unwrap().is_none()); +1027 let should_transmit = chunks.finalize(); +1028 assert!(should_transmit.0); +1029 assert!(pending.max_stream_id[Dir::Uni as usize]); +1030 assert_eq!(client.local_max_data - initial_max, MESSAGE_SIZE as u64); +1031 } +1032 +1033 #[test] +1034 fn reset_flow_control() { +1035 let mut client = make(Side::Client); +1036 let id = StreamId::new(Side::Server, Dir::Uni, 0); +1037 let initial_max = client.local_max_data; +1038 assert_eq!( +1039 client +1040 .received( +1041 frame::Stream { +1042 id, +1043 offset: 0, +1044 fin: false, +1045 data: Bytes::from_static(&[0; 2048]), +1046 }, +1047 2048 +1048 ) +1049 .unwrap(), +1050 ShouldTransmit(false) +1051 ); +1052 assert_eq!(client.data_recvd, 2048); +1053 assert_eq!(client.local_max_data - initial_max, 0); +1054 +1055 let mut pending = Retransmits::default(); +1056 let mut recv = RecvStream { +1057 id, +1058 state: &mut client, +1059 pending: &mut pending, +1060 }; +1061 +1062 let mut chunks = recv.read(true).unwrap(); +1063 chunks.next(1024).unwrap(); +1064 let _ = chunks.finalize(); +1065 assert_eq!(client.local_max_data - initial_max, 1024); 1066 assert_eq!( -1067 chunks.next(1024).unwrap_err(), -1068 crate::ReadError::Reset(0u32.into()) -1069 ); -1070 let _ = chunks.finalize(); -1071 assert_eq!(client.data_recvd, 4096); -1072 assert_eq!(client.local_max_data - initial_max, 4096); -1073 } -1074 -1075 #[test] -1076 fn reset_after_empty_frame_flow_control() { -1077 let mut client = make(Side::Client); -1078 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1079 let initial_max = client.local_max_data; -1080 assert_eq!( -1081 client -1082 .received( -1083 frame::Stream { -1084 id, -1085 offset: 4096, -1086 fin: false, -1087 data: Bytes::from_static(&[0; 0]), -1088 }, -1089 0 -1090 ) -1091 .unwrap(), -1092 ShouldTransmit(false) -1093 ); -1094 assert_eq!(client.data_recvd, 4096); -1095 assert_eq!(client.local_max_data - initial_max, 0); -1096 assert_eq!( -1097 client -1098 .received_reset(frame::ResetStream { -1099 id, -1100 error_code: 0u32.into(), -1101 final_offset: 4096u32.into(), -1102 }) -1103 .unwrap(), -1104 ShouldTransmit(false) -1105 ); -1106 assert_eq!(client.data_recvd, 4096); -1107 assert_eq!(client.local_max_data - initial_max, 4096); -1108 } -1109 -1110 #[test] -1111 fn duplicate_reset_flow_control() { -1112 let mut client = make(Side::Client); -1113 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1114 assert_eq!( -1115 client -1116 .received_reset(frame::ResetStream { -1117 id, -1118 error_code: 0u32.into(), -1119 final_offset: 4096u32.into(), -1120 }) -1121 .unwrap(), -1122 ShouldTransmit(false) -1123 ); -1124 assert_eq!(client.data_recvd, 4096); -1125 assert_eq!( -1126 client -1127 .received_reset(frame::ResetStream { -1128 id, -1129 error_code: 0u32.into(), -1130 final_offset: 4096u32.into(), -1131 }) -1132 .unwrap(), -1133 ShouldTransmit(false) -1134 ); -1135 assert_eq!(client.data_recvd, 4096); -1136 } -1137 -1138 #[test] -1139 fn recv_stopped() { -1140 let mut client = make(Side::Client); -1141 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1142 let initial_max = client.local_max_data; -1143 assert_eq!( -1144 client -1145 .received( -1146 frame::Stream { -1147 id, -1148 offset: 0, -1149 fin: false, -1150 data: Bytes::from_static(&[0; 32]), -1151 }, -1152 32 -1153 ) -1154 .unwrap(), -1155 ShouldTransmit(false) -1156 ); -1157 assert_eq!(client.local_max_data, initial_max); +1067 client +1068 .received_reset(frame::ResetStream { +1069 id, +1070 error_code: 0u32.into(), +1071 final_offset: 4096u32.into(), +1072 }) +1073 .unwrap(), +1074 ShouldTransmit(false) +1075 ); +1076 +1077 assert_eq!(client.data_recvd, 4096); +1078 assert_eq!(client.local_max_data - initial_max, 4096); +1079 +1080 // Ensure reading after a reset doesn't issue redundant credit +1081 let mut recv = RecvStream { +1082 id, +1083 state: &mut client, +1084 pending: &mut pending, +1085 }; +1086 let mut chunks = recv.read(true).unwrap(); +1087 assert_eq!( +1088 chunks.next(1024).unwrap_err(), +1089 crate::ReadError::Reset(0u32.into()) +1090 ); +1091 let _ = chunks.finalize(); +1092 assert_eq!(client.data_recvd, 4096); +1093 assert_eq!(client.local_max_data - initial_max, 4096); +1094 } +1095 +1096 #[test] +1097 fn reset_after_empty_frame_flow_control() { +1098 let mut client = make(Side::Client); +1099 let id = StreamId::new(Side::Server, Dir::Uni, 0); +1100 let initial_max = client.local_max_data; +1101 assert_eq!( +1102 client +1103 .received( +1104 frame::Stream { +1105 id, +1106 offset: 4096, +1107 fin: false, +1108 data: Bytes::from_static(&[0; 0]), +1109 }, +1110 0 +1111 ) +1112 .unwrap(), +1113 ShouldTransmit(false) +1114 ); +1115 assert_eq!(client.data_recvd, 4096); +1116 assert_eq!(client.local_max_data - initial_max, 0); +1117 assert_eq!( +1118 client +1119 .received_reset(frame::ResetStream { +1120 id, +1121 error_code: 0u32.into(), +1122 final_offset: 4096u32.into(), +1123 }) +1124 .unwrap(), +1125 ShouldTransmit(false) +1126 ); +1127 assert_eq!(client.data_recvd, 4096); +1128 assert_eq!(client.local_max_data - initial_max, 4096); +1129 } +1130 +1131 #[test] +1132 fn duplicate_reset_flow_control() { +1133 let mut client = make(Side::Client); +1134 let id = StreamId::new(Side::Server, Dir::Uni, 0); +1135 assert_eq!( +1136 client +1137 .received_reset(frame::ResetStream { +1138 id, +1139 error_code: 0u32.into(), +1140 final_offset: 4096u32.into(), +1141 }) +1142 .unwrap(), +1143 ShouldTransmit(false) +1144 ); +1145 assert_eq!(client.data_recvd, 4096); +1146 assert_eq!( +1147 client +1148 .received_reset(frame::ResetStream { +1149 id, +1150 error_code: 0u32.into(), +1151 final_offset: 4096u32.into(), +1152 }) +1153 .unwrap(), +1154 ShouldTransmit(false) +1155 ); +1156 assert_eq!(client.data_recvd, 4096); +1157 } 1158 -1159 let mut pending = Retransmits::default(); -1160 let mut recv = RecvStream { -1161 id, -1162 state: &mut client, -1163 pending: &mut pending, -1164 }; -1165 -1166 recv.stop(0u32.into()).unwrap(); -1167 assert_eq!(recv.pending.stop_sending.len(), 1); -1168 assert!(!recv.pending.max_data); -1169 -1170 assert!(recv.stop(0u32.into()).is_err()); -1171 assert_eq!(recv.read(true).err(), Some(ReadableError::ClosedStream)); -1172 assert_eq!(recv.read(false).err(), Some(ReadableError::ClosedStream)); -1173 -1174 assert_eq!(client.local_max_data - initial_max, 32); -1175 assert_eq!( -1176 client -1177 .received( -1178 frame::Stream { -1179 id, -1180 offset: 32, -1181 fin: true, -1182 data: Bytes::from_static(&[0; 16]), -1183 }, -1184 16 -1185 ) -1186 .unwrap(), -1187 ShouldTransmit(false) -1188 ); -1189 assert_eq!(client.local_max_data - initial_max, 48); -1190 assert!(!client.recv.contains_key(&id)); -1191 } -1192 -1193 #[test] -1194 fn stopped_reset() { -1195 let mut client = make(Side::Client); -1196 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1197 // Server opens stream -1198 assert_eq!( -1199 client -1200 .received( -1201 frame::Stream { -1202 id, -1203 offset: 0, -1204 fin: false, -1205 data: Bytes::from_static(&[0; 32]) -1206 }, -1207 32 -1208 ) -1209 .unwrap(), -1210 ShouldTransmit(false) -1211 ); -1212 -1213 let mut pending = Retransmits::default(); -1214 let mut recv = RecvStream { -1215 id, -1216 state: &mut client, -1217 pending: &mut pending, -1218 }; -1219 -1220 recv.stop(0u32.into()).unwrap(); -1221 assert_eq!(pending.stop_sending.len(), 1); -1222 assert!(!pending.max_data); -1223 -1224 // Server complies -1225 let prev_max = client.max_remote[Dir::Uni as usize]; -1226 assert_eq!( -1227 client -1228 .received_reset(frame::ResetStream { -1229 id, -1230 error_code: 0u32.into(), -1231 final_offset: 32u32.into(), -1232 }) -1233 .unwrap(), -1234 ShouldTransmit(false) -1235 ); -1236 assert!(!client.recv.contains_key(&id), "stream state is freed"); -1237 assert_eq!(client.max_remote[Dir::Uni as usize], prev_max + 1); -1238 } -1239 -1240 #[test] -1241 fn send_stopped() { -1242 let mut server = make(Side::Server); -1243 server.set_params(&TransportParameters { -1244 initial_max_streams_uni: 1u32.into(), -1245 initial_max_data: 42u32.into(), -1246 initial_max_stream_data_uni: 42u32.into(), -1247 ..TransportParameters::default() -1248 }); -1249 -1250 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1251 let id = Streams { -1252 state: &mut server, -1253 conn_state: &state, -1254 } -1255 .open(Dir::Uni) -1256 .unwrap(); -1257 -1258 let mut stream = SendStream { -1259 id, -1260 state: &mut server, -1261 pending: &mut pending, -1262 conn_state: &state, -1263 }; -1264 -1265 let error_code = 0u32.into(); -1266 stream.state.received_stop_sending(id, error_code); -1267 assert!( -1268 stream -1269 .state -1270 .events -1271 .contains(&StreamEvent::Stopped { id, error_code }) -1272 ); -1273 stream.state.events.clear(); -1274 -1275 assert_eq!(stream.write(&[]), Err(WriteError::Stopped(error_code))); -1276 -1277 stream.reset(0u32.into()).unwrap(); -1278 assert_eq!(stream.write(&[]), Err(WriteError::ClosedStream)); -1279 -1280 // A duplicate frame is a no-op -1281 stream.state.received_stop_sending(id, error_code); -1282 assert!(stream.state.events.is_empty()); -1283 } -1284 -1285 #[test] -1286 fn final_offset_flow_control() { -1287 let mut client = make(Side::Client); -1288 assert_eq!( -1289 client -1290 .received_reset(frame::ResetStream { -1291 id: StreamId::new(Side::Server, Dir::Uni, 0), -1292 error_code: 0u32.into(), -1293 final_offset: VarInt::MAX, -1294 }) -1295 .unwrap_err() -1296 .code, -1297 TransportErrorCode::FLOW_CONTROL_ERROR -1298 ); -1299 } +1159 #[test] +1160 fn recv_stopped() { +1161 let mut client = make(Side::Client); +1162 let id = StreamId::new(Side::Server, Dir::Uni, 0); +1163 let initial_max = client.local_max_data; +1164 assert_eq!( +1165 client +1166 .received( +1167 frame::Stream { +1168 id, +1169 offset: 0, +1170 fin: false, +1171 data: Bytes::from_static(&[0; 32]), +1172 }, +1173 32 +1174 ) +1175 .unwrap(), +1176 ShouldTransmit(false) +1177 ); +1178 assert_eq!(client.local_max_data, initial_max); +1179 +1180 let mut pending = Retransmits::default(); +1181 let mut recv = RecvStream { +1182 id, +1183 state: &mut client, +1184 pending: &mut pending, +1185 }; +1186 +1187 recv.stop(0u32.into()).unwrap(); +1188 assert_eq!(recv.pending.stop_sending.len(), 1); +1189 assert!(!recv.pending.max_data); +1190 +1191 assert!(recv.stop(0u32.into()).is_err()); +1192 assert_eq!(recv.read(true).err(), Some(ReadableError::ClosedStream)); +1193 assert_eq!(recv.read(false).err(), Some(ReadableError::ClosedStream)); +1194 +1195 assert_eq!(client.local_max_data - initial_max, 32); +1196 assert_eq!( +1197 client +1198 .received( +1199 frame::Stream { +1200 id, +1201 offset: 32, +1202 fin: true, +1203 data: Bytes::from_static(&[0; 16]), +1204 }, +1205 16 +1206 ) +1207 .unwrap(), +1208 ShouldTransmit(false) +1209 ); +1210 assert_eq!(client.local_max_data - initial_max, 48); +1211 assert!(!client.recv.contains_key(&id)); +1212 } +1213 +1214 #[test] +1215 fn stopped_reset() { +1216 let mut client = make(Side::Client); +1217 let id = StreamId::new(Side::Server, Dir::Uni, 0); +1218 // Server opens stream +1219 assert_eq!( +1220 client +1221 .received( +1222 frame::Stream { +1223 id, +1224 offset: 0, +1225 fin: false, +1226 data: Bytes::from_static(&[0; 32]) +1227 }, +1228 32 +1229 ) +1230 .unwrap(), +1231 ShouldTransmit(false) +1232 ); +1233 +1234 let mut pending = Retransmits::default(); +1235 let mut recv = RecvStream { +1236 id, +1237 state: &mut client, +1238 pending: &mut pending, +1239 }; +1240 +1241 recv.stop(0u32.into()).unwrap(); +1242 assert_eq!(pending.stop_sending.len(), 1); +1243 assert!(!pending.max_data); +1244 +1245 // Server complies +1246 let prev_max = client.max_remote[Dir::Uni as usize]; +1247 assert_eq!( +1248 client +1249 .received_reset(frame::ResetStream { +1250 id, +1251 error_code: 0u32.into(), +1252 final_offset: 32u32.into(), +1253 }) +1254 .unwrap(), +1255 ShouldTransmit(false) +1256 ); +1257 assert!(!client.recv.contains_key(&id), "stream state is freed"); +1258 assert_eq!(client.max_remote[Dir::Uni as usize], prev_max + 1); +1259 } +1260 +1261 #[test] +1262 fn send_stopped() { +1263 let mut server = make(Side::Server); +1264 server.set_params(&TransportParameters { +1265 initial_max_streams_uni: 1u32.into(), +1266 initial_max_data: 42u32.into(), +1267 initial_max_stream_data_uni: 42u32.into(), +1268 ..TransportParameters::default() +1269 }); +1270 +1271 let (mut pending, state) = (Retransmits::default(), ConnState::established()); +1272 let id = Streams { +1273 state: &mut server, +1274 conn_state: &state, +1275 } +1276 .open(Dir::Uni) +1277 .unwrap(); +1278 +1279 let mut stream = SendStream { +1280 id, +1281 state: &mut server, +1282 pending: &mut pending, +1283 conn_state: &state, +1284 }; +1285 +1286 let error_code = 0u32.into(); +1287 stream.state.received_stop_sending(id, error_code); +1288 assert!( +1289 stream +1290 .state +1291 .events +1292 .contains(&StreamEvent::Stopped { id, error_code }) +1293 ); +1294 stream.state.events.clear(); +1295 +1296 assert_eq!(stream.write(&[]), Err(WriteError::Stopped(error_code))); +1297 +1298 stream.reset(0u32.into()).unwrap(); +1299 assert_eq!(stream.write(&[]), Err(WriteError::ClosedStream)); 1300 -1301 #[test] -1302 fn stream_priority() { -1303 let mut server = make(Side::Server); -1304 server.set_params(&TransportParameters { -1305 initial_max_streams_bidi: 3u32.into(), -1306 initial_max_data: 10u32.into(), -1307 initial_max_stream_data_bidi_remote: 10u32.into(), -1308 ..TransportParameters::default() -1309 }); -1310 -1311 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1312 let mut streams = Streams { -1313 state: &mut server, -1314 conn_state: &state, -1315 }; -1316 -1317 let id_high = streams.open(Dir::Bi).unwrap(); -1318 let id_mid = streams.open(Dir::Bi).unwrap(); -1319 let id_low = streams.open(Dir::Bi).unwrap(); -1320 -1321 let mut mid = SendStream { -1322 id: id_mid, -1323 state: &mut server, -1324 pending: &mut pending, -1325 conn_state: &state, -1326 }; -1327 mid.write(b"mid").unwrap(); -1328 -1329 let mut low = SendStream { -1330 id: id_low, -1331 state: &mut server, -1332 pending: &mut pending, -1333 conn_state: &state, -1334 }; -1335 low.set_priority(-1).unwrap(); -1336 low.write(b"low").unwrap(); +1301 // A duplicate frame is a no-op +1302 stream.state.received_stop_sending(id, error_code); +1303 assert!(stream.state.events.is_empty()); +1304 } +1305 +1306 #[test] +1307 fn final_offset_flow_control() { +1308 let mut client = make(Side::Client); +1309 assert_eq!( +1310 client +1311 .received_reset(frame::ResetStream { +1312 id: StreamId::new(Side::Server, Dir::Uni, 0), +1313 error_code: 0u32.into(), +1314 final_offset: VarInt::MAX, +1315 }) +1316 .unwrap_err() +1317 .code, +1318 TransportErrorCode::FLOW_CONTROL_ERROR +1319 ); +1320 } +1321 +1322 #[test] +1323 fn stream_priority() { +1324 let mut server = make(Side::Server); +1325 server.set_params(&TransportParameters { +1326 initial_max_streams_bidi: 3u32.into(), +1327 initial_max_data: 10u32.into(), +1328 initial_max_stream_data_bidi_remote: 10u32.into(), +1329 ..TransportParameters::default() +1330 }); +1331 +1332 let (mut pending, state) = (Retransmits::default(), ConnState::established()); +1333 let mut streams = Streams { +1334 state: &mut server, +1335 conn_state: &state, +1336 }; 1337 -1338 let mut high = SendStream { -1339 id: id_high, -1340 state: &mut server, -1341 pending: &mut pending, -1342 conn_state: &state, -1343 }; -1344 high.set_priority(1).unwrap(); -1345 high.write(b"high").unwrap(); -1346 -1347 let meta = server.write_frames_for_test(40, true); -1348 assert_eq!(meta[0].id, id_high); -1349 assert_eq!(meta[1].id, id_mid); -1350 assert_eq!(meta[2].id, id_low); -1351 -1352 assert!(!server.can_send_stream_data()); -1353 assert_eq!(server.pending.len(), 0); -1354 } -1355 -1356 #[test] -1357 fn requeue_stream_priority() { -1358 let mut server = make(Side::Server); -1359 server.set_params(&TransportParameters { -1360 initial_max_streams_bidi: 3u32.into(), -1361 initial_max_data: 1000u32.into(), -1362 initial_max_stream_data_bidi_remote: 1000u32.into(), -1363 ..TransportParameters::default() -1364 }); -1365 -1366 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1367 let mut streams = Streams { -1368 state: &mut server, -1369 conn_state: &state, -1370 }; -1371 -1372 let id_high = streams.open(Dir::Bi).unwrap(); -1373 let id_mid = streams.open(Dir::Bi).unwrap(); -1374 -1375 let mut mid = SendStream { -1376 id: id_mid, -1377 state: &mut server, -1378 pending: &mut pending, -1379 conn_state: &state, -1380 }; -1381 assert_eq!(mid.write(b"mid").unwrap(), 3); -1382 assert_eq!(server.pending.len(), 1); -1383 -1384 let mut high = SendStream { -1385 id: id_high, -1386 state: &mut server, -1387 pending: &mut pending, -1388 conn_state: &state, -1389 }; -1390 high.set_priority(1).unwrap(); -1391 assert_eq!(high.write(&[0; 200]).unwrap(), 200); -1392 assert_eq!(server.pending.len(), 2); -1393 -1394 // Requeue the high priority stream to lowest priority. The initial send -1395 // still uses high priority since it's queued that way. After that it will -1396 // switch to low priority -1397 let mut high = SendStream { -1398 id: id_high, -1399 state: &mut server, -1400 pending: &mut pending, -1401 conn_state: &state, -1402 }; -1403 high.set_priority(-1).unwrap(); +1338 let id_high = streams.open(Dir::Bi).unwrap(); +1339 let id_mid = streams.open(Dir::Bi).unwrap(); +1340 let id_low = streams.open(Dir::Bi).unwrap(); +1341 +1342 let mut mid = SendStream { +1343 id: id_mid, +1344 state: &mut server, +1345 pending: &mut pending, +1346 conn_state: &state, +1347 }; +1348 mid.write(b"mid").unwrap(); +1349 +1350 let mut low = SendStream { +1351 id: id_low, +1352 state: &mut server, +1353 pending: &mut pending, +1354 conn_state: &state, +1355 }; +1356 low.set_priority(-1).unwrap(); +1357 low.write(b"low").unwrap(); +1358 +1359 let mut high = SendStream { +1360 id: id_high, +1361 state: &mut server, +1362 pending: &mut pending, +1363 conn_state: &state, +1364 }; +1365 high.set_priority(1).unwrap(); +1366 high.write(b"high").unwrap(); +1367 +1368 let meta = server.write_frames_for_test(40, true); +1369 assert_eq!(meta[0].id, id_high); +1370 assert_eq!(meta[1].id, id_mid); +1371 assert_eq!(meta[2].id, id_low); +1372 +1373 assert!(!server.can_send_stream_data()); +1374 assert_eq!(server.pending.len(), 0); +1375 } +1376 +1377 #[test] +1378 fn requeue_stream_priority() { +1379 let mut server = make(Side::Server); +1380 server.set_params(&TransportParameters { +1381 initial_max_streams_bidi: 3u32.into(), +1382 initial_max_data: 1000u32.into(), +1383 initial_max_stream_data_bidi_remote: 1000u32.into(), +1384 ..TransportParameters::default() +1385 }); +1386 +1387 let (mut pending, state) = (Retransmits::default(), ConnState::established()); +1388 let mut streams = Streams { +1389 state: &mut server, +1390 conn_state: &state, +1391 }; +1392 +1393 let id_high = streams.open(Dir::Bi).unwrap(); +1394 let id_mid = streams.open(Dir::Bi).unwrap(); +1395 +1396 let mut mid = SendStream { +1397 id: id_mid, +1398 state: &mut server, +1399 pending: &mut pending, +1400 conn_state: &state, +1401 }; +1402 assert_eq!(mid.write(b"mid").unwrap(), 3); +1403 assert_eq!(server.pending.len(), 1); 1404 -1405 let meta = server.write_frames_for_test(40, true); -1406 assert_eq!(meta.len(), 1); -1407 assert_eq!(meta[0].id, id_high); -1408 -1409 // After requeuing we should end up with 2 priorities - not 3 -1410 assert_eq!(server.pending.len(), 2); -1411 -1412 // Send the remaining data. The initial mid priority one should go first now -1413 let meta = server.write_frames_for_test(1000 - 40, true); -1414 assert_eq!(meta.len(), 2); -1415 assert_eq!(meta[0].id, id_mid); -1416 assert_eq!(meta[1].id, id_high); -1417 -1418 assert!(!server.can_send_stream_data()); -1419 assert_eq!(server.pending.len(), 0); -1420 } -1421 -1422 #[test] -1423 fn same_stream_priority() { -1424 for fair in [true, false] { -1425 let mut server = make(Side::Server); -1426 server.set_params(&TransportParameters { -1427 initial_max_streams_bidi: 3u32.into(), -1428 initial_max_data: 300u32.into(), -1429 initial_max_stream_data_bidi_remote: 300u32.into(), -1430 ..TransportParameters::default() -1431 }); +1405 let mut high = SendStream { +1406 id: id_high, +1407 state: &mut server, +1408 pending: &mut pending, +1409 conn_state: &state, +1410 }; +1411 high.set_priority(1).unwrap(); +1412 assert_eq!(high.write(&[0; 200]).unwrap(), 200); +1413 assert_eq!(server.pending.len(), 2); +1414 +1415 // Requeue the high priority stream to lowest priority. The initial send +1416 // still uses high priority since it's queued that way. After that it will +1417 // switch to low priority +1418 let mut high = SendStream { +1419 id: id_high, +1420 state: &mut server, +1421 pending: &mut pending, +1422 conn_state: &state, +1423 }; +1424 high.set_priority(-1).unwrap(); +1425 +1426 let meta = server.write_frames_for_test(40, true); +1427 assert_eq!(meta.len(), 1); +1428 assert_eq!(meta[0].id, id_high); +1429 +1430 // After requeuing we should end up with 2 priorities - not 3 +1431 assert_eq!(server.pending.len(), 2); 1432 -1433 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1434 let mut streams = Streams { -1435 state: &mut server, -1436 conn_state: &state, -1437 }; +1433 // Send the remaining data. The initial mid priority one should go first now +1434 let meta = server.write_frames_for_test(1000 - 40, true); +1435 assert_eq!(meta.len(), 2); +1436 assert_eq!(meta[0].id, id_mid); +1437 assert_eq!(meta[1].id, id_high); 1438 -1439 // a, b and c all have the same priority -1440 let id_a = streams.open(Dir::Bi).unwrap(); -1441 let id_b = streams.open(Dir::Bi).unwrap(); -1442 let id_c = streams.open(Dir::Bi).unwrap(); -1443 -1444 let mut stream_a = SendStream { -1445 id: id_a, -1446 state: &mut server, -1447 pending: &mut pending, -1448 conn_state: &state, -1449 }; -1450 stream_a.write(&[b'a'; 100]).unwrap(); -1451 -1452 let mut stream_b = SendStream { -1453 id: id_b, -1454 state: &mut server, -1455 pending: &mut pending, -1456 conn_state: &state, -1457 }; -1458 stream_b.write(&[b'b'; 100]).unwrap(); +1439 assert!(!server.can_send_stream_data()); +1440 assert_eq!(server.pending.len(), 0); +1441 } +1442 +1443 #[test] +1444 fn same_stream_priority() { +1445 for fair in [true, false] { +1446 let mut server = make(Side::Server); +1447 server.set_params(&TransportParameters { +1448 initial_max_streams_bidi: 3u32.into(), +1449 initial_max_data: 300u32.into(), +1450 initial_max_stream_data_bidi_remote: 300u32.into(), +1451 ..TransportParameters::default() +1452 }); +1453 +1454 let (mut pending, state) = (Retransmits::default(), ConnState::established()); +1455 let mut streams = Streams { +1456 state: &mut server, +1457 conn_state: &state, +1458 }; 1459 -1460 let mut stream_c = SendStream { -1461 id: id_c, -1462 state: &mut server, -1463 pending: &mut pending, -1464 conn_state: &state, -1465 }; -1466 stream_c.write(&[b'c'; 100]).unwrap(); -1467 -1468 let mut metas = vec![]; -1469 -1470 // loop until all the streams are written -1471 loop { -1472 let meta = server.write_frames_for_test(40, fair); -1473 if meta.is_empty() { -1474 break; -1475 } -1476 metas.extend(meta); -1477 } -1478 -1479 assert!(!server.can_send_stream_data()); -1480 assert_eq!(server.pending.len(), 0); -1481 -1482 let stream_ids = metas.iter().map(|m| m.id).collect::<Vec<_>>(); -1483 if fair { -1484 // When fairness is enabled, if we run out of buffer space to write out a stream, -1485 // the stream is re-queued after all the streams with the same priority. -1486 assert_eq!( -1487 stream_ids, -1488 vec![id_a, id_b, id_c, id_a, id_b, id_c, id_a, id_b, id_c] -1489 ); -1490 } else { -1491 // When fairness is disabled the stream is re-queued before all the other streams -1492 // with the same priority. -1493 assert_eq!( -1494 stream_ids, -1495 vec![id_a, id_a, id_a, id_b, id_b, id_b, id_c, id_c, id_c] -1496 ); -1497 } -1498 } -1499 } -1500 -1501 #[test] -1502 fn unfair_priority_bump() { -1503 let mut server = make(Side::Server); -1504 server.set_params(&TransportParameters { -1505 initial_max_streams_bidi: 3u32.into(), -1506 initial_max_data: 300u32.into(), -1507 initial_max_stream_data_bidi_remote: 300u32.into(), -1508 ..TransportParameters::default() -1509 }); -1510 -1511 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1512 let mut streams = Streams { -1513 state: &mut server, -1514 conn_state: &state, -1515 }; -1516 -1517 // a, and b have the same priority, c has higher priority -1518 let id_a = streams.open(Dir::Bi).unwrap(); -1519 let id_b = streams.open(Dir::Bi).unwrap(); -1520 let id_c = streams.open(Dir::Bi).unwrap(); +1460 // a, b and c all have the same priority +1461 let id_a = streams.open(Dir::Bi).unwrap(); +1462 let id_b = streams.open(Dir::Bi).unwrap(); +1463 let id_c = streams.open(Dir::Bi).unwrap(); +1464 +1465 let mut stream_a = SendStream { +1466 id: id_a, +1467 state: &mut server, +1468 pending: &mut pending, +1469 conn_state: &state, +1470 }; +1471 stream_a.write(&[b'a'; 100]).unwrap(); +1472 +1473 let mut stream_b = SendStream { +1474 id: id_b, +1475 state: &mut server, +1476 pending: &mut pending, +1477 conn_state: &state, +1478 }; +1479 stream_b.write(&[b'b'; 100]).unwrap(); +1480 +1481 let mut stream_c = SendStream { +1482 id: id_c, +1483 state: &mut server, +1484 pending: &mut pending, +1485 conn_state: &state, +1486 }; +1487 stream_c.write(&[b'c'; 100]).unwrap(); +1488 +1489 let mut metas = vec![]; +1490 +1491 // loop until all the streams are written +1492 loop { +1493 let meta = server.write_frames_for_test(40, fair); +1494 if meta.is_empty() { +1495 break; +1496 } +1497 metas.extend(meta); +1498 } +1499 +1500 assert!(!server.can_send_stream_data()); +1501 assert_eq!(server.pending.len(), 0); +1502 +1503 let stream_ids = metas.iter().map(|m| m.id).collect::<Vec<_>>(); +1504 if fair { +1505 // When fairness is enabled, if we run out of buffer space to write out a stream, +1506 // the stream is re-queued after all the streams with the same priority. +1507 assert_eq!( +1508 stream_ids, +1509 vec![id_a, id_b, id_c, id_a, id_b, id_c, id_a, id_b, id_c] +1510 ); +1511 } else { +1512 // When fairness is disabled the stream is re-queued before all the other streams +1513 // with the same priority. +1514 assert_eq!( +1515 stream_ids, +1516 vec![id_a, id_a, id_a, id_b, id_b, id_b, id_c, id_c, id_c] +1517 ); +1518 } +1519 } +1520 } 1521 -1522 let mut stream_a = SendStream { -1523 id: id_a, -1524 state: &mut server, -1525 pending: &mut pending, -1526 conn_state: &state, -1527 }; -1528 stream_a.write(&[b'a'; 100]).unwrap(); -1529 -1530 let mut stream_b = SendStream { -1531 id: id_b, -1532 state: &mut server, -1533 pending: &mut pending, -1534 conn_state: &state, -1535 }; -1536 stream_b.write(&[b'b'; 100]).unwrap(); +1522 #[test] +1523 fn unfair_priority_bump() { +1524 let mut server = make(Side::Server); +1525 server.set_params(&TransportParameters { +1526 initial_max_streams_bidi: 3u32.into(), +1527 initial_max_data: 300u32.into(), +1528 initial_max_stream_data_bidi_remote: 300u32.into(), +1529 ..TransportParameters::default() +1530 }); +1531 +1532 let (mut pending, state) = (Retransmits::default(), ConnState::established()); +1533 let mut streams = Streams { +1534 state: &mut server, +1535 conn_state: &state, +1536 }; 1537 -1538 let mut metas = vec![]; -1539 -1540 // Write the first chunk of stream_a -1541 let meta = server.write_frames_for_test(40, false); -1542 assert!(!meta.is_empty()); -1543 metas.extend(meta); -1544 -1545 // Queue stream_c which has higher priority -1546 let mut stream_c = SendStream { -1547 id: id_c, -1548 state: &mut server, -1549 pending: &mut pending, -1550 conn_state: &state, -1551 }; -1552 stream_c.set_priority(1).unwrap(); -1553 stream_c.write(&[b'b'; 100]).unwrap(); -1554 -1555 // loop until all the streams are written -1556 loop { -1557 let meta = server.write_frames_for_test(40, false); -1558 if meta.is_empty() { -1559 break; -1560 } -1561 metas.extend(meta); -1562 } -1563 -1564 assert!(!server.can_send_stream_data()); -1565 assert_eq!(server.pending.len(), 0); -1566 -1567 let stream_ids = metas.iter().map(|m| m.id).collect::<Vec<_>>(); -1568 assert_eq!( -1569 stream_ids, -1570 // stream_c bumps stream_b but doesn't bump stream_a which had already been partly -1571 // written out -1572 vec![id_a, id_a, id_a, id_c, id_c, id_c, id_b, id_b, id_b] -1573 ); -1574 } +1538 // a, and b have the same priority, c has higher priority +1539 let id_a = streams.open(Dir::Bi).unwrap(); +1540 let id_b = streams.open(Dir::Bi).unwrap(); +1541 let id_c = streams.open(Dir::Bi).unwrap(); +1542 +1543 let mut stream_a = SendStream { +1544 id: id_a, +1545 state: &mut server, +1546 pending: &mut pending, +1547 conn_state: &state, +1548 }; +1549 stream_a.write(&[b'a'; 100]).unwrap(); +1550 +1551 let mut stream_b = SendStream { +1552 id: id_b, +1553 state: &mut server, +1554 pending: &mut pending, +1555 conn_state: &state, +1556 }; +1557 stream_b.write(&[b'b'; 100]).unwrap(); +1558 +1559 let mut metas = vec![]; +1560 +1561 // Write the first chunk of stream_a +1562 let meta = server.write_frames_for_test(40, false); +1563 assert!(!meta.is_empty()); +1564 metas.extend(meta); +1565 +1566 // Queue stream_c which has higher priority +1567 let mut stream_c = SendStream { +1568 id: id_c, +1569 state: &mut server, +1570 pending: &mut pending, +1571 conn_state: &state, +1572 }; +1573 stream_c.set_priority(1).unwrap(); +1574 stream_c.write(&[b'b'; 100]).unwrap(); 1575 -1576 #[test] -1577 fn stop_finished() { -1578 let mut client = make(Side::Client); -1579 let id = StreamId::new(Side::Server, Dir::Uni, 0); -1580 // Server finishes stream -1581 let _ = client -1582 .received( -1583 frame::Stream { -1584 id, -1585 offset: 0, -1586 fin: true, -1587 data: Bytes::from_static(&[0; 32]), -1588 }, -1589 32, -1590 ) -1591 .unwrap(); -1592 let mut pending = Retransmits::default(); -1593 let mut stream = RecvStream { -1594 id, -1595 state: &mut client, -1596 pending: &mut pending, -1597 }; -1598 stream.stop(0u32.into()).unwrap(); -1599 assert!(client.recv.get_mut(&id).is_none(), "stream is freed"); -1600 } -1601 -1602 // Verify that a stream that's been reset doesn't cause the appearance of pending data -1603 #[test] -1604 fn reset_stream_cannot_send() { -1605 let mut server = make(Side::Server); -1606 server.set_params(&TransportParameters { -1607 initial_max_streams_uni: 1u32.into(), -1608 initial_max_data: 42u32.into(), -1609 initial_max_stream_data_uni: 42u32.into(), -1610 ..TransportParameters::default() -1611 }); -1612 let (mut pending, state) = (Retransmits::default(), ConnState::established()); -1613 let mut streams = Streams { -1614 state: &mut server, -1615 conn_state: &state, -1616 }; -1617 -1618 let id = streams.open(Dir::Uni).unwrap(); -1619 let mut stream = SendStream { -1620 id, -1621 state: &mut server, -1622 pending: &mut pending, -1623 conn_state: &state, -1624 }; -1625 stream.write(b"hello").unwrap(); -1626 stream.reset(0u32.into()).unwrap(); -1627 -1628 assert_eq!(pending.reset_stream, &[(id, 0u32.into())]); -1629 assert!(!server.can_send_stream_data()); -1630 } -1631 -1632 #[test] -1633 fn stream_limit_fixed() { -1634 let mut client = make(Side::Client); -1635 // Open streams 0-127 -1636 assert_eq!( -1637 client.received( -1638 frame::Stream { -1639 id: StreamId::new(Side::Server, Dir::Uni, 127), -1640 offset: 0, -1641 fin: true, -1642 data: Bytes::from_static(&[]), -1643 }, -1644 0 -1645 ), -1646 Ok(ShouldTransmit(false)) -1647 ); -1648 // Try to open stream 128, exceeding limit -1649 assert_eq!( -1650 client -1651 .received( -1652 frame::Stream { -1653 id: StreamId::new(Side::Server, Dir::Uni, 128), -1654 offset: 0, -1655 fin: true, -1656 data: Bytes::from_static(&[]), -1657 }, -1658 0 -1659 ) -1660 .unwrap_err() -1661 .code, -1662 TransportErrorCode::STREAM_LIMIT_ERROR -1663 ); -1664 -1665 // Free stream 127 -1666 let mut pending = Retransmits::default(); -1667 let mut stream = RecvStream { -1668 id: StreamId::new(Side::Server, Dir::Uni, 127), -1669 state: &mut client, -1670 pending: &mut pending, -1671 }; -1672 stream.stop(0u32.into()).unwrap(); -1673 -1674 // Open stream 128 -1675 assert_eq!( -1676 client.received( -1677 frame::Stream { -1678 id: StreamId::new(Side::Server, Dir::Uni, 128), -1679 offset: 0, -1680 fin: true, -1681 data: Bytes::from_static(&[]), -1682 }, -1683 0 -1684 ), -1685 Ok(ShouldTransmit(false)) -1686 ); -1687 } -1688 -1689 #[test] -1690 fn stream_limit_grows() { -1691 let mut client = make(Side::Client); -1692 // Open streams 0-127 -1693 assert_eq!( -1694 client.received( -1695 frame::Stream { -1696 id: StreamId::new(Side::Server, Dir::Uni, 127), -1697 offset: 0, -1698 fin: true, -1699 data: Bytes::from_static(&[]), -1700 }, -1701 0 -1702 ), -1703 Ok(ShouldTransmit(false)) -1704 ); -1705 // Try to open stream 128, exceeding limit -1706 assert_eq!( -1707 client -1708 .received( -1709 frame::Stream { -1710 id: StreamId::new(Side::Server, Dir::Uni, 128), -1711 offset: 0, -1712 fin: true, -1713 data: Bytes::from_static(&[]), -1714 }, -1715 0 -1716 ) -1717 .unwrap_err() -1718 .code, -1719 TransportErrorCode::STREAM_LIMIT_ERROR -1720 ); -1721 -1722 // Relax limit by one -1723 client.set_max_concurrent(Dir::Uni, 129u32.into()); -1724 -1725 // Open stream 128 -1726 assert_eq!( -1727 client.received( -1728 frame::Stream { -1729 id: StreamId::new(Side::Server, Dir::Uni, 128), -1730 offset: 0, -1731 fin: true, -1732 data: Bytes::from_static(&[]), -1733 }, -1734 0 -1735 ), -1736 Ok(ShouldTransmit(false)) -1737 ); -1738 } -1739 -1740 #[test] -1741 fn stream_limit_shrinks() { -1742 let mut client = make(Side::Client); -1743 // Open streams 0-127 -1744 assert_eq!( -1745 client.received( -1746 frame::Stream { -1747 id: StreamId::new(Side::Server, Dir::Uni, 127), -1748 offset: 0, -1749 fin: true, -1750 data: Bytes::from_static(&[]), -1751 }, -1752 0 -1753 ), -1754 Ok(ShouldTransmit(false)) -1755 ); -1756 -1757 // Tighten limit by one -1758 client.set_max_concurrent(Dir::Uni, 127u32.into()); -1759 -1760 // Free stream 127 -1761 let mut pending = Retransmits::default(); -1762 let mut stream = RecvStream { -1763 id: StreamId::new(Side::Server, Dir::Uni, 127), -1764 state: &mut client, -1765 pending: &mut pending, -1766 }; -1767 stream.stop(0u32.into()).unwrap(); -1768 -1769 // Try to open stream 128, still exceeding limit -1770 assert_eq!( -1771 client -1772 .received( -1773 frame::Stream { -1774 id: StreamId::new(Side::Server, Dir::Uni, 128), -1775 offset: 0, -1776 fin: true, -1777 data: Bytes::from_static(&[]), -1778 }, -1779 0 -1780 ) -1781 .unwrap_err() -1782 .code, -1783 TransportErrorCode::STREAM_LIMIT_ERROR -1784 ); -1785 -1786 // Free stream 126 -1787 assert_eq!( -1788 client.received_reset(frame::ResetStream { -1789 id: StreamId::new(Side::Server, Dir::Uni, 126), -1790 error_code: 0u32.into(), -1791 final_offset: 0u32.into(), -1792 }), -1793 Ok(ShouldTransmit(false)) -1794 ); -1795 let mut pending = Retransmits::default(); -1796 let mut stream = RecvStream { -1797 id: StreamId::new(Side::Server, Dir::Uni, 126), -1798 state: &mut client, -1799 pending: &mut pending, -1800 }; -1801 stream.stop(0u32.into()).unwrap(); -1802 -1803 // Open stream 128 -1804 assert_eq!( -1805 client.received( -1806 frame::Stream { -1807 id: StreamId::new(Side::Server, Dir::Uni, 128), -1808 offset: 0, -1809 fin: true, -1810 data: Bytes::from_static(&[]), -1811 }, -1812 0 -1813 ), +1576 // loop until all the streams are written +1577 loop { +1578 let meta = server.write_frames_for_test(40, false); +1579 if meta.is_empty() { +1580 break; +1581 } +1582 metas.extend(meta); +1583 } +1584 +1585 assert!(!server.can_send_stream_data()); +1586 assert_eq!(server.pending.len(), 0); +1587 +1588 let stream_ids = metas.iter().map(|m| m.id).collect::<Vec<_>>(); +1589 assert_eq!( +1590 stream_ids, +1591 // stream_c bumps stream_b but doesn't bump stream_a which had already been partly +1592 // written out +1593 vec![id_a, id_a, id_a, id_c, id_c, id_c, id_b, id_b, id_b] +1594 ); +1595 } +1596 +1597 #[test] +1598 fn stop_finished() { +1599 let mut client = make(Side::Client); +1600 let id = StreamId::new(Side::Server, Dir::Uni, 0); +1601 // Server finishes stream +1602 let _ = client +1603 .received( +1604 frame::Stream { +1605 id, +1606 offset: 0, +1607 fin: true, +1608 data: Bytes::from_static(&[0; 32]), +1609 }, +1610 32, +1611 ) +1612 .unwrap(); +1613 let mut pending = Retransmits::default(); +1614 let mut stream = RecvStream { +1615 id, +1616 state: &mut client, +1617 pending: &mut pending, +1618 }; +1619 stream.stop(0u32.into()).unwrap(); +1620 assert!(client.recv.get_mut(&id).is_none(), "stream is freed"); +1621 } +1622 +1623 // Verify that a stream that's been reset doesn't cause the appearance of pending data +1624 #[test] +1625 fn reset_stream_cannot_send() { +1626 let mut server = make(Side::Server); +1627 server.set_params(&TransportParameters { +1628 initial_max_streams_uni: 1u32.into(), +1629 initial_max_data: 42u32.into(), +1630 initial_max_stream_data_uni: 42u32.into(), +1631 ..TransportParameters::default() +1632 }); +1633 let (mut pending, state) = (Retransmits::default(), ConnState::established()); +1634 let mut streams = Streams { +1635 state: &mut server, +1636 conn_state: &state, +1637 }; +1638 +1639 let id = streams.open(Dir::Uni).unwrap(); +1640 let mut stream = SendStream { +1641 id, +1642 state: &mut server, +1643 pending: &mut pending, +1644 conn_state: &state, +1645 }; +1646 stream.write(b"hello").unwrap(); +1647 stream.reset(0u32.into()).unwrap(); +1648 +1649 assert_eq!(pending.reset_stream, &[(id, 0u32.into())]); +1650 assert!(!server.can_send_stream_data()); +1651 } +1652 +1653 #[test] +1654 fn stream_limit_fixed() { +1655 let mut client = make(Side::Client); +1656 // Open streams 0-127 +1657 assert_eq!( +1658 client.received( +1659 frame::Stream { +1660 id: StreamId::new(Side::Server, Dir::Uni, 127), +1661 offset: 0, +1662 fin: true, +1663 data: Bytes::from_static(&[]), +1664 }, +1665 0 +1666 ), +1667 Ok(ShouldTransmit(false)) +1668 ); +1669 // Try to open stream 128, exceeding limit +1670 assert_eq!( +1671 client +1672 .received( +1673 frame::Stream { +1674 id: StreamId::new(Side::Server, Dir::Uni, 128), +1675 offset: 0, +1676 fin: true, +1677 data: Bytes::from_static(&[]), +1678 }, +1679 0 +1680 ) +1681 .unwrap_err() +1682 .code, +1683 TransportErrorCode::STREAM_LIMIT_ERROR +1684 ); +1685 +1686 // Free stream 127 +1687 let mut pending = Retransmits::default(); +1688 let mut stream = RecvStream { +1689 id: StreamId::new(Side::Server, Dir::Uni, 127), +1690 state: &mut client, +1691 pending: &mut pending, +1692 }; +1693 stream.stop(0u32.into()).unwrap(); +1694 +1695 // Open stream 128 +1696 assert_eq!( +1697 client.received( +1698 frame::Stream { +1699 id: StreamId::new(Side::Server, Dir::Uni, 128), +1700 offset: 0, +1701 fin: true, +1702 data: Bytes::from_static(&[]), +1703 }, +1704 0 +1705 ), +1706 Ok(ShouldTransmit(false)) +1707 ); +1708 } +1709 +1710 #[test] +1711 fn stream_limit_grows() { +1712 let mut client = make(Side::Client); +1713 // Open streams 0-127 +1714 assert_eq!( +1715 client.received( +1716 frame::Stream { +1717 id: StreamId::new(Side::Server, Dir::Uni, 127), +1718 offset: 0, +1719 fin: true, +1720 data: Bytes::from_static(&[]), +1721 }, +1722 0 +1723 ), +1724 Ok(ShouldTransmit(false)) +1725 ); +1726 // Try to open stream 128, exceeding limit +1727 assert_eq!( +1728 client +1729 .received( +1730 frame::Stream { +1731 id: StreamId::new(Side::Server, Dir::Uni, 128), +1732 offset: 0, +1733 fin: true, +1734 data: Bytes::from_static(&[]), +1735 }, +1736 0 +1737 ) +1738 .unwrap_err() +1739 .code, +1740 TransportErrorCode::STREAM_LIMIT_ERROR +1741 ); +1742 +1743 // Relax limit by one +1744 client.set_max_concurrent(Dir::Uni, 129u32.into()); +1745 +1746 // Open stream 128 +1747 assert_eq!( +1748 client.received( +1749 frame::Stream { +1750 id: StreamId::new(Side::Server, Dir::Uni, 128), +1751 offset: 0, +1752 fin: true, +1753 data: Bytes::from_static(&[]), +1754 }, +1755 0 +1756 ), +1757 Ok(ShouldTransmit(false)) +1758 ); +1759 } +1760 +1761 #[test] +1762 fn stream_limit_shrinks() { +1763 let mut client = make(Side::Client); +1764 // Open streams 0-127 +1765 assert_eq!( +1766 client.received( +1767 frame::Stream { +1768 id: StreamId::new(Side::Server, Dir::Uni, 127), +1769 offset: 0, +1770 fin: true, +1771 data: Bytes::from_static(&[]), +1772 }, +1773 0 +1774 ), +1775 Ok(ShouldTransmit(false)) +1776 ); +1777 +1778 // Tighten limit by one +1779 client.set_max_concurrent(Dir::Uni, 127u32.into()); +1780 +1781 // Free stream 127 +1782 let mut pending = Retransmits::default(); +1783 let mut stream = RecvStream { +1784 id: StreamId::new(Side::Server, Dir::Uni, 127), +1785 state: &mut client, +1786 pending: &mut pending, +1787 }; +1788 stream.stop(0u32.into()).unwrap(); +1789 +1790 // Try to open stream 128, still exceeding limit +1791 assert_eq!( +1792 client +1793 .received( +1794 frame::Stream { +1795 id: StreamId::new(Side::Server, Dir::Uni, 128), +1796 offset: 0, +1797 fin: true, +1798 data: Bytes::from_static(&[]), +1799 }, +1800 0 +1801 ) +1802 .unwrap_err() +1803 .code, +1804 TransportErrorCode::STREAM_LIMIT_ERROR +1805 ); +1806 +1807 // Free stream 126 +1808 assert_eq!( +1809 client.received_reset(frame::ResetStream { +1810 id: StreamId::new(Side::Server, Dir::Uni, 126), +1811 error_code: 0u32.into(), +1812 final_offset: 0u32.into(), +1813 }), 1814 Ok(ShouldTransmit(false)) 1815 ); -1816 } -1817 -1818 #[test] -1819 fn remote_stream_capacity() { -1820 let mut client = make(Side::Client); -1821 for _ in 0..2 { -1822 client.set_max_concurrent(Dir::Uni, 200u32.into()); -1823 client.set_max_concurrent(Dir::Bi, 201u32.into()); -1824 assert_eq!(client.recv.len(), 200 + 201); -1825 assert_eq!(client.max_remote[Dir::Uni as usize], 200); -1826 assert_eq!(client.max_remote[Dir::Bi as usize], 201); -1827 } -1828 } -1829 -1830 #[test] -1831 fn expand_receive_window() { -1832 let mut server = make(Side::Server); -1833 let new_receive_window = 2 * server.receive_window as u32; -1834 let expanded = server.set_receive_window(new_receive_window.into()); -1835 assert!(expanded); -1836 assert_eq!(server.receive_window, new_receive_window as u64); -1837 assert_eq!(server.local_max_data, new_receive_window as u64); -1838 assert_eq!(server.receive_window_shrink_debt, 0); -1839 let prev_local_max_data = server.local_max_data; -1840 -1841 // credit, expecting all of them added to local_max_data -1842 let credits = 1024u64; -1843 let should_transmit = server.add_read_credits(credits); -1844 assert_eq!(server.receive_window_shrink_debt, 0); -1845 assert_eq!(server.local_max_data, prev_local_max_data + credits); -1846 assert!(should_transmit.should_transmit()); -1847 } -1848 -1849 #[test] -1850 fn shrink_receive_window() { -1851 let mut server = make(Side::Server); -1852 let new_receive_window = server.receive_window as u32 / 2; -1853 let prev_local_max_data = server.local_max_data; +1816 let mut pending = Retransmits::default(); +1817 let mut stream = RecvStream { +1818 id: StreamId::new(Side::Server, Dir::Uni, 126), +1819 state: &mut client, +1820 pending: &mut pending, +1821 }; +1822 stream.stop(0u32.into()).unwrap(); +1823 +1824 // Open stream 128 +1825 assert_eq!( +1826 client.received( +1827 frame::Stream { +1828 id: StreamId::new(Side::Server, Dir::Uni, 128), +1829 offset: 0, +1830 fin: true, +1831 data: Bytes::from_static(&[]), +1832 }, +1833 0 +1834 ), +1835 Ok(ShouldTransmit(false)) +1836 ); +1837 } +1838 +1839 #[test] +1840 fn remote_stream_capacity() { +1841 let mut client = make(Side::Client); +1842 for _ in 0..2 { +1843 client.set_max_concurrent(Dir::Uni, 200u32.into()); +1844 client.set_max_concurrent(Dir::Bi, 201u32.into()); +1845 assert_eq!(client.max_remote[Dir::Uni as usize], 200); +1846 assert_eq!(client.max_remote[Dir::Bi as usize], 201); +1847 assert_eq!(client.allocated_remote_count[Dir::Uni as usize], 200); +1848 assert_eq!(client.allocated_remote_count[Dir::Bi as usize], 201); +1849 // Slots are materialized lazily: no remote stream has been touched yet. +1850 assert!(client.recv.is_empty()); +1851 assert!(client.send.is_empty()); +1852 } +1853 } 1854 -1855 // shrink the receive_winbow, local_max_data is not expected to be changed -1856 let shrink_diff = server.receive_window - new_receive_window as u64; -1857 let expanded = server.set_receive_window(new_receive_window.into()); -1858 assert!(!expanded); -1859 assert_eq!(server.receive_window, new_receive_window as u64); -1860 assert_eq!(server.local_max_data, prev_local_max_data); -1861 assert_eq!(server.receive_window_shrink_debt, shrink_diff); -1862 let prev_local_max_data = server.local_max_data; -1863 -1864 // credit twice, local_max_data does not change as it is absorbed by receive_window_shrink_debt -1865 let credits = 1024u64; -1866 for _ in 0..2 { -1867 let expected_receive_window_shrink_debt = server.receive_window_shrink_debt - credits; -1868 let should_transmit = server.add_read_credits(credits); -1869 assert_eq!( -1870 server.receive_window_shrink_debt, -1871 expected_receive_window_shrink_debt -1872 ); -1873 assert_eq!(server.local_max_data, prev_local_max_data); -1874 assert!(!should_transmit.should_transmit()); -1875 } -1876 -1877 // credit again which exceeds all remaining expected_receive_window_shrink_debt -1878 let credits = 1024 * 512; -1879 let prev_local_max_data = server.local_max_data; -1880 let expected_local_max_data = -1881 server.local_max_data + (credits - server.receive_window_shrink_debt); -1882 let _should_transmit = server.add_read_credits(credits); -1883 assert_eq!(server.receive_window_shrink_debt, 0); -1884 assert_eq!(server.local_max_data, expected_local_max_data); -1885 assert!(server.local_max_data > prev_local_max_data); -1886 -1887 // credit again, all should be added to local_max_data -1888 let credits = 1024 * 512; -1889 let expected_local_max_data = server.local_max_data + credits; -1890 let should_transmit = server.add_read_credits(credits); -1891 assert_eq!(server.receive_window_shrink_debt, 0); -1892 assert_eq!(server.local_max_data, expected_local_max_data); -1893 assert!(should_transmit.should_transmit()); -1894 } -1895 -1896 #[test] -1897 fn expand_send_window() { -1898 let mut server = make(Side::Server); -1899 -1900 let initial_send_window = server.send_window; -1901 let larger_send_window = initial_send_window * 2; -1902 -1903 // Set `initial_max_data` larger than `send_window` so we're limited by local flow control -1904 server.set_params(&TransportParameters { -1905 initial_max_data: VarInt::MAX, -1906 initial_max_stream_data_uni: VarInt::MAX, -1907 initial_max_streams_uni: VarInt::from_u32(100), -1908 ..TransportParameters::default() -1909 }); -1910 -1911 assert_eq!(server.write_limit(), initial_send_window); -1912 assert_eq!(server.poll(), None); -1913 -1914 let mut retransmits = Retransmits::default(); -1915 let conn_state = ConnState::established(); -1916 -1917 let stream_id = Streams { -1918 state: &mut server, -1919 conn_state: &conn_state, -1920 } -1921 .open(Dir::Uni) -1922 .expect("should be able to open a stream"); -1923 -1924 let mut stream = SendStream { -1925 id: stream_id, -1926 state: &mut server, -1927 pending: &mut retransmits, -1928 conn_state: &conn_state, -1929 }; -1930 -1931 // Check that the stream accepts `initial_send_window` bytes -1932 let initial_send_len = initial_send_window as usize; -1933 let data = vec![0xFFu8; initial_send_len]; -1934 -1935 assert_eq!(stream.write(&data), Ok(initial_send_len)); -1936 -1937 // Try to write the same data again, observe that it's blocked -1938 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -1939 -1940 // Check that we get a `Writable` event after increasing the send window -1941 stream.state.set_send_window(larger_send_window); -1942 assert_eq!( -1943 stream.state.poll(), -1944 Some(StreamEvent::Writable { id: stream_id }) -1945 ); -1946 -1947 // Check that the stream accepts the exact same amount of data again -1948 assert_eq!(stream.write(&data), Ok(initial_send_len)); -1949 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -1950 -1951 assert_eq!(stream.state.poll(), None); -1952 -1953 // Ack the data -1954 stream.state.received_ack_of(frame::StreamMeta { -1955 id: stream_id, -1956 offsets: 0..larger_send_window, -1957 fin: false, -1958 }); -1959 -1960 assert_eq!( -1961 stream.state.poll(), -1962 Some(StreamEvent::Writable { id: stream_id }) -1963 ); -1964 -1965 // Check that our full send window is available again -1966 assert_eq!(stream.write(&data), Ok(initial_send_len)); -1967 assert_eq!(stream.write(&data), Ok(initial_send_len)); -1968 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -1969 } -1970 -1971 #[test] -1972 fn shrink_send_window() { -1973 let mut server = make(Side::Server); -1974 -1975 let initial_send_window = server.send_window; -1976 let smaller_send_window = server.send_window / 2; -1977 -1978 // Set `initial_max_data` larger than `send_window` so we're limited by local flow control -1979 server.set_params(&TransportParameters { -1980 initial_max_data: VarInt::MAX, -1981 initial_max_stream_data_uni: VarInt::MAX, -1982 initial_max_streams_uni: VarInt::from_u32(100), -1983 ..TransportParameters::default() -1984 }); -1985 -1986 assert_eq!(server.write_limit(), initial_send_window); -1987 assert_eq!(server.poll(), None); -1988 -1989 let mut retransmits = Retransmits::default(); -1990 let conn_state = ConnState::established(); -1991 -1992 let stream_id = Streams { -1993 state: &mut server, -1994 conn_state: &conn_state, -1995 } -1996 .open(Dir::Uni) -1997 .expect("should be able to open a stream"); -1998 -1999 let mut stream = SendStream { -2000 id: stream_id, -2001 state: &mut server, -2002 pending: &mut retransmits, -2003 conn_state: &conn_state, -2004 }; -2005 -2006 let initial_send_len = initial_send_window as usize; +1855 #[test] +1856 fn lazy_remote_allocation_starts_empty() { +1857 // `StreamsState::new` must not pre-populate `send`/`recv` with placeholder slots. +1858 let client = StreamsState::new( +1859 Side::Client, +1860 10_000u32.into(), +1861 10_000u32.into(), +1862 1024 * 1024, +1863 (1024 * 1024u32).into(), +1864 (1024 * 1024u32).into(), +1865 ); +1866 // No slots allocated until a stream is actually received. +1867 assert!(client.recv.is_empty()); +1868 assert!(client.send.is_empty()); +1869 assert_eq!(client.recv.capacity(), 0); +1870 assert_eq!(client.send.capacity(), 0); +1871 } +1872 +1873 #[test] +1874 fn out_of_order_implicit_open() { +1875 // Receiving idx=5 implicitly opens idx 0..=4. A later frame for idx=3 must be +1876 // processed normally, not dropped as "closed". +1877 const STREAM_5_PAYLOAD: &[u8] = &[0xAA; 8]; +1878 const STREAM_3_PAYLOAD: &[u8] = &[0xBB; 4]; +1879 +1880 let mut client = make(Side::Client); +1881 assert_eq!( +1882 client.received( +1883 frame::Stream { +1884 id: StreamId::new(Side::Server, Dir::Uni, 5), +1885 offset: 0, +1886 fin: true, +1887 data: Bytes::from_static(STREAM_5_PAYLOAD), +1888 }, +1889 STREAM_5_PAYLOAD.len(), +1890 ), +1891 Ok(ShouldTransmit(false)) +1892 ); +1893 assert_eq!(client.next_remote[Dir::Uni as usize], 6); +1894 assert_eq!( +1895 client.received( +1896 frame::Stream { +1897 id: StreamId::new(Side::Server, Dir::Uni, 3), +1898 offset: 0, +1899 fin: true, +1900 data: Bytes::from_static(STREAM_3_PAYLOAD), +1901 }, +1902 STREAM_3_PAYLOAD.len(), +1903 ), +1904 Ok(ShouldTransmit(false)) +1905 ); +1906 +1907 let id = StreamId::new(Side::Server, Dir::Uni, 3); +1908 let mut pending = Retransmits::default(); +1909 let mut recv = RecvStream { +1910 id, +1911 state: &mut client, +1912 pending: &mut pending, +1913 }; +1914 let mut chunks = recv.read(true).unwrap(); +1915 assert_eq!( +1916 chunks.next(STREAM_3_PAYLOAD.len()).unwrap().unwrap().bytes, +1917 STREAM_3_PAYLOAD +1918 ); +1919 let _ = chunks.finalize(); +1920 } +1921 +1922 #[test] +1923 fn frame_for_closed_stream_is_dropped() { +1924 // After a remote stream is fully freed, a subsequent frame for the same id must be +1925 // dropped — absence from the map unambiguously means "closed" for ids below the +1926 // frontier. +1927 const PAYLOAD: &[u8] = &[0; 4]; +1928 +1929 let mut client = make(Side::Client); +1930 let id = StreamId::new(Side::Server, Dir::Uni, 0); +1931 assert_eq!( +1932 client.received( +1933 frame::Stream { +1934 id, +1935 offset: 0, +1936 fin: true, +1937 data: Bytes::from_static(PAYLOAD), +1938 }, +1939 PAYLOAD.len(), +1940 ), +1941 Ok(ShouldTransmit(false)) +1942 ); +1943 // Stop the stream so it's fully freed. +1944 let mut pending = Retransmits::default(); +1945 RecvStream { +1946 id, +1947 state: &mut client, +1948 pending: &mut pending, +1949 } +1950 .stop(0u32.into()) +1951 .unwrap(); +1952 assert!(!client.recv.contains_key(&id)); +1953 +1954 // A stray retransmit for the freed stream must be dropped without resurrecting state. +1955 assert_eq!( +1956 client.received( +1957 frame::Stream { +1958 id, +1959 offset: 0, +1960 fin: true, +1961 data: Bytes::from_static(PAYLOAD), +1962 }, +1963 PAYLOAD.len(), +1964 ), +1965 Ok(ShouldTransmit(false)) +1966 ); +1967 assert!(!client.recv.contains_key(&id)); +1968 } +1969 +1970 #[test] +1971 fn churn_keeps_maps_bounded() { +1972 // Rapidly open + fully close a long sequence of remote streams. The maps must stay +1973 // bounded (only active streams are materialized) even though thousands of ids have +1974 // been used over the connection's lifetime. +1975 const N: u64 = 5_000; +1976 +1977 let mut client = make(Side::Client); +1978 for i in 0..N { +1979 let id = StreamId::new(Side::Server, Dir::Uni, i); +1980 assert_eq!( +1981 client.received( +1982 frame::Stream { +1983 id, +1984 offset: 0, +1985 fin: true, +1986 data: Bytes::from_static(&[0; 1]), +1987 }, +1988 1, +1989 ), +1990 Ok(ShouldTransmit(false)) +1991 ); +1992 let mut pending = Retransmits::default(); +1993 let mut recv = RecvStream { +1994 id, +1995 state: &mut client, +1996 pending: &mut pending, +1997 }; +1998 let mut chunks = recv.read(true).unwrap(); +1999 let _ = chunks.next(1).unwrap(); +2000 assert!(chunks.next(1).unwrap().is_none()); +2001 let _ = chunks.finalize(); +2002 } +2003 // Every stream was fully drained; the map must be empty. +2004 assert_eq!(client.recv.len(), 0); +2005 assert_eq!(client.send.len(), 0); +2006 } 2007 -2008 let data = vec![0xFFu8; initial_send_len]; -2009 -2010 // Assert that the full send window is accepted -2011 assert_eq!(stream.write(&data), Ok(initial_send_len)); -2012 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -2013 -2014 assert_eq!(stream.state.write_limit(), 0); -2015 assert_eq!(stream.state.poll(), None); -2016 -2017 // Shrink our send window, assert that it's still not writable -2018 stream.state.set_send_window(smaller_send_window); -2019 assert_eq!(stream.state.write_limit(), 0); -2020 assert_eq!(stream.state.poll(), None); -2021 -2022 // Assert that data is still not accepted -2023 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -2024 -2025 // Ack some data, assert that writes are still not accepted due to outstanding sends -2026 stream.state.received_ack_of(frame::StreamMeta { -2027 id: stream_id, -2028 offsets: 0..smaller_send_window, -2029 fin: false, -2030 }); -2031 -2032 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -2033 -2034 // Ack the rest of the data -2035 stream.state.received_ack_of(frame::StreamMeta { -2036 id: stream_id, -2037 offsets: smaller_send_window..initial_send_window, -2038 fin: false, -2039 }); -2040 -2041 // This should generate a `Writable` event -2042 assert_eq!( -2043 stream.state.poll(), -2044 Some(StreamEvent::Writable { id: stream_id }) -2045 ); -2046 assert_eq!(stream.state.write_limit(), smaller_send_window); -2047 -2048 // Assert that only `smaller_send_window` bytes are accepted -2049 assert_eq!(stream.write(&data), Ok(smaller_send_window as usize)); -2050 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); -2051 } -2052}
\ No newline at end of file +2008 #[test] +2009 fn expand_receive_window() { +2010 let mut server = make(Side::Server); +2011 let new_receive_window = 2 * server.receive_window as u32; +2012 let expanded = server.set_receive_window(new_receive_window.into()); +2013 assert!(expanded); +2014 assert_eq!(server.receive_window, new_receive_window as u64); +2015 assert_eq!(server.local_max_data, new_receive_window as u64); +2016 assert_eq!(server.receive_window_shrink_debt, 0); +2017 let prev_local_max_data = server.local_max_data; +2018 +2019 // credit, expecting all of them added to local_max_data +2020 let credits = 1024u64; +2021 let should_transmit = server.add_read_credits(credits); +2022 assert_eq!(server.receive_window_shrink_debt, 0); +2023 assert_eq!(server.local_max_data, prev_local_max_data + credits); +2024 assert!(should_transmit.should_transmit()); +2025 } +2026 +2027 #[test] +2028 fn shrink_receive_window() { +2029 let mut server = make(Side::Server); +2030 let new_receive_window = server.receive_window as u32 / 2; +2031 let prev_local_max_data = server.local_max_data; +2032 +2033 // shrink the receive_winbow, local_max_data is not expected to be changed +2034 let shrink_diff = server.receive_window - new_receive_window as u64; +2035 let expanded = server.set_receive_window(new_receive_window.into()); +2036 assert!(!expanded); +2037 assert_eq!(server.receive_window, new_receive_window as u64); +2038 assert_eq!(server.local_max_data, prev_local_max_data); +2039 assert_eq!(server.receive_window_shrink_debt, shrink_diff); +2040 let prev_local_max_data = server.local_max_data; +2041 +2042 // credit twice, local_max_data does not change as it is absorbed by receive_window_shrink_debt +2043 let credits = 1024u64; +2044 for _ in 0..2 { +2045 let expected_receive_window_shrink_debt = server.receive_window_shrink_debt - credits; +2046 let should_transmit = server.add_read_credits(credits); +2047 assert_eq!( +2048 server.receive_window_shrink_debt, +2049 expected_receive_window_shrink_debt +2050 ); +2051 assert_eq!(server.local_max_data, prev_local_max_data); +2052 assert!(!should_transmit.should_transmit()); +2053 } +2054 +2055 // credit again which exceeds all remaining expected_receive_window_shrink_debt +2056 let credits = 1024 * 512; +2057 let prev_local_max_data = server.local_max_data; +2058 let expected_local_max_data = +2059 server.local_max_data + (credits - server.receive_window_shrink_debt); +2060 let _should_transmit = server.add_read_credits(credits); +2061 assert_eq!(server.receive_window_shrink_debt, 0); +2062 assert_eq!(server.local_max_data, expected_local_max_data); +2063 assert!(server.local_max_data > prev_local_max_data); +2064 +2065 // credit again, all should be added to local_max_data +2066 let credits = 1024 * 512; +2067 let expected_local_max_data = server.local_max_data + credits; +2068 let should_transmit = server.add_read_credits(credits); +2069 assert_eq!(server.receive_window_shrink_debt, 0); +2070 assert_eq!(server.local_max_data, expected_local_max_data); +2071 assert!(should_transmit.should_transmit()); +2072 } +2073 +2074 #[test] +2075 fn expand_send_window() { +2076 let mut server = make(Side::Server); +2077 +2078 let initial_send_window = server.send_window; +2079 let larger_send_window = initial_send_window * 2; +2080 +2081 // Set `initial_max_data` larger than `send_window` so we're limited by local flow control +2082 server.set_params(&TransportParameters { +2083 initial_max_data: VarInt::MAX, +2084 initial_max_stream_data_uni: VarInt::MAX, +2085 initial_max_streams_uni: VarInt::from_u32(100), +2086 ..TransportParameters::default() +2087 }); +2088 +2089 assert_eq!(server.write_limit(), initial_send_window); +2090 assert_eq!(server.poll(), None); +2091 +2092 let mut retransmits = Retransmits::default(); +2093 let conn_state = ConnState::established(); +2094 +2095 let stream_id = Streams { +2096 state: &mut server, +2097 conn_state: &conn_state, +2098 } +2099 .open(Dir::Uni) +2100 .expect("should be able to open a stream"); +2101 +2102 let mut stream = SendStream { +2103 id: stream_id, +2104 state: &mut server, +2105 pending: &mut retransmits, +2106 conn_state: &conn_state, +2107 }; +2108 +2109 // Check that the stream accepts `initial_send_window` bytes +2110 let initial_send_len = initial_send_window as usize; +2111 let data = vec![0xFFu8; initial_send_len]; +2112 +2113 assert_eq!(stream.write(&data), Ok(initial_send_len)); +2114 +2115 // Try to write the same data again, observe that it's blocked +2116 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); +2117 +2118 // Check that we get a `Writable` event after increasing the send window +2119 stream.state.set_send_window(larger_send_window); +2120 assert_eq!( +2121 stream.state.poll(), +2122 Some(StreamEvent::Writable { id: stream_id }) +2123 ); +2124 +2125 // Check that the stream accepts the exact same amount of data again +2126 assert_eq!(stream.write(&data), Ok(initial_send_len)); +2127 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); +2128 +2129 assert_eq!(stream.state.poll(), None); +2130 +2131 // Ack the data +2132 stream.state.received_ack_of(frame::StreamMeta { +2133 id: stream_id, +2134 offsets: 0..larger_send_window, +2135 fin: false, +2136 }); +2137 +2138 assert_eq!( +2139 stream.state.poll(), +2140 Some(StreamEvent::Writable { id: stream_id }) +2141 ); +2142 +2143 // Check that our full send window is available again +2144 assert_eq!(stream.write(&data), Ok(initial_send_len)); +2145 assert_eq!(stream.write(&data), Ok(initial_send_len)); +2146 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); +2147 } +2148 +2149 #[test] +2150 fn shrink_send_window() { +2151 let mut server = make(Side::Server); +2152 +2153 let initial_send_window = server.send_window; +2154 let smaller_send_window = server.send_window / 2; +2155 +2156 // Set `initial_max_data` larger than `send_window` so we're limited by local flow control +2157 server.set_params(&TransportParameters { +2158 initial_max_data: VarInt::MAX, +2159 initial_max_stream_data_uni: VarInt::MAX, +2160 initial_max_streams_uni: VarInt::from_u32(100), +2161 ..TransportParameters::default() +2162 }); +2163 +2164 assert_eq!(server.write_limit(), initial_send_window); +2165 assert_eq!(server.poll(), None); +2166 +2167 let mut retransmits = Retransmits::default(); +2168 let conn_state = ConnState::established(); +2169 +2170 let stream_id = Streams { +2171 state: &mut server, +2172 conn_state: &conn_state, +2173 } +2174 .open(Dir::Uni) +2175 .expect("should be able to open a stream"); +2176 +2177 let mut stream = SendStream { +2178 id: stream_id, +2179 state: &mut server, +2180 pending: &mut retransmits, +2181 conn_state: &conn_state, +2182 }; +2183 +2184 let initial_send_len = initial_send_window as usize; +2185 +2186 let data = vec![0xFFu8; initial_send_len]; +2187 +2188 // Assert that the full send window is accepted +2189 assert_eq!(stream.write(&data), Ok(initial_send_len)); +2190 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); +2191 +2192 assert_eq!(stream.state.write_limit(), 0); +2193 assert_eq!(stream.state.poll(), None); +2194 +2195 // Shrink our send window, assert that it's still not writable +2196 stream.state.set_send_window(smaller_send_window); +2197 assert_eq!(stream.state.write_limit(), 0); +2198 assert_eq!(stream.state.poll(), None); +2199 +2200 // Assert that data is still not accepted +2201 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); +2202 +2203 // Ack some data, assert that writes are still not accepted due to outstanding sends +2204 stream.state.received_ack_of(frame::StreamMeta { +2205 id: stream_id, +2206 offsets: 0..smaller_send_window, +2207 fin: false, +2208 }); +2209 +2210 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); +2211 +2212 // Ack the rest of the data +2213 stream.state.received_ack_of(frame::StreamMeta { +2214 id: stream_id, +2215 offsets: smaller_send_window..initial_send_window, +2216 fin: false, +2217 }); +2218 +2219 // This should generate a `Writable` event +2220 assert_eq!( +2221 stream.state.poll(), +2222 Some(StreamEvent::Writable { id: stream_id }) +2223 ); +2224 assert_eq!(stream.state.write_limit(), smaller_send_window); +2225 +2226 // Assert that only `smaller_send_window` bytes are accepted +2227 assert_eq!(stream.write(&data), Ok(smaller_send_window as usize)); +2228 assert_eq!(stream.write(&data), Err(WriteError::Blocked)); +2229 } +2230}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/connection/timer.rs.html b/pr/451/docs/src/noq_proto/connection/timer.rs.html index 5a95404d1..b7ac52e75 100644 --- a/pr/451/docs/src/noq_proto/connection/timer.rs.html +++ b/pr/451/docs/src/noq_proto/connection/timer.rs.html @@ -93,434 +93,446 @@ 93 ]; 94} 95 -96/// Keeps track of the nearest timeout for each `Timer` -97/// -98/// The [`TimerTable`] is advanced with [`TimerTable::expire_before`]. -99#[derive(Debug, Clone, Default)] -100pub(crate) struct TimerTable { -101 generic: [Option<Instant>; ConnTimer::VALUES.len()], -102 path_timers: SmallMap<PathId, PathTimerTable, STACK_TIMERS>, -103} -104 -105/// For how many paths we keep the timers on the stack, before spilling onto the heap. -106const STACK_TIMERS: usize = 4; +96/// Newtype around [`PathId`] that implements [`IdentityHashable`]. +97#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] +98struct PathIdKey(PathId); +99 +100impl IdentityHashable for PathIdKey {} +101 +102impl std::hash::Hash for PathIdKey { +103 fn hash<H: std::hash::Hasher>(&self, state: &mut H) { +104 state.write_u32(self.0.0); +105 } +106} 107 -108/// Works like a `HashMap` but stores up to `SIZE` items on the stack. -109#[derive(Debug, Clone)] -110struct SmallMap<K, V, const SIZE: usize> { -111 stack: [Option<(K, V)>; SIZE], -112 heap: Option<IntMap<K, V>>, -113} -114 -115impl<K, V, const SIZE: usize> Default for SmallMap<K, V, SIZE> { -116 fn default() -> Self { -117 Self { -118 stack: [const { None }; SIZE], -119 heap: None, -120 } -121 } -122} -123 -124impl<K, V, const SIZE: usize> SmallMap<K, V, SIZE> -125where -126 K: std::cmp::Eq + std::hash::Hash + IdentityHashable, -127{ -128 fn insert(&mut self, key: K, value: V) -> Option<V> { -129 // check stack for space -130 for el in self.stack.iter_mut() { -131 match el { -132 Some((k, v)) => { -133 if *k == key { -134 let old_value = std::mem::replace(v, value); -135 return Some(old_value); -136 } -137 } -138 None => { -139 // make sure to remove a potentially old value from the heap -140 let old_heap = self.heap.as_mut().and_then(|h| h.remove(&key)); -141 *el = Some((key, value)); -142 -143 return old_heap; -144 } -145 } -146 } -147 -148 // No space on the stack, use the heap -149 let heap = self.heap.get_or_insert_default(); -150 heap.insert(key, value) -151 } -152 -153 #[cfg(test)] -154 fn remove(&mut self, key: &K) -> Option<V> { -155 for el in self.stack.iter_mut() { -156 if let Some((k, _)) = el -157 && key == k -158 { -159 return el.take().map(|(_, v)| v); -160 } -161 } -162 -163 self.heap.as_mut().and_then(|h| h.remove(key)) -164 } -165 -166 fn get(&self, key: &K) -> Option<&V> { -167 for (k, v) in self.stack.iter().filter_map(|v| v.as_ref()) { -168 if k == key { -169 return Some(v); -170 } -171 } -172 -173 self.heap.as_ref().and_then(|h| h.get(key)) -174 } -175 -176 fn get_mut(&mut self, key: &K) -> Option<&mut V> { -177 for (k, v) in self.stack.iter_mut().filter_map(|v| v.as_mut()) { -178 if k == key { -179 return Some(v); -180 } -181 } -182 -183 self.heap.as_mut().and_then(|h| h.get_mut(key)) -184 } -185 -186 #[cfg(test)] -187 fn iter(&self) -> impl Iterator<Item = (&K, &V)> { -188 let a = self -189 .stack -190 .iter() -191 .filter_map(|v| v.as_ref().map(|(k, v)| (k, v))); -192 let b = self.heap.iter().flat_map(|h| h.iter()); -193 a.chain(b) -194 } -195 -196 fn values(&self) -> impl Iterator<Item = &V> { -197 let a = self.stack.iter().filter_map(|v| v.as_ref().map(|(_, v)| v)); -198 let b = self.heap.iter().flat_map(|h| h.values()); -199 a.chain(b) -200 } -201 -202 fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> { -203 let a = self -204 .stack -205 .iter_mut() -206 .filter_map(|v| v.as_mut().map(|(k, v)| (&*k, v))); -207 let b = self.heap.iter_mut().flat_map(|h| h.iter_mut()); -208 a.chain(b) -209 } -210 -211 fn retain<F>(&mut self, mut f: F) -212 where -213 F: FnMut(&K, &mut V) -> bool, -214 { -215 let mut to_remove = [false; SIZE]; -216 for (i, el) in self.stack.iter_mut().enumerate() { -217 if let Some((key, value)) = el { -218 to_remove[i] = !f(key, value); -219 } -220 } -221 for (i, to_remove) in to_remove.into_iter().enumerate() { -222 if to_remove { -223 self.stack[i] = None; -224 } -225 } -226 -227 if let Some(ref mut heap) = self.heap { -228 heap.retain(f); -229 } -230 } -231 -232 fn clear(&mut self) { -233 for el in self.stack.iter_mut() { -234 *el = None; -235 } -236 self.heap = None; -237 } -238} -239 -240#[derive(Debug, Clone, Copy, Default)] -241struct PathTimerTable { -242 timers: [Option<Instant>; PathTimer::VALUES.len()], -243} -244 -245impl PathTimerTable { -246 fn set(&mut self, timer: PathTimer, time: Instant) { -247 self.timers[timer as usize] = Some(time); -248 } -249 -250 fn get(&self, timer: PathTimer) -> Option<Instant> { -251 self.timers[timer as usize] -252 } -253 -254 fn stop(&mut self, timer: PathTimer) { -255 self.timers[timer as usize] = None; -256 } -257 -258 /// Remove the next timer up until `now`, including it -259 fn expire_before(&mut self, now: Instant) -> Option<(PathTimer, Instant)> { -260 for timer in PathTimer::VALUES { -261 if self.timers[timer as usize].is_some() -262 && self.timers[timer as usize].expect("checked") <= now -263 { -264 return self.timers[timer as usize].take().map(|time| (timer, time)); -265 } -266 } -267 -268 None -269 } -270} -271 -272impl TimerTable { -273 /// Sets the timer unconditionally. -274 /// -275 /// If the timer is already set, this will change the timer's value. -276 pub(super) fn set(&mut self, timer: Timer, time: Instant, qlog: QlogSinkWithTime<'_>) { -277 match timer { -278 Timer::Conn(timer) => { -279 self.generic[timer as usize] = Some(time); -280 } -281 Timer::PerPath(path_id, timer) => match self.path_timers.get_mut(&path_id) { -282 None => { -283 let mut table = PathTimerTable::default(); -284 table.set(timer, time); -285 self.path_timers.insert(path_id, table); -286 } -287 Some(table) => { -288 table.set(timer, time); -289 } -290 }, -291 } -292 qlog.emit_timer_set(timer, time); -293 } -294 -295 pub(super) fn get(&self, timer: Timer) -> Option<Instant> { -296 match timer { -297 Timer::Conn(timer) => self.generic[timer as usize], -298 Timer::PerPath(path_id, timer) => self.path_timers.get(&path_id)?.get(timer), -299 } -300 } -301 -302 pub(super) fn set_or_stop( -303 &mut self, -304 timer: Timer, -305 time: Option<Instant>, -306 qlog: QlogSinkWithTime<'_>, -307 ) { -308 match time { -309 Some(time) => self.set(timer, time, qlog), -310 None => self.stop(timer, qlog), +108/// Keeps track of the nearest timeout for each `Timer` +109/// +110/// The [`TimerTable`] is advanced with [`TimerTable::expire_before`]. +111#[derive(Debug, Clone, Default)] +112pub(crate) struct TimerTable { +113 generic: [Option<Instant>; ConnTimer::VALUES.len()], +114 path_timers: SmallMap<PathIdKey, PathTimerTable, STACK_TIMERS>, +115} +116 +117/// For how many paths we keep the timers on the stack, before spilling onto the heap. +118const STACK_TIMERS: usize = 4; +119 +120/// Works like a `HashMap` but stores up to `SIZE` items on the stack. +121#[derive(Debug, Clone)] +122struct SmallMap<K, V, const SIZE: usize> { +123 stack: [Option<(K, V)>; SIZE], +124 heap: Option<IntMap<K, V>>, +125} +126 +127impl<K, V, const SIZE: usize> Default for SmallMap<K, V, SIZE> { +128 fn default() -> Self { +129 Self { +130 stack: [const { None }; SIZE], +131 heap: None, +132 } +133 } +134} +135 +136impl<K, V, const SIZE: usize> SmallMap<K, V, SIZE> +137where +138 K: std::cmp::Eq + std::hash::Hash + IdentityHashable, +139{ +140 fn insert(&mut self, key: K, value: V) -> Option<V> { +141 // check stack for space +142 for el in self.stack.iter_mut() { +143 match el { +144 Some((k, v)) => { +145 if *k == key { +146 let old_value = std::mem::replace(v, value); +147 return Some(old_value); +148 } +149 } +150 None => { +151 // make sure to remove a potentially old value from the heap +152 let old_heap = self.heap.as_mut().and_then(|h| h.remove(&key)); +153 *el = Some((key, value)); +154 +155 return old_heap; +156 } +157 } +158 } +159 +160 // No space on the stack, use the heap +161 let heap = self.heap.get_or_insert_default(); +162 heap.insert(key, value) +163 } +164 +165 #[cfg(test)] +166 fn remove(&mut self, key: &K) -> Option<V> { +167 for el in self.stack.iter_mut() { +168 if let Some((k, _)) = el +169 && key == k +170 { +171 return el.take().map(|(_, v)| v); +172 } +173 } +174 +175 self.heap.as_mut().and_then(|h| h.remove(key)) +176 } +177 +178 fn get(&self, key: &K) -> Option<&V> { +179 for (k, v) in self.stack.iter().filter_map(|v| v.as_ref()) { +180 if k == key { +181 return Some(v); +182 } +183 } +184 +185 self.heap.as_ref().and_then(|h| h.get(key)) +186 } +187 +188 fn get_mut(&mut self, key: &K) -> Option<&mut V> { +189 for (k, v) in self.stack.iter_mut().filter_map(|v| v.as_mut()) { +190 if k == key { +191 return Some(v); +192 } +193 } +194 +195 self.heap.as_mut().and_then(|h| h.get_mut(key)) +196 } +197 +198 #[cfg(test)] +199 fn iter(&self) -> impl Iterator<Item = (&K, &V)> { +200 let a = self +201 .stack +202 .iter() +203 .filter_map(|v| v.as_ref().map(|(k, v)| (k, v))); +204 let b = self.heap.iter().flat_map(|h| h.iter()); +205 a.chain(b) +206 } +207 +208 fn values(&self) -> impl Iterator<Item = &V> { +209 let a = self.stack.iter().filter_map(|v| v.as_ref().map(|(_, v)| v)); +210 let b = self.heap.iter().flat_map(|h| h.values()); +211 a.chain(b) +212 } +213 +214 fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> { +215 let a = self +216 .stack +217 .iter_mut() +218 .filter_map(|v| v.as_mut().map(|(k, v)| (&*k, v))); +219 let b = self.heap.iter_mut().flat_map(|h| h.iter_mut()); +220 a.chain(b) +221 } +222 +223 fn retain<F>(&mut self, mut f: F) +224 where +225 F: FnMut(&K, &mut V) -> bool, +226 { +227 let mut to_remove = [false; SIZE]; +228 for (i, el) in self.stack.iter_mut().enumerate() { +229 if let Some((key, value)) = el { +230 to_remove[i] = !f(key, value); +231 } +232 } +233 for (i, to_remove) in to_remove.into_iter().enumerate() { +234 if to_remove { +235 self.stack[i] = None; +236 } +237 } +238 +239 if let Some(ref mut heap) = self.heap { +240 heap.retain(f); +241 } +242 } +243 +244 fn clear(&mut self) { +245 for el in self.stack.iter_mut() { +246 *el = None; +247 } +248 self.heap = None; +249 } +250} +251 +252#[derive(Debug, Clone, Copy, Default)] +253struct PathTimerTable { +254 timers: [Option<Instant>; PathTimer::VALUES.len()], +255} +256 +257impl PathTimerTable { +258 fn set(&mut self, timer: PathTimer, time: Instant) { +259 self.timers[timer as usize] = Some(time); +260 } +261 +262 fn get(&self, timer: PathTimer) -> Option<Instant> { +263 self.timers[timer as usize] +264 } +265 +266 fn stop(&mut self, timer: PathTimer) { +267 self.timers[timer as usize] = None; +268 } +269 +270 /// Remove the next timer up until `now`, including it +271 fn expire_before(&mut self, now: Instant) -> Option<(PathTimer, Instant)> { +272 for timer in PathTimer::VALUES { +273 if self.timers[timer as usize].is_some() +274 && self.timers[timer as usize].expect("checked") <= now +275 { +276 return self.timers[timer as usize].take().map(|time| (timer, time)); +277 } +278 } +279 +280 None +281 } +282} +283 +284impl TimerTable { +285 /// Sets the timer unconditionally. +286 /// +287 /// If the timer is already set, this will change the timer's value. +288 pub(super) fn set(&mut self, timer: Timer, time: Instant, qlog: QlogSinkWithTime<'_>) { +289 match timer { +290 Timer::Conn(timer) => { +291 self.generic[timer as usize] = Some(time); +292 } +293 Timer::PerPath(path_id, timer) => match self.path_timers.get_mut(&PathIdKey(path_id)) { +294 None => { +295 let mut table = PathTimerTable::default(); +296 table.set(timer, time); +297 self.path_timers.insert(PathIdKey(path_id), table); +298 } +299 Some(table) => { +300 table.set(timer, time); +301 } +302 }, +303 } +304 qlog.emit_timer_set(timer, time); +305 } +306 +307 pub(super) fn get(&self, timer: Timer) -> Option<Instant> { +308 match timer { +309 Timer::Conn(timer) => self.generic[timer as usize], +310 Timer::PerPath(path_id, timer) => self.path_timers.get(&PathIdKey(path_id))?.get(timer), 311 } 312 } 313 -314 pub(super) fn stop(&mut self, timer: Timer, qlog: QlogSinkWithTime<'_>) { -315 match timer { -316 Timer::Conn(timer) => { -317 self.generic[timer as usize] = None; -318 } -319 Timer::PerPath(path_id, timer) => { -320 if let Some(e) = self.path_timers.get_mut(&path_id) { -321 e.stop(timer); -322 } -323 } -324 } -325 qlog.emit_timer_stop(timer); -326 } -327 -328 /// Stops all per-path timers -329 pub(super) fn stop_per_path(&mut self, path_id: PathId, qlog: QlogSinkWithTime<'_>) { -330 for timer in PathTimer::VALUES { -331 if let Some(e) = self.path_timers.get_mut(&path_id) { -332 e.stop(timer); -333 qlog.emit_timer_stop(Timer::PerPath(path_id, timer)); -334 } -335 } -336 } -337 -338 /// Get the next queued timeout -339 pub(super) fn peek(&mut self) -> Option<Instant> { -340 // TODO: this is currently linear in the number of paths -341 -342 let min_generic = self.generic.iter().filter_map(|&x| x).min(); -343 let min_path = self -344 .path_timers -345 .values() -346 .flat_map(|p| p.timers.iter().filter_map(|&x| x)) -347 .min(); -348 -349 match (min_generic, min_path) { -350 (None, None) => None, -351 (Some(val), None) => Some(val), -352 (Some(a), Some(b)) => Some(a.min(b)), -353 (None, Some(val)) => Some(val), -354 } -355 } -356 -357 /// Remove the next timer up until `now`, including it -358 pub(super) fn expire_before( -359 &mut self, -360 now: Instant, -361 qlog: &QlogSink, -362 ) -> Option<(Timer, Instant)> { -363 let (timer, instant) = self.expire_before_inner(now)?; -364 qlog.with_time(now).emit_timer_expire(timer); -365 Some((timer, instant)) -366 } -367 -368 fn expire_before_inner(&mut self, now: Instant) -> Option<(Timer, Instant)> { -369 // TODO: this is currently linear in the number of paths -370 -371 for timer in ConnTimer::VALUES { -372 if self.generic[timer as usize].is_some() -373 && self.generic[timer as usize].expect("checked") <= now -374 { -375 return self.generic[timer as usize] -376 .take() -377 .map(|time| (Timer::Conn(timer), time)); -378 } -379 } -380 -381 let mut res = None; -382 for (path_id, timers) in self.path_timers.iter_mut() { -383 if let Some((timer, time)) = timers.expire_before(now) { -384 res = Some((Timer::PerPath(*path_id, timer), time)); -385 break; -386 } -387 } -388 -389 // clear out old timers -390 self.path_timers -391 .retain(|_path_id, timers| timers.timers.iter().any(|t| t.is_some())); -392 res -393 } -394 -395 pub(super) fn reset(&mut self) { -396 for timer in ConnTimer::VALUES { -397 self.generic[timer as usize] = None; -398 } -399 self.path_timers.clear(); -400 } -401 -402 #[cfg(test)] -403 pub(super) fn values(&self) -> Vec<(Timer, Instant)> { -404 let mut values = Vec::new(); -405 -406 for timer in ConnTimer::VALUES { -407 if let Some(time) = self.generic[timer as usize] { -408 values.push((Timer::Conn(timer), time)); -409 } +314 pub(super) fn set_or_stop( +315 &mut self, +316 timer: Timer, +317 time: Option<Instant>, +318 qlog: QlogSinkWithTime<'_>, +319 ) { +320 match time { +321 Some(time) => self.set(timer, time, qlog), +322 None => self.stop(timer, qlog), +323 } +324 } +325 +326 pub(super) fn stop(&mut self, timer: Timer, qlog: QlogSinkWithTime<'_>) { +327 match timer { +328 Timer::Conn(timer) => { +329 self.generic[timer as usize] = None; +330 } +331 Timer::PerPath(path_id, timer) => { +332 if let Some(e) = self.path_timers.get_mut(&PathIdKey(path_id)) { +333 e.stop(timer); +334 } +335 } +336 } +337 qlog.emit_timer_stop(timer); +338 } +339 +340 /// Stops all per-path timers +341 pub(super) fn stop_per_path(&mut self, path_id: PathId, qlog: QlogSinkWithTime<'_>) { +342 for timer in PathTimer::VALUES { +343 if let Some(e) = self.path_timers.get_mut(&PathIdKey(path_id)) { +344 e.stop(timer); +345 qlog.emit_timer_stop(Timer::PerPath(path_id, timer)); +346 } +347 } +348 } +349 +350 /// Get the next queued timeout +351 pub(super) fn peek(&mut self) -> Option<Instant> { +352 // TODO: this is currently linear in the number of paths +353 +354 let min_generic = self.generic.iter().filter_map(|&x| x).min(); +355 let min_path = self +356 .path_timers +357 .values() +358 .flat_map(|p| p.timers.iter().filter_map(|&x| x)) +359 .min(); +360 +361 match (min_generic, min_path) { +362 (None, None) => None, +363 (Some(val), None) => Some(val), +364 (Some(a), Some(b)) => Some(a.min(b)), +365 (None, Some(val)) => Some(val), +366 } +367 } +368 +369 /// Remove the next timer up until `now`, including it +370 pub(super) fn expire_before( +371 &mut self, +372 now: Instant, +373 qlog: &QlogSink, +374 ) -> Option<(Timer, Instant)> { +375 let (timer, instant) = self.expire_before_inner(now)?; +376 qlog.with_time(now).emit_timer_expire(timer); +377 Some((timer, instant)) +378 } +379 +380 fn expire_before_inner(&mut self, now: Instant) -> Option<(Timer, Instant)> { +381 // TODO: this is currently linear in the number of paths +382 +383 for timer in ConnTimer::VALUES { +384 if self.generic[timer as usize].is_some() +385 && self.generic[timer as usize].expect("checked") <= now +386 { +387 return self.generic[timer as usize] +388 .take() +389 .map(|time| (Timer::Conn(timer), time)); +390 } +391 } +392 +393 let mut res = None; +394 for (PathIdKey(path_id), timers) in self.path_timers.iter_mut() { +395 if let Some((timer, time)) = timers.expire_before(now) { +396 res = Some((Timer::PerPath(*path_id, timer), time)); +397 break; +398 } +399 } +400 +401 // clear out old timers +402 self.path_timers +403 .retain(|_path_id, timers| timers.timers.iter().any(|t| t.is_some())); +404 res +405 } +406 +407 pub(super) fn reset(&mut self) { +408 for timer in ConnTimer::VALUES { +409 self.generic[timer as usize] = None; 410 } -411 -412 for timer in PathTimer::VALUES { -413 for (path_id, timers) in self.path_timers.iter() { -414 if let Some(time) = timers.timers[timer as usize] { -415 values.push((Timer::PerPath(*path_id, timer), time)); -416 } -417 } -418 } -419 -420 values -421 } -422} +411 self.path_timers.clear(); +412 } +413 +414 #[cfg(test)] +415 pub(super) fn values(&self) -> Vec<(Timer, Instant)> { +416 let mut values = Vec::new(); +417 +418 for timer in ConnTimer::VALUES { +419 if let Some(time) = self.generic[timer as usize] { +420 values.push((Timer::Conn(timer), time)); +421 } +422 } 423 -424#[cfg(test)] -425mod tests { -426 use std::time::Duration; -427 -428 use crate::connection::qlog::QlogSink; -429 -430 use super::*; +424 for timer in PathTimer::VALUES { +425 for (PathIdKey(path_id), timers) in self.path_timers.iter() { +426 if let Some(time) = timers.timers[timer as usize] { +427 values.push((Timer::PerPath(*path_id, timer), time)); +428 } +429 } +430 } 431 -432 #[test] -433 fn timer_table() { -434 let mut timers = TimerTable::default(); -435 let sec = Duration::from_secs(1); -436 let now = Instant::now() + Duration::from_secs(10); -437 timers.set( -438 Timer::Conn(ConnTimer::Idle), -439 now - 3 * sec, -440 QlogSink::default().with_time(now), -441 ); -442 timers.set( -443 Timer::Conn(ConnTimer::Close), -444 now - 2 * sec, -445 QlogSink::default().with_time(now), -446 ); -447 -448 assert_eq!(timers.peek(), Some(now - 3 * sec)); -449 assert_eq!( -450 timers.expire_before(now, &QlogSink::default()), -451 Some((Timer::Conn(ConnTimer::Idle), now - 3 * sec)) -452 ); -453 assert_eq!( -454 timers.expire_before(now, &QlogSink::default()), -455 Some((Timer::Conn(ConnTimer::Close), now - 2 * sec)) -456 ); -457 assert_eq!(timers.expire_before(now, &QlogSink::default()), None); -458 } +432 values +433 } +434} +435 +436#[cfg(test)] +437mod tests { +438 use std::time::Duration; +439 +440 use crate::connection::qlog::QlogSink; +441 +442 use super::*; +443 +444 #[test] +445 fn timer_table() { +446 let mut timers = TimerTable::default(); +447 let sec = Duration::from_secs(1); +448 let now = Instant::now() + Duration::from_secs(10); +449 timers.set( +450 Timer::Conn(ConnTimer::Idle), +451 now - 3 * sec, +452 QlogSink::default().with_time(now), +453 ); +454 timers.set( +455 Timer::Conn(ConnTimer::Close), +456 now - 2 * sec, +457 QlogSink::default().with_time(now), +458 ); 459 -460 #[test] -461 fn test_small_map() { -462 let mut map = SmallMap::<usize, usize, 2>::default(); -463 -464 // inserts only on the stack -465 assert_eq!(map.insert(1, 1), None); -466 assert!(map.heap.is_none()); -467 assert_eq!(map.insert(2, 2), None); -468 assert!(map.heap.is_none()); -469 -470 // replace on the stack -471 assert_eq!(map.insert(1, 2), Some(1)); -472 -473 assert_eq!(map.remove(&1), Some(2)); -474 assert_eq!(map.insert(3, 3), None); -475 assert!(map.heap.is_none()); -476 -477 // spill -478 assert_eq!(map.insert(4, 4), None); -479 assert!(map.heap.is_some()); -480 -481 assert_eq!( -482 map.iter() -483 .map(|(&a, &b)| (a, b)) -484 .collect::<Vec<(usize, usize)>>(), -485 vec![(3, 3), (2, 2), (4, 4)] -486 ); -487 assert_eq!( -488 map.iter() -489 .map(|(a, b)| (*a, *b)) -490 .collect::<Vec<(usize, usize)>>(), -491 map.iter_mut() -492 .map(|(a, b)| (*a, *b)) -493 .collect::<Vec<(usize, usize)>>(), -494 ); -495 -496 assert_eq!(map.heap.as_ref().unwrap().len(), 1); -497 -498 for i in 0..10 { -499 map.insert(10 + i, 10 + i); -500 } -501 assert_eq!(map.heap.as_ref().unwrap().len(), 11); -502 map.retain(|k, _v| *k < 10); -503 -504 assert_eq!(map.heap.as_ref().unwrap().len(), 1); -505 -506 assert_eq!( -507 map.iter() -508 .map(|(&a, &b)| (a, b)) -509 .collect::<Vec<(usize, usize)>>(), -510 vec![(3, 3), (2, 2), (4, 4)] -511 ); -512 -513 assert_eq!( -514 map.iter() -515 .map(|(a, b)| (*a, *b)) -516 .collect::<Vec<(usize, usize)>>(), -517 map.iter_mut() -518 .map(|(a, b)| (*a, *b)) -519 .collect::<Vec<(usize, usize)>>(), -520 ); -521 -522 map.clear(); -523 assert_eq!(map.iter().collect::<Vec<_>>(), Vec::new()); -524 assert!(map.heap.is_none()); -525 } -526}

\ No newline at end of file +460 assert_eq!(timers.peek(), Some(now - 3 * sec)); +461 assert_eq!( +462 timers.expire_before(now, &QlogSink::default()), +463 Some((Timer::Conn(ConnTimer::Idle), now - 3 * sec)) +464 ); +465 assert_eq!( +466 timers.expire_before(now, &QlogSink::default()), +467 Some((Timer::Conn(ConnTimer::Close), now - 2 * sec)) +468 ); +469 assert_eq!(timers.expire_before(now, &QlogSink::default()), None); +470 } +471 +472 #[test] +473 fn test_small_map() { +474 let mut map = SmallMap::<usize, usize, 2>::default(); +475 +476 // inserts only on the stack +477 assert_eq!(map.insert(1, 1), None); +478 assert!(map.heap.is_none()); +479 assert_eq!(map.insert(2, 2), None); +480 assert!(map.heap.is_none()); +481 +482 // replace on the stack +483 assert_eq!(map.insert(1, 2), Some(1)); +484 +485 assert_eq!(map.remove(&1), Some(2)); +486 assert_eq!(map.insert(3, 3), None); +487 assert!(map.heap.is_none()); +488 +489 // spill +490 assert_eq!(map.insert(4, 4), None); +491 assert!(map.heap.is_some()); +492 +493 assert_eq!( +494 map.iter() +495 .map(|(&a, &b)| (a, b)) +496 .collect::<Vec<(usize, usize)>>(), +497 vec![(3, 3), (2, 2), (4, 4)] +498 ); +499 assert_eq!( +500 map.iter() +501 .map(|(a, b)| (*a, *b)) +502 .collect::<Vec<(usize, usize)>>(), +503 map.iter_mut() +504 .map(|(a, b)| (*a, *b)) +505 .collect::<Vec<(usize, usize)>>(), +506 ); +507 +508 assert_eq!(map.heap.as_ref().unwrap().len(), 1); +509 +510 for i in 0..10 { +511 map.insert(10 + i, 10 + i); +512 } +513 assert_eq!(map.heap.as_ref().unwrap().len(), 11); +514 map.retain(|k, _v| *k < 10); +515 +516 assert_eq!(map.heap.as_ref().unwrap().len(), 1); +517 +518 assert_eq!( +519 map.iter() +520 .map(|(&a, &b)| (a, b)) +521 .collect::<Vec<(usize, usize)>>(), +522 vec![(3, 3), (2, 2), (4, 4)] +523 ); +524 +525 assert_eq!( +526 map.iter() +527 .map(|(a, b)| (*a, *b)) +528 .collect::<Vec<(usize, usize)>>(), +529 map.iter_mut() +530 .map(|(a, b)| (*a, *b)) +531 .collect::<Vec<(usize, usize)>>(), +532 ); +533 +534 map.clear(); +535 assert_eq!(map.iter().collect::<Vec<_>>(), Vec::new()); +536 assert!(map.heap.is_none()); +537 } +538}

\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/endpoint.rs.html b/pr/451/docs/src/noq_proto/endpoint.rs.html index 0e0389133..b1d9ea877 100644 --- a/pr/451/docs/src/noq_proto/endpoint.rs.html +++ b/pr/451/docs/src/noq_proto/endpoint.rs.html @@ -129,1517 +129,1520 @@ 129 } 130 } 131 } -132 Drained => { -133 if let Some(conn) = self.connections.try_remove(ch.0) { -134 self.index.remove(&conn); -135 } else { -136 // This indicates a bug in downstream code, which could cause spurious -137 // connection loss instead of this error if the CID was (re)allocated prior to -138 // the illegal call. -139 error!(id = ch.0, "unknown connection drained"); -140 } -141 } -142 } -143 None -144 } -145 -146 /// Process an incoming UDP datagram -147 pub fn handle( -148 &mut self, -149 now: Instant, -150 network_path: FourTuple, -151 ecn: Option<EcnCodepoint>, -152 data: BytesMut, -153 buf: &mut Vec<u8>, -154 ) -> Option<DatagramEvent> { -155 // Partially decode packet or short-circuit if unable -156 let datagram_len = data.len(); -157 let mut event = match PartialDecode::new( -158 data, -159 &FixedLengthConnectionIdParser::new(self.local_cid_generator.cid_len()), -160 &self.config.supported_versions, -161 self.config.grease_quic_bit, -162 ) { -163 Ok((first_decode, remaining)) => DatagramConnectionEvent { -164 now, -165 network_path, -166 path_id: PathId::ZERO, // Corrected later for existing paths -167 ecn, -168 first_decode, -169 remaining, -170 }, -171 Err(PacketDecodeError::UnsupportedVersion { -172 src_cid, -173 dst_cid, -174 version, -175 }) => { -176 if self.server_config.is_none() { -177 debug!("dropping packet with unsupported version"); -178 return None; -179 } -180 trace!("sending version negotiation"); -181 // Negotiate versions -182 Header::VersionNegotiate { -183 random: self.rng.random::<u8>() | 0x40, -184 src_cid: dst_cid, -185 dst_cid: src_cid, -186 } -187 .encode(buf); -188 // Grease with a reserved version -189 buf.write::<u32>(match version { -190 0x0a1a_2a3a => 0x0a1a_2a4a, -191 _ => 0x0a1a_2a3a, -192 }); -193 for &version in &self.config.supported_versions { -194 buf.write(version); -195 } -196 return Some(DatagramEvent::Response(Transmit { -197 destination: network_path.remote, -198 ecn: None, -199 size: buf.len(), -200 segment_size: None, -201 src_ip: network_path.local_ip, -202 })); -203 } -204 Err(e) => { -205 trace!("malformed header: {}", e); -206 return None; -207 } -208 }; -209 -210 let dst_cid = event.first_decode.dst_cid(); -211 -212 if let Some(route_to) = self.index.get(&network_path, &event.first_decode) { -213 event.path_id = match route_to { -214 RouteDatagramTo::Incoming(_) => PathId::ZERO, -215 RouteDatagramTo::Connection(_, path_id) => path_id, -216 }; -217 match route_to { -218 RouteDatagramTo::Incoming(incoming_idx) => { -219 let incoming_buffer = &mut self.incoming_buffers[incoming_idx]; -220 let config = &self.server_config.as_ref().unwrap(); -221 -222 if incoming_buffer -223 .total_bytes -224 .checked_add(datagram_len as u64) -225 .is_some_and(|n| n <= config.incoming_buffer_size) -226 && self -227 .all_incoming_buffers_total_bytes -228 .checked_add(datagram_len as u64) -229 .is_some_and(|n| n <= config.incoming_buffer_size_total) -230 { -231 incoming_buffer.datagrams.push(event); -232 incoming_buffer.total_bytes += datagram_len as u64; -233 self.all_incoming_buffers_total_bytes += datagram_len as u64; -234 } -235 -236 None -237 } -238 RouteDatagramTo::Connection(ch, _path_id) => Some(DatagramEvent::ConnectionEvent( -239 ch, -240 ConnectionEvent(ConnectionEventInner::Datagram(event)), -241 )), -242 } -243 } else if event.first_decode.initial_header().is_some() { -244 // Potentially create a new connection -245 -246 self.handle_first_packet(datagram_len, event, network_path, buf) -247 } else if event.first_decode.has_long_header() { -248 debug!( -249 "ignoring non-initial packet for unknown connection {}", -250 dst_cid -251 ); -252 None -253 } else if !event.first_decode.is_initial() -254 && self.local_cid_generator.validate(dst_cid).is_err() -255 { -256 debug!("dropping packet with invalid CID"); -257 None -258 } else if dst_cid.is_empty() { -259 trace!("dropping unrecognized short packet without ID"); +132 Draining => { +133 // Nothing to do. +134 } +135 Drained => { +136 if let Some(conn) = self.connections.try_remove(ch.0) { +137 self.index.remove(&conn); +138 } else { +139 // This indicates a bug in downstream code, which could cause spurious +140 // connection loss instead of this error if the CID was (re)allocated prior to +141 // the illegal call. +142 error!(id = ch.0, "unknown connection drained"); +143 } +144 } +145 } +146 None +147 } +148 +149 /// Process an incoming UDP datagram +150 pub fn handle( +151 &mut self, +152 now: Instant, +153 network_path: FourTuple, +154 ecn: Option<EcnCodepoint>, +155 data: BytesMut, +156 buf: &mut Vec<u8>, +157 ) -> Option<DatagramEvent> { +158 // Partially decode packet or short-circuit if unable +159 let datagram_len = data.len(); +160 let mut event = match PartialDecode::new( +161 data, +162 &FixedLengthConnectionIdParser::new(self.local_cid_generator.cid_len()), +163 &self.config.supported_versions, +164 self.config.grease_quic_bit, +165 ) { +166 Ok((first_decode, remaining)) => DatagramConnectionEvent { +167 now, +168 network_path, +169 path_id: PathId::ZERO, // Corrected later for existing paths +170 ecn, +171 first_decode, +172 remaining, +173 }, +174 Err(PacketDecodeError::UnsupportedVersion { +175 src_cid, +176 dst_cid, +177 version, +178 }) => { +179 if self.server_config.is_none() { +180 debug!("dropping packet with unsupported version"); +181 return None; +182 } +183 trace!("sending version negotiation"); +184 // Negotiate versions +185 Header::VersionNegotiate { +186 random: self.rng.random::<u8>() | 0x40, +187 src_cid: dst_cid, +188 dst_cid: src_cid, +189 } +190 .encode(buf); +191 // Grease with a reserved version +192 buf.write::<u32>(match version { +193 0x0a1a_2a3a => 0x0a1a_2a4a, +194 _ => 0x0a1a_2a3a, +195 }); +196 for &version in &self.config.supported_versions { +197 buf.write(version); +198 } +199 return Some(DatagramEvent::Response(Transmit { +200 destination: network_path.remote, +201 ecn: None, +202 size: buf.len(), +203 segment_size: None, +204 src_ip: network_path.local_ip, +205 })); +206 } +207 Err(e) => { +208 trace!("malformed header: {}", e); +209 return None; +210 } +211 }; +212 +213 let dst_cid = event.first_decode.dst_cid(); +214 +215 if let Some(route_to) = self.index.get(&network_path, &event.first_decode) { +216 event.path_id = match route_to { +217 RouteDatagramTo::Incoming(_) => PathId::ZERO, +218 RouteDatagramTo::Connection(_, path_id) => path_id, +219 }; +220 match route_to { +221 RouteDatagramTo::Incoming(incoming_idx) => { +222 let incoming_buffer = &mut self.incoming_buffers[incoming_idx]; +223 let config = &self.server_config.as_ref().unwrap(); +224 +225 if incoming_buffer +226 .total_bytes +227 .checked_add(datagram_len as u64) +228 .is_some_and(|n| n <= config.incoming_buffer_size) +229 && self +230 .all_incoming_buffers_total_bytes +231 .checked_add(datagram_len as u64) +232 .is_some_and(|n| n <= config.incoming_buffer_size_total) +233 { +234 incoming_buffer.datagrams.push(event); +235 incoming_buffer.total_bytes += datagram_len as u64; +236 self.all_incoming_buffers_total_bytes += datagram_len as u64; +237 } +238 +239 None +240 } +241 RouteDatagramTo::Connection(ch, _path_id) => Some(DatagramEvent::ConnectionEvent( +242 ch, +243 ConnectionEvent(ConnectionEventInner::Datagram(event)), +244 )), +245 } +246 } else if event.first_decode.initial_header().is_some() { +247 // Potentially create a new connection +248 +249 self.handle_first_packet(datagram_len, event, network_path, buf) +250 } else if event.first_decode.has_long_header() { +251 debug!( +252 "ignoring non-initial packet for unknown connection {}", +253 dst_cid +254 ); +255 None +256 } else if !event.first_decode.is_initial() +257 && self.local_cid_generator.validate(dst_cid).is_err() +258 { +259 debug!("dropping packet with invalid CID"); 260 None -261 } else { -262 // If we got this far, we're receiving a seemingly valid packet for an unknown -263 // connection. Send a stateless reset if possible. -264 self.stateless_reset(now, datagram_len, network_path, dst_cid, buf) -265 .map(DatagramEvent::Response) -266 } -267 } -268 -269 /// Builds a stateless reset packet to respond with -270 fn stateless_reset( -271 &mut self, -272 now: Instant, -273 inciting_dgram_len: usize, -274 network_path: FourTuple, -275 dst_cid: ConnectionId, -276 buf: &mut Vec<u8>, -277 ) -> Option<Transmit> { -278 if self -279 .last_stateless_reset -280 .is_some_and(|last| last + self.config.min_reset_interval > now) -281 { -282 debug!("ignoring unexpected packet within minimum stateless reset interval"); -283 return None; -284 } -285 -286 /// Minimum amount of padding for the stateless reset to look like a short-header packet -287 const MIN_PADDING_LEN: usize = 5; +261 } else if dst_cid.is_empty() { +262 trace!("dropping unrecognized short packet without ID"); +263 None +264 } else { +265 // If we got this far, we're receiving a seemingly valid packet for an unknown +266 // connection. Send a stateless reset if possible. +267 self.stateless_reset(now, datagram_len, network_path, dst_cid, buf) +268 .map(DatagramEvent::Response) +269 } +270 } +271 +272 /// Builds a stateless reset packet to respond with +273 fn stateless_reset( +274 &mut self, +275 now: Instant, +276 inciting_dgram_len: usize, +277 network_path: FourTuple, +278 dst_cid: ConnectionId, +279 buf: &mut Vec<u8>, +280 ) -> Option<Transmit> { +281 if self +282 .last_stateless_reset +283 .is_some_and(|last| last + self.config.min_reset_interval > now) +284 { +285 debug!("ignoring unexpected packet within minimum stateless reset interval"); +286 return None; +287 } 288 -289 // Prevent amplification attacks and reset loops by ensuring we pad to at most 1 byte -290 // smaller than the inciting packet. -291 let max_padding_len = match inciting_dgram_len.checked_sub(RESET_TOKEN_SIZE) { -292 Some(headroom) if headroom > MIN_PADDING_LEN => headroom - 1, -293 _ => { -294 debug!( -295 "ignoring unexpected {} byte packet: not larger than minimum stateless reset size", -296 inciting_dgram_len -297 ); -298 return None; -299 } -300 }; -301 -302 debug!(%dst_cid, %network_path.remote, "sending stateless reset"); -303 self.last_stateless_reset = Some(now); -304 // Resets with at least this much padding can't possibly be distinguished from real packets -305 const IDEAL_MIN_PADDING_LEN: usize = MIN_PADDING_LEN + MAX_CID_SIZE; -306 let padding_len = if max_padding_len <= IDEAL_MIN_PADDING_LEN { -307 max_padding_len -308 } else { -309 self.rng -310 .random_range(IDEAL_MIN_PADDING_LEN..max_padding_len) -311 }; -312 buf.reserve(padding_len + RESET_TOKEN_SIZE); -313 buf.resize(padding_len, 0); -314 self.rng.fill_bytes(&mut buf[0..padding_len]); -315 buf[0] = 0b0100_0000 | (buf[0] >> 2); -316 buf.extend_from_slice(&ResetToken::new(&*self.config.reset_key, dst_cid)); -317 -318 debug_assert!(buf.len() < inciting_dgram_len); -319 -320 Some(Transmit { -321 destination: network_path.remote, -322 ecn: None, -323 size: buf.len(), -324 segment_size: None, -325 src_ip: network_path.local_ip, -326 }) -327 } -328 -329 /// Initiate a connection -330 pub fn connect( -331 &mut self, -332 now: Instant, -333 config: ClientConfig, -334 remote: SocketAddr, -335 server_name: &str, -336 ) -> Result<(ConnectionHandle, Connection), ConnectError> { -337 if self.cids_exhausted() { -338 return Err(ConnectError::CidsExhausted); -339 } -340 if remote.port() == 0 || remote.ip().is_unspecified() { -341 return Err(ConnectError::InvalidRemoteAddress(remote)); +289 /// Minimum amount of padding for the stateless reset to look like a short-header packet +290 const MIN_PADDING_LEN: usize = 5; +291 +292 // Prevent amplification attacks and reset loops by ensuring we pad to at most 1 byte +293 // smaller than the inciting packet. +294 let max_padding_len = match inciting_dgram_len.checked_sub(RESET_TOKEN_SIZE) { +295 Some(headroom) if headroom > MIN_PADDING_LEN => headroom - 1, +296 _ => { +297 debug!( +298 "ignoring unexpected {} byte packet: not larger than minimum stateless reset size", +299 inciting_dgram_len +300 ); +301 return None; +302 } +303 }; +304 +305 debug!(%dst_cid, %network_path.remote, "sending stateless reset"); +306 self.last_stateless_reset = Some(now); +307 // Resets with at least this much padding can't possibly be distinguished from real packets +308 const IDEAL_MIN_PADDING_LEN: usize = MIN_PADDING_LEN + MAX_CID_SIZE; +309 let padding_len = if max_padding_len <= IDEAL_MIN_PADDING_LEN { +310 max_padding_len +311 } else { +312 self.rng +313 .random_range(IDEAL_MIN_PADDING_LEN..max_padding_len) +314 }; +315 buf.reserve(padding_len + RESET_TOKEN_SIZE); +316 buf.resize(padding_len, 0); +317 self.rng.fill_bytes(&mut buf[0..padding_len]); +318 buf[0] = 0b0100_0000 | (buf[0] >> 2); +319 buf.extend_from_slice(&ResetToken::new(&*self.config.reset_key, dst_cid)); +320 +321 debug_assert!(buf.len() < inciting_dgram_len); +322 +323 Some(Transmit { +324 destination: network_path.remote, +325 ecn: None, +326 size: buf.len(), +327 segment_size: None, +328 src_ip: network_path.local_ip, +329 }) +330 } +331 +332 /// Initiate a connection +333 pub fn connect( +334 &mut self, +335 now: Instant, +336 config: ClientConfig, +337 remote: SocketAddr, +338 server_name: &str, +339 ) -> Result<(ConnectionHandle, Connection), ConnectError> { +340 if self.cids_exhausted() { +341 return Err(ConnectError::CidsExhausted); 342 } -343 if !self.config.supported_versions.contains(&config.version) { -344 return Err(ConnectError::UnsupportedVersion); +343 if remote.port() == 0 || remote.ip().is_unspecified() { +344 return Err(ConnectError::InvalidRemoteAddress(remote)); 345 } -346 -347 let remote_id = (config.initial_dst_cid_provider)(); -348 trace!(initial_dcid = %remote_id); +346 if !self.config.supported_versions.contains(&config.version) { +347 return Err(ConnectError::UnsupportedVersion); +348 } 349 -350 let ch = ConnectionHandle(self.connections.vacant_key()); -351 let local_cid = self.new_cid(ch, PathId::ZERO); -352 let params = TransportParameters::new( -353 &config.transport, -354 &self.config, -355 self.local_cid_generator.as_ref(), -356 local_cid, -357 None, -358 &mut self.rng, -359 ); -360 let tls = config -361 .crypto -362 .start_session(config.version, server_name, &params)?; -363 -364 let conn = self.add_connection( -365 ch, -366 config.version, -367 remote_id, -368 local_cid, -369 remote_id, -370 FourTuple { -371 remote, -372 local_ip: None, -373 }, -374 now, -375 tls, -376 config.transport, -377 SideArgs::Client { -378 token_store: config.token_store, -379 server_name: server_name.into(), -380 }, -381 &params, -382 ); -383 Ok((ch, conn)) -384 } -385 -386 /// Generates new CIDs and creates message to send to the connection state -387 fn send_new_identifiers( -388 &mut self, -389 path_id: PathId, -390 now: Instant, -391 ch: ConnectionHandle, -392 num: u64, -393 ) -> ConnectionEvent { -394 let mut ids = vec![]; -395 for _ in 0..num { -396 let id = self.new_cid(ch, path_id); -397 let cid_meta = self.connections[ch].local_cids.entry(path_id).or_default(); -398 let sequence = cid_meta.issued; -399 cid_meta.issued += 1; -400 cid_meta.cids.insert(sequence, id); -401 ids.push(IssuedCid { -402 path_id, -403 sequence, -404 id, -405 reset_token: ResetToken::new(&*self.config.reset_key, id), -406 }); -407 } -408 ConnectionEvent(ConnectionEventInner::NewIdentifiers( -409 ids, -410 now, -411 self.local_cid_generator.cid_len(), -412 self.local_cid_generator.cid_lifetime(), -413 )) -414 } -415 -416 /// Generate a connection ID for `ch` -417 fn new_cid(&mut self, ch: ConnectionHandle, path_id: PathId) -> ConnectionId { -418 loop { -419 let cid = self.local_cid_generator.generate_cid(); -420 if cid.is_empty() { -421 // Zero-length CID; nothing to track -422 debug_assert_eq!(self.local_cid_generator.cid_len(), 0); -423 return cid; -424 } -425 if let hash_map::Entry::Vacant(e) = self.index.connection_ids.entry(cid) { -426 e.insert((ch, path_id)); -427 break cid; -428 } -429 } -430 } -431 -432 fn handle_first_packet( -433 &mut self, -434 datagram_len: usize, -435 event: DatagramConnectionEvent, -436 network_path: FourTuple, -437 buf: &mut Vec<u8>, -438 ) -> Option<DatagramEvent> { -439 let dst_cid = event.first_decode.dst_cid(); -440 let header = event.first_decode.initial_header().unwrap(); -441 -442 let Some(server_config) = &self.server_config else { -443 debug!("packet for unrecognized connection {}", dst_cid); -444 return self -445 .stateless_reset(event.now, datagram_len, network_path, dst_cid, buf) -446 .map(DatagramEvent::Response); -447 }; -448 -449 if datagram_len < MIN_INITIAL_SIZE as usize { -450 debug!("ignoring short initial for connection {}", dst_cid); -451 return None; -452 } -453 -454 let crypto = match server_config.crypto.initial_keys(header.version, dst_cid) { -455 Ok(keys) => keys, -456 Err(UnsupportedVersion) => { -457 // This probably indicates that the user set supported_versions incorrectly in -458 // `EndpointConfig`. -459 debug!( -460 "ignoring initial packet version {:#x} unsupported by cryptographic layer", -461 header.version -462 ); -463 return None; -464 } -465 }; -466 -467 if let Err(reason) = self.early_validate_first_packet(header) { -468 return Some(DatagramEvent::Response(self.initial_close( -469 header.version, -470 network_path, -471 &crypto, -472 header.src_cid, -473 reason, -474 buf, -475 ))); -476 } -477 -478 let packet = match event.first_decode.finish(Some(&*crypto.header.remote)) { -479 Ok(packet) => packet, -480 Err(e) => { -481 trace!("unable to decode initial packet: {}", e); -482 return None; -483 } -484 }; -485 -486 if !packet.reserved_bits_valid() { -487 debug!("dropping connection attempt with invalid reserved bits"); -488 return None; -489 } -490 -491 let Header::Initial(header) = packet.header else { -492 panic!("non-initial packet in handle_first_packet()"); -493 }; -494 -495 let server_config = self.server_config.as_ref().unwrap().clone(); -496 -497 let token = match IncomingToken::from_header(&header, &server_config, network_path.remote) { -498 Ok(token) => token, -499 Err(InvalidRetryTokenError) => { -500 debug!("rejecting invalid retry token"); -501 return Some(DatagramEvent::Response(self.initial_close( -502 header.version, -503 network_path, -504 &crypto, -505 header.src_cid, -506 TransportError::INVALID_TOKEN(""), -507 buf, -508 ))); -509 } -510 }; -511 -512 let incoming_idx = self.incoming_buffers.insert(IncomingBuffer::default()); -513 self.index -514 .insert_initial_incoming(header.dst_cid, incoming_idx); -515 -516 Some(DatagramEvent::NewConnection(Incoming { -517 received_at: event.now, -518 network_path, -519 ecn: event.ecn, -520 packet: InitialPacket { -521 header, -522 header_data: packet.header_data, -523 payload: packet.payload, -524 }, -525 rest: event.remaining, -526 crypto, -527 token, -528 incoming_idx, -529 improper_drop_warner: IncomingImproperDropWarner, -530 })) -531 } -532 -533 /// Attempt to accept this incoming connection (an error may still occur) -534 // box err to avoid clippy::result_large_err -535 pub fn accept( -536 &mut self, -537 mut incoming: Incoming, -538 now: Instant, -539 buf: &mut Vec<u8>, -540 server_config: Option<Arc<ServerConfig>>, -541 ) -> Result<(ConnectionHandle, Connection), Box<AcceptError>> { -542 let remote_address_validated = incoming.remote_address_validated(); -543 incoming.improper_drop_warner.dismiss(); -544 let incoming_buffer = self.incoming_buffers.remove(incoming.incoming_idx); -545 self.all_incoming_buffers_total_bytes -= incoming_buffer.total_bytes; -546 -547 let packet_number = incoming.packet.header.number.expand(0); -548 let InitialHeader { -549 src_cid, -550 dst_cid, -551 version, -552 .. -553 } = incoming.packet.header; -554 let server_config = -555 server_config.unwrap_or_else(|| self.server_config.as_ref().unwrap().clone()); -556 -557 if server_config -558 .transport -559 .max_idle_timeout -560 .is_some_and(|timeout| { -561 incoming.received_at + Duration::from_millis(timeout.into()) <= now -562 }) -563 { -564 debug!("abandoning accept of stale initial"); -565 self.index.remove_initial(dst_cid); -566 return Err(Box::new(AcceptError { -567 cause: ConnectionError::TimedOut, -568 response: None, -569 })); -570 } -571 -572 if self.cids_exhausted() { -573 debug!("refusing connection"); -574 self.index.remove_initial(dst_cid); -575 return Err(Box::new(AcceptError { -576 cause: ConnectionError::CidsExhausted, -577 response: Some(self.initial_close( -578 version, -579 incoming.network_path, -580 &incoming.crypto, -581 src_cid, -582 TransportError::CONNECTION_REFUSED(""), -583 buf, -584 )), -585 })); -586 } -587 -588 if incoming -589 .crypto -590 .packet -591 .remote -592 .decrypt( -593 PathId::ZERO, -594 packet_number, -595 &incoming.packet.header_data, -596 &mut incoming.packet.payload, -597 ) -598 .is_err() -599 { -600 debug!(packet_number, "failed to authenticate initial packet"); -601 self.index.remove_initial(dst_cid); -602 return Err(Box::new(AcceptError { -603 cause: TransportError::PROTOCOL_VIOLATION("authentication failed").into(), -604 response: None, -605 })); -606 }; -607 -608 let ch = ConnectionHandle(self.connections.vacant_key()); -609 let local_cid = self.new_cid(ch, PathId::ZERO); -610 let mut params = TransportParameters::new( -611 &server_config.transport, -612 &self.config, -613 self.local_cid_generator.as_ref(), -614 local_cid, -615 Some(&server_config), -616 &mut self.rng, -617 ); -618 params.stateless_reset_token = Some(ResetToken::new(&*self.config.reset_key, local_cid)); -619 params.original_dst_cid = Some(incoming.token.orig_dst_cid); -620 params.retry_src_cid = incoming.token.retry_src_cid; -621 let mut pref_addr_cid = None; -622 if server_config.has_preferred_address() { -623 let cid = self.new_cid(ch, PathId::ZERO); -624 pref_addr_cid = Some(cid); -625 params.preferred_address = Some(PreferredAddress { -626 address_v4: server_config.preferred_address_v4, -627 address_v6: server_config.preferred_address_v6, -628 connection_id: cid, -629 stateless_reset_token: ResetToken::new(&*self.config.reset_key, cid), -630 }); -631 } -632 -633 let tls = server_config.crypto.start_session(version, &params); -634 let transport_config = server_config.transport.clone(); -635 let mut conn = self.add_connection( -636 ch, -637 version, -638 dst_cid, -639 local_cid, -640 src_cid, -641 incoming.network_path, -642 incoming.received_at, -643 tls, -644 transport_config, -645 SideArgs::Server { -646 server_config, -647 pref_addr_cid, -648 path_validated: remote_address_validated, -649 }, -650 &params, -651 ); -652 self.index.insert_initial(dst_cid, ch); -653 -654 match conn.handle_first_packet( -655 incoming.received_at, -656 incoming.network_path, -657 incoming.ecn, -658 packet_number, -659 incoming.packet, -660 incoming.rest, -661 ) { -662 Ok(()) => { -663 trace!( -664 id = ch.0, -665 icid = %dst_cid, -666 network_path = %incoming.network_path, -667 "new connection", -668 ); -669 -670 for event in incoming_buffer.datagrams { -671 conn.handle_event(ConnectionEvent(ConnectionEventInner::Datagram(event))) -672 } -673 -674 Ok((ch, conn)) -675 } -676 Err(e) => { -677 debug!("handshake failed: {}", e); -678 self.handle_event(ch, EndpointEvent(EndpointEventInner::Drained)); -679 let response = match e { -680 ConnectionError::TransportError(ref e) => Some(self.initial_close( -681 version, -682 incoming.network_path, -683 &incoming.crypto, -684 src_cid, -685 e.clone(), -686 buf, -687 )), -688 _ => None, -689 }; -690 Err(Box::new(AcceptError { cause: e, response })) -691 } -692 } -693 } -694 -695 /// Check if we should refuse a connection attempt regardless of the packet's contents -696 fn early_validate_first_packet( -697 &mut self, -698 header: &ProtectedInitialHeader, -699 ) -> Result<(), TransportError> { -700 let config = &self.server_config.as_ref().unwrap(); -701 if self.cids_exhausted() || self.incoming_buffers.len() >= config.max_incoming { -702 return Err(TransportError::CONNECTION_REFUSED("")); -703 } -704 -705 // RFC9000 §7.2 dictates that initial (client-chosen) destination CIDs must be at least 8 -706 // bytes. If this is a Retry packet, then the length must instead match our usual CID -707 // length. If we ever issue non-Retry address validation tokens via `NEW_TOKEN`, then we'll -708 // also need to validate CID length for those after decoding the token. -709 if header.dst_cid.len() < 8 -710 && (header.token_pos.is_empty() -711 || header.dst_cid.len() != self.local_cid_generator.cid_len()) -712 { -713 debug!( -714 "rejecting connection due to invalid DCID length {}", -715 header.dst_cid.len() -716 ); -717 return Err(TransportError::PROTOCOL_VIOLATION( -718 "invalid destination CID length", -719 )); -720 } -721 -722 Ok(()) -723 } +350 let remote_id = (config.initial_dst_cid_provider)(); +351 trace!(initial_dcid = %remote_id); +352 +353 let ch = ConnectionHandle(self.connections.vacant_key()); +354 let local_cid = self.new_cid(ch, PathId::ZERO); +355 let params = TransportParameters::new( +356 &config.transport, +357 &self.config, +358 self.local_cid_generator.as_ref(), +359 local_cid, +360 None, +361 &mut self.rng, +362 ); +363 let tls = config +364 .crypto +365 .start_session(config.version, server_name, &params)?; +366 +367 let conn = self.add_connection( +368 ch, +369 config.version, +370 remote_id, +371 local_cid, +372 remote_id, +373 FourTuple { +374 remote, +375 local_ip: None, +376 }, +377 now, +378 tls, +379 config.transport, +380 SideArgs::Client { +381 token_store: config.token_store, +382 server_name: server_name.into(), +383 }, +384 &params, +385 ); +386 Ok((ch, conn)) +387 } +388 +389 /// Generates new CIDs and creates message to send to the connection state +390 fn send_new_identifiers( +391 &mut self, +392 path_id: PathId, +393 now: Instant, +394 ch: ConnectionHandle, +395 num: u64, +396 ) -> ConnectionEvent { +397 let mut ids = vec![]; +398 for _ in 0..num { +399 let id = self.new_cid(ch, path_id); +400 let cid_meta = self.connections[ch].local_cids.entry(path_id).or_default(); +401 let sequence = cid_meta.issued; +402 cid_meta.issued += 1; +403 cid_meta.cids.insert(sequence, id); +404 ids.push(IssuedCid { +405 path_id, +406 sequence, +407 id, +408 reset_token: ResetToken::new(&*self.config.reset_key, id), +409 }); +410 } +411 ConnectionEvent(ConnectionEventInner::NewIdentifiers( +412 ids, +413 now, +414 self.local_cid_generator.cid_len(), +415 self.local_cid_generator.cid_lifetime(), +416 )) +417 } +418 +419 /// Generate a connection ID for `ch` +420 fn new_cid(&mut self, ch: ConnectionHandle, path_id: PathId) -> ConnectionId { +421 loop { +422 let cid = self.local_cid_generator.generate_cid(); +423 if cid.is_empty() { +424 // Zero-length CID; nothing to track +425 debug_assert_eq!(self.local_cid_generator.cid_len(), 0); +426 return cid; +427 } +428 if let hash_map::Entry::Vacant(e) = self.index.connection_ids.entry(cid) { +429 e.insert((ch, path_id)); +430 break cid; +431 } +432 } +433 } +434 +435 fn handle_first_packet( +436 &mut self, +437 datagram_len: usize, +438 event: DatagramConnectionEvent, +439 network_path: FourTuple, +440 buf: &mut Vec<u8>, +441 ) -> Option<DatagramEvent> { +442 let dst_cid = event.first_decode.dst_cid(); +443 let header = event.first_decode.initial_header().unwrap(); +444 +445 let Some(server_config) = &self.server_config else { +446 debug!("packet for unrecognized connection {}", dst_cid); +447 return self +448 .stateless_reset(event.now, datagram_len, network_path, dst_cid, buf) +449 .map(DatagramEvent::Response); +450 }; +451 +452 if datagram_len < MIN_INITIAL_SIZE as usize { +453 debug!("ignoring short initial for connection {}", dst_cid); +454 return None; +455 } +456 +457 let crypto = match server_config.crypto.initial_keys(header.version, dst_cid) { +458 Ok(keys) => keys, +459 Err(UnsupportedVersion) => { +460 // This probably indicates that the user set supported_versions incorrectly in +461 // `EndpointConfig`. +462 debug!( +463 "ignoring initial packet version {:#x} unsupported by cryptographic layer", +464 header.version +465 ); +466 return None; +467 } +468 }; +469 +470 if let Err(reason) = self.early_validate_first_packet(header) { +471 return Some(DatagramEvent::Response(self.initial_close( +472 header.version, +473 network_path, +474 &crypto, +475 header.src_cid, +476 reason, +477 buf, +478 ))); +479 } +480 +481 let packet = match event.first_decode.finish(Some(&*crypto.header.remote)) { +482 Ok(packet) => packet, +483 Err(e) => { +484 trace!("unable to decode initial packet: {}", e); +485 return None; +486 } +487 }; +488 +489 if !packet.reserved_bits_valid() { +490 debug!("dropping connection attempt with invalid reserved bits"); +491 return None; +492 } +493 +494 let Header::Initial(header) = packet.header else { +495 panic!("non-initial packet in handle_first_packet()"); +496 }; +497 +498 let server_config = self.server_config.as_ref().unwrap().clone(); +499 +500 let token = match IncomingToken::from_header(&header, &server_config, network_path.remote) { +501 Ok(token) => token, +502 Err(InvalidRetryTokenError) => { +503 debug!("rejecting invalid retry token"); +504 return Some(DatagramEvent::Response(self.initial_close( +505 header.version, +506 network_path, +507 &crypto, +508 header.src_cid, +509 TransportError::INVALID_TOKEN(""), +510 buf, +511 ))); +512 } +513 }; +514 +515 let incoming_idx = self.incoming_buffers.insert(IncomingBuffer::default()); +516 self.index +517 .insert_initial_incoming(header.dst_cid, incoming_idx); +518 +519 Some(DatagramEvent::NewConnection(Incoming { +520 received_at: event.now, +521 network_path, +522 ecn: event.ecn, +523 packet: InitialPacket { +524 header, +525 header_data: packet.header_data, +526 payload: packet.payload, +527 }, +528 rest: event.remaining, +529 crypto, +530 token, +531 incoming_idx, +532 improper_drop_warner: IncomingImproperDropWarner, +533 })) +534 } +535 +536 /// Attempt to accept this incoming connection (an error may still occur) +537 // box err to avoid clippy::result_large_err +538 pub fn accept( +539 &mut self, +540 mut incoming: Incoming, +541 now: Instant, +542 buf: &mut Vec<u8>, +543 server_config: Option<Arc<ServerConfig>>, +544 ) -> Result<(ConnectionHandle, Connection), Box<AcceptError>> { +545 let remote_address_validated = incoming.remote_address_validated(); +546 incoming.improper_drop_warner.dismiss(); +547 let incoming_buffer = self.incoming_buffers.remove(incoming.incoming_idx); +548 self.all_incoming_buffers_total_bytes -= incoming_buffer.total_bytes; +549 +550 let packet_number = incoming.packet.header.number.expand(0); +551 let InitialHeader { +552 src_cid, +553 dst_cid, +554 version, +555 .. +556 } = incoming.packet.header; +557 let server_config = +558 server_config.unwrap_or_else(|| self.server_config.as_ref().unwrap().clone()); +559 +560 if server_config +561 .transport +562 .max_idle_timeout +563 .is_some_and(|timeout| { +564 incoming.received_at + Duration::from_millis(timeout.into()) <= now +565 }) +566 { +567 debug!("abandoning accept of stale initial"); +568 self.index.remove_initial(dst_cid); +569 return Err(Box::new(AcceptError { +570 cause: ConnectionError::TimedOut, +571 response: None, +572 })); +573 } +574 +575 if self.cids_exhausted() { +576 debug!("refusing connection"); +577 self.index.remove_initial(dst_cid); +578 return Err(Box::new(AcceptError { +579 cause: ConnectionError::CidsExhausted, +580 response: Some(self.initial_close( +581 version, +582 incoming.network_path, +583 &incoming.crypto, +584 src_cid, +585 TransportError::CONNECTION_REFUSED(""), +586 buf, +587 )), +588 })); +589 } +590 +591 if incoming +592 .crypto +593 .packet +594 .remote +595 .decrypt( +596 PathId::ZERO, +597 packet_number, +598 &incoming.packet.header_data, +599 &mut incoming.packet.payload, +600 ) +601 .is_err() +602 { +603 debug!(packet_number, "failed to authenticate initial packet"); +604 self.index.remove_initial(dst_cid); +605 return Err(Box::new(AcceptError { +606 cause: TransportError::PROTOCOL_VIOLATION("authentication failed").into(), +607 response: None, +608 })); +609 }; +610 +611 let ch = ConnectionHandle(self.connections.vacant_key()); +612 let local_cid = self.new_cid(ch, PathId::ZERO); +613 let mut params = TransportParameters::new( +614 &server_config.transport, +615 &self.config, +616 self.local_cid_generator.as_ref(), +617 local_cid, +618 Some(&server_config), +619 &mut self.rng, +620 ); +621 params.stateless_reset_token = Some(ResetToken::new(&*self.config.reset_key, local_cid)); +622 params.original_dst_cid = Some(incoming.token.orig_dst_cid); +623 params.retry_src_cid = incoming.token.retry_src_cid; +624 let mut pref_addr_cid = None; +625 if server_config.has_preferred_address() { +626 let cid = self.new_cid(ch, PathId::ZERO); +627 pref_addr_cid = Some(cid); +628 params.preferred_address = Some(PreferredAddress { +629 address_v4: server_config.preferred_address_v4, +630 address_v6: server_config.preferred_address_v6, +631 connection_id: cid, +632 stateless_reset_token: ResetToken::new(&*self.config.reset_key, cid), +633 }); +634 } +635 +636 let tls = server_config.crypto.start_session(version, &params); +637 let transport_config = server_config.transport.clone(); +638 let mut conn = self.add_connection( +639 ch, +640 version, +641 dst_cid, +642 local_cid, +643 src_cid, +644 incoming.network_path, +645 incoming.received_at, +646 tls, +647 transport_config, +648 SideArgs::Server { +649 server_config, +650 pref_addr_cid, +651 path_validated: remote_address_validated, +652 }, +653 &params, +654 ); +655 self.index.insert_initial(dst_cid, ch); +656 +657 match conn.handle_first_packet( +658 incoming.received_at, +659 incoming.network_path, +660 incoming.ecn, +661 packet_number, +662 incoming.packet, +663 incoming.rest, +664 ) { +665 Ok(()) => { +666 trace!( +667 id = ch.0, +668 icid = %dst_cid, +669 network_path = %incoming.network_path, +670 "new connection", +671 ); +672 +673 for event in incoming_buffer.datagrams { +674 conn.handle_event(ConnectionEvent(ConnectionEventInner::Datagram(event))) +675 } +676 +677 Ok((ch, conn)) +678 } +679 Err(e) => { +680 debug!("handshake failed: {}", e); +681 self.handle_event(ch, EndpointEvent(EndpointEventInner::Drained)); +682 let response = match e { +683 ConnectionError::TransportError(ref e) => Some(self.initial_close( +684 version, +685 incoming.network_path, +686 &incoming.crypto, +687 src_cid, +688 e.clone(), +689 buf, +690 )), +691 _ => None, +692 }; +693 Err(Box::new(AcceptError { cause: e, response })) +694 } +695 } +696 } +697 +698 /// Check if we should refuse a connection attempt regardless of the packet's contents +699 fn early_validate_first_packet( +700 &mut self, +701 header: &ProtectedInitialHeader, +702 ) -> Result<(), TransportError> { +703 let config = &self.server_config.as_ref().unwrap(); +704 if self.cids_exhausted() || self.incoming_buffers.len() >= config.max_incoming { +705 return Err(TransportError::CONNECTION_REFUSED("")); +706 } +707 +708 // RFC9000 §7.2 dictates that initial (client-chosen) destination CIDs must be at least 8 +709 // bytes. If this is a Retry packet, then the length must instead match our usual CID +710 // length. If we ever issue non-Retry address validation tokens via `NEW_TOKEN`, then we'll +711 // also need to validate CID length for those after decoding the token. +712 if header.dst_cid.len() < 8 +713 && (header.token_pos.is_empty() +714 || header.dst_cid.len() != self.local_cid_generator.cid_len()) +715 { +716 debug!( +717 "rejecting connection due to invalid DCID length {}", +718 header.dst_cid.len() +719 ); +720 return Err(TransportError::PROTOCOL_VIOLATION( +721 "invalid destination CID length", +722 )); +723 } 724 -725 /// Reject this incoming connection attempt -726 pub fn refuse(&mut self, incoming: Incoming, buf: &mut Vec<u8>) -> Transmit { -727 self.clean_up_incoming(&incoming); -728 incoming.improper_drop_warner.dismiss(); -729 -730 self.initial_close( -731 incoming.packet.header.version, -732 incoming.network_path, -733 &incoming.crypto, -734 incoming.packet.header.src_cid, -735 TransportError::CONNECTION_REFUSED(""), -736 buf, -737 ) -738 } -739 -740 /// Respond with a retry packet, requiring the client to retry with address validation -741 /// -742 /// Errors if `incoming.may_retry()` is false. -743 pub fn retry(&mut self, incoming: Incoming, buf: &mut Vec<u8>) -> Result<Transmit, RetryError> { -744 if !incoming.may_retry() { -745 return Err(RetryError(Box::new(incoming))); -746 } -747 -748 self.clean_up_incoming(&incoming); -749 incoming.improper_drop_warner.dismiss(); +725 Ok(()) +726 } +727 +728 /// Reject this incoming connection attempt +729 pub fn refuse(&mut self, incoming: Incoming, buf: &mut Vec<u8>) -> Transmit { +730 self.clean_up_incoming(&incoming); +731 incoming.improper_drop_warner.dismiss(); +732 +733 self.initial_close( +734 incoming.packet.header.version, +735 incoming.network_path, +736 &incoming.crypto, +737 incoming.packet.header.src_cid, +738 TransportError::CONNECTION_REFUSED(""), +739 buf, +740 ) +741 } +742 +743 /// Respond with a retry packet, requiring the client to retry with address validation +744 /// +745 /// Errors if `incoming.may_retry()` is false. +746 pub fn retry(&mut self, incoming: Incoming, buf: &mut Vec<u8>) -> Result<Transmit, RetryError> { +747 if !incoming.may_retry() { +748 return Err(RetryError(Box::new(incoming))); +749 } 750 -751 let server_config = self.server_config.as_ref().unwrap(); -752 -753 // First Initial -754 // The peer will use this as the DCID of its following Initials. Initial DCIDs are -755 // looked up separately from Handshake/Data DCIDs, so there is no risk of collision -756 // with established connections. In the unlikely event that a collision occurs -757 // between two connections in the initial phase, both will fail fast and may be -758 // retried by the application layer. -759 let local_cid = self.local_cid_generator.generate_cid(); -760 -761 let payload = TokenPayload::Retry { -762 address: incoming.network_path.remote, -763 orig_dst_cid: incoming.packet.header.dst_cid, -764 issued: server_config.time_source.now(), -765 }; -766 let token = Token::new(payload, &mut self.rng).encode(&*server_config.token_key); -767 -768 let header = Header::Retry { -769 src_cid: local_cid, -770 dst_cid: incoming.packet.header.src_cid, -771 version: incoming.packet.header.version, -772 }; -773 -774 let encode = header.encode(buf); -775 buf.put_slice(&token); -776 buf.extend_from_slice(&server_config.crypto.retry_tag( -777 incoming.packet.header.version, -778 incoming.packet.header.dst_cid, -779 buf, -780 )); -781 encode.finish(buf, &*incoming.crypto.header.local, None); -782 -783 Ok(Transmit { -784 destination: incoming.network_path.remote, -785 ecn: None, -786 size: buf.len(), -787 segment_size: None, -788 src_ip: incoming.network_path.local_ip, -789 }) -790 } -791 -792 /// Ignore this incoming connection attempt, not sending any packet in response -793 /// -794 /// Doing this actively, rather than merely dropping the [`Incoming`], is necessary to prevent -795 /// memory leaks due to state within [`Endpoint`] tracking the incoming connection. -796 pub fn ignore(&mut self, incoming: Incoming) { -797 self.clean_up_incoming(&incoming); -798 incoming.improper_drop_warner.dismiss(); -799 } -800 -801 /// Clean up endpoint data structures associated with an `Incoming`. -802 fn clean_up_incoming(&mut self, incoming: &Incoming) { -803 self.index.remove_initial(incoming.packet.header.dst_cid); -804 let incoming_buffer = self.incoming_buffers.remove(incoming.incoming_idx); -805 self.all_incoming_buffers_total_bytes -= incoming_buffer.total_bytes; -806 } -807 -808 fn add_connection( -809 &mut self, -810 ch: ConnectionHandle, -811 version: u32, -812 init_cid: ConnectionId, -813 local_cid: ConnectionId, -814 remote_cid: ConnectionId, -815 network_path: FourTuple, -816 now: Instant, -817 tls: Box<dyn crypto::Session>, -818 transport_config: Arc<TransportConfig>, -819 side_args: SideArgs, -820 // Only used for qlog. -821 params: &TransportParameters, -822 ) -> Connection { -823 let mut rng_seed = [0; 32]; -824 self.rng.fill_bytes(&mut rng_seed); -825 let side = side_args.side(); -826 let pref_addr_cid = side_args.pref_addr_cid(); -827 -828 let qlog = -829 transport_config.create_qlog_sink(side_args.side(), network_path.remote, init_cid, now); +751 self.clean_up_incoming(&incoming); +752 incoming.improper_drop_warner.dismiss(); +753 +754 let server_config = self.server_config.as_ref().unwrap(); +755 +756 // First Initial +757 // The peer will use this as the DCID of its following Initials. Initial DCIDs are +758 // looked up separately from Handshake/Data DCIDs, so there is no risk of collision +759 // with established connections. In the unlikely event that a collision occurs +760 // between two connections in the initial phase, both will fail fast and may be +761 // retried by the application layer. +762 let local_cid = self.local_cid_generator.generate_cid(); +763 +764 let payload = TokenPayload::Retry { +765 address: incoming.network_path.remote, +766 orig_dst_cid: incoming.packet.header.dst_cid, +767 issued: server_config.time_source.now(), +768 }; +769 let token = Token::new(payload, &mut self.rng).encode(&*server_config.token_key); +770 +771 let header = Header::Retry { +772 src_cid: local_cid, +773 dst_cid: incoming.packet.header.src_cid, +774 version: incoming.packet.header.version, +775 }; +776 +777 let encode = header.encode(buf); +778 buf.put_slice(&token); +779 buf.extend_from_slice(&server_config.crypto.retry_tag( +780 incoming.packet.header.version, +781 incoming.packet.header.dst_cid, +782 buf, +783 )); +784 encode.finish(buf, &*incoming.crypto.header.local, None); +785 +786 Ok(Transmit { +787 destination: incoming.network_path.remote, +788 ecn: None, +789 size: buf.len(), +790 segment_size: None, +791 src_ip: incoming.network_path.local_ip, +792 }) +793 } +794 +795 /// Ignore this incoming connection attempt, not sending any packet in response +796 /// +797 /// Doing this actively, rather than merely dropping the [`Incoming`], is necessary to prevent +798 /// memory leaks due to state within [`Endpoint`] tracking the incoming connection. +799 pub fn ignore(&mut self, incoming: Incoming) { +800 self.clean_up_incoming(&incoming); +801 incoming.improper_drop_warner.dismiss(); +802 } +803 +804 /// Clean up endpoint data structures associated with an `Incoming`. +805 fn clean_up_incoming(&mut self, incoming: &Incoming) { +806 self.index.remove_initial(incoming.packet.header.dst_cid); +807 let incoming_buffer = self.incoming_buffers.remove(incoming.incoming_idx); +808 self.all_incoming_buffers_total_bytes -= incoming_buffer.total_bytes; +809 } +810 +811 fn add_connection( +812 &mut self, +813 ch: ConnectionHandle, +814 version: u32, +815 init_cid: ConnectionId, +816 local_cid: ConnectionId, +817 remote_cid: ConnectionId, +818 network_path: FourTuple, +819 now: Instant, +820 tls: Box<dyn crypto::Session>, +821 transport_config: Arc<TransportConfig>, +822 side_args: SideArgs, +823 // Only used for qlog. +824 params: &TransportParameters, +825 ) -> Connection { +826 let mut rng_seed = [0; 32]; +827 self.rng.fill_bytes(&mut rng_seed); +828 let side = side_args.side(); +829 let pref_addr_cid = side_args.pref_addr_cid(); 830 -831 qlog.emit_connection_started( -832 now, -833 local_cid, -834 remote_cid, -835 network_path.remote, -836 network_path.local_ip, -837 params, -838 ); -839 -840 let conn = Connection::new( -841 self.config.clone(), -842 transport_config, -843 init_cid, -844 local_cid, -845 remote_cid, -846 network_path, -847 tls, -848 self.local_cid_generator.as_ref(), -849 now, -850 version, -851 self.allow_mtud, -852 rng_seed, -853 side_args, -854 qlog, -855 ); -856 -857 let mut path_cids = PathLocalCids::default(); -858 path_cids.cids.insert(path_cids.issued, local_cid); -859 path_cids.issued += 1; -860 -861 if let Some(cid) = pref_addr_cid { -862 debug_assert_eq!(path_cids.issued, 1, "preferred address cid seq must be 1"); -863 path_cids.cids.insert(path_cids.issued, cid); -864 path_cids.issued += 1; -865 } -866 -867 let id = self.connections.insert(ConnectionMeta { -868 init_cid, -869 local_cids: FxHashMap::from_iter([(PathId::ZERO, path_cids)]), -870 network_path, -871 side, -872 reset_token: Default::default(), -873 }); -874 debug_assert_eq!(id, ch.0, "connection handle allocation out of sync"); -875 -876 self.index.insert_conn(network_path, local_cid, ch, side); -877 -878 conn -879 } +831 let qlog = +832 transport_config.create_qlog_sink(side_args.side(), network_path.remote, init_cid, now); +833 +834 qlog.emit_connection_started( +835 now, +836 local_cid, +837 remote_cid, +838 network_path.remote, +839 network_path.local_ip, +840 params, +841 ); +842 +843 let conn = Connection::new( +844 self.config.clone(), +845 transport_config, +846 init_cid, +847 local_cid, +848 remote_cid, +849 network_path, +850 tls, +851 self.local_cid_generator.as_ref(), +852 now, +853 version, +854 self.allow_mtud, +855 rng_seed, +856 side_args, +857 qlog, +858 ); +859 +860 let mut path_cids = PathLocalCids::default(); +861 path_cids.cids.insert(path_cids.issued, local_cid); +862 path_cids.issued += 1; +863 +864 if let Some(cid) = pref_addr_cid { +865 debug_assert_eq!(path_cids.issued, 1, "preferred address cid seq must be 1"); +866 path_cids.cids.insert(path_cids.issued, cid); +867 path_cids.issued += 1; +868 } +869 +870 let id = self.connections.insert(ConnectionMeta { +871 init_cid, +872 local_cids: FxHashMap::from_iter([(PathId::ZERO, path_cids)]), +873 network_path, +874 side, +875 reset_token: Default::default(), +876 }); +877 debug_assert_eq!(id, ch.0, "connection handle allocation out of sync"); +878 +879 self.index.insert_conn(network_path, local_cid, ch, side); 880 -881 fn initial_close( -882 &mut self, -883 version: u32, -884 network_path: FourTuple, -885 crypto: &Keys, -886 remote_id: ConnectionId, -887 reason: TransportError, -888 buf: &mut Vec<u8>, -889 ) -> Transmit { -890 // We don't need to worry about CID collisions in initial closes because the peer -891 // shouldn't respond, and if it does, and the CID collides, we'll just drop the -892 // unexpected response. -893 let local_id = self.local_cid_generator.generate_cid(); -894 let number = PacketNumber::U8(0); -895 let header = Header::Initial(InitialHeader { -896 dst_cid: remote_id, -897 src_cid: local_id, -898 number, -899 token: Bytes::new(), -900 version, -901 }); -902 -903 let partial_encode = header.encode(buf); -904 let max_len = -905 INITIAL_MTU as usize - partial_encode.header_len - crypto.packet.local.tag_len(); -906 frame::Close::from(reason).encoder(max_len).encode(buf); -907 buf.resize(buf.len() + crypto.packet.local.tag_len(), 0); -908 partial_encode.finish( -909 buf, -910 &*crypto.header.local, -911 Some((0, Default::default(), &*crypto.packet.local)), -912 ); -913 Transmit { -914 destination: network_path.remote, -915 ecn: None, -916 size: buf.len(), -917 segment_size: None, -918 src_ip: network_path.local_ip, -919 } -920 } -921 -922 /// Access the configuration used by this endpoint -923 pub fn config(&self) -> &EndpointConfig { -924 &self.config -925 } -926 -927 /// Number of connections that are currently open -928 pub fn open_connections(&self) -> usize { -929 self.connections.len() -930 } -931 -932 /// Counter for the number of bytes currently used -933 /// in the buffers for Initial and 0-RTT messages for pending incoming connections -934 pub fn incoming_buffer_bytes(&self) -> u64 { -935 self.all_incoming_buffers_total_bytes -936 } -937 -938 #[cfg(test)] -939 pub(crate) fn known_connections(&self) -> usize { -940 let x = self.connections.len(); -941 debug_assert_eq!(x, self.index.connection_ids_initial.len()); -942 // Not all connections have known reset tokens -943 debug_assert!(x >= self.index.connection_reset_tokens.0.len()); -944 // Not all connections have unique remotes, and 0-length CIDs might not be in use. -945 debug_assert!(x >= self.index.incoming_connection_remotes.len()); -946 debug_assert!(x >= self.index.outgoing_connection_remotes.len()); -947 x -948 } -949 -950 #[cfg(test)] -951 pub(crate) fn known_cids(&self) -> usize { -952 self.index.connection_ids.len() -953 } -954 -955 /// Whether we've used up 3/4 of the available CID space -956 /// -957 /// We leave some space unused so that `new_cid` can be relied upon to finish quickly. We don't -958 /// bother to check when CID longer than 4 bytes are used because 2^40 connections is a lot. -959 fn cids_exhausted(&self) -> bool { -960 let cid_len = self.local_cid_generator.cid_len(); -961 if cid_len == 0 || cid_len > 4 { -962 return false; -963 } -964 -965 // Keep this architecture-independent: on 32-bit targets, 2usize.pow(32) overflows. -966 let bits = (cid_len * 8) as u32; -967 let space = 1u64 << bits; -968 let reserve = 1u64 << (bits - 2); -969 let len = self.index.connection_ids.len() as u64; -970 -971 len > (space - reserve) -972 } -973} -974 -975impl fmt::Debug for Endpoint { -976 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -977 fmt.debug_struct("Endpoint") -978 .field("rng", &self.rng) -979 .field("index", &self.index) -980 .field("connections", &self.connections) -981 .field("config", &self.config) -982 .field("server_config", &self.server_config) -983 // incoming_buffers too large -984 .field("incoming_buffers.len", &self.incoming_buffers.len()) -985 .field( -986 "all_incoming_buffers_total_bytes", -987 &self.all_incoming_buffers_total_bytes, -988 ) -989 .finish() -990 } -991} -992 -993/// Buffered Initial and 0-RTT messages for a pending incoming connection -994#[derive(Default)] -995struct IncomingBuffer { -996 datagrams: Vec<DatagramConnectionEvent>, -997 total_bytes: u64, -998} -999 -1000/// Part of protocol state incoming datagrams can be routed to -1001#[derive(Copy, Clone, Debug)] -1002enum RouteDatagramTo { -1003 Incoming(usize), -1004 Connection(ConnectionHandle, PathId), -1005} -1006 -1007/// Maps packets to existing connections -1008#[derive(Default, Debug)] -1009struct ConnectionIndex { -1010 /// Identifies connections based on the initial DCID the peer utilized -1011 /// -1012 /// Uses a standard `HashMap` to protect against hash collision attacks. -1013 /// -1014 /// Used by the server, not the client. -1015 connection_ids_initial: HashMap<ConnectionId, RouteDatagramTo>, -1016 /// Identifies connections based on locally created CIDs -1017 /// -1018 /// Uses a cheaper hash function since keys are locally created -1019 connection_ids: FxHashMap<ConnectionId, (ConnectionHandle, PathId)>, -1020 /// Identifies incoming connections with zero-length CIDs -1021 /// -1022 /// Uses a standard `HashMap` to protect against hash collision attacks. -1023 incoming_connection_remotes: HashMap<FourTuple, ConnectionHandle>, -1024 /// Identifies outgoing connections with zero-length CIDs -1025 /// -1026 /// We don't yet support explicit source addresses for client connections, and zero-length CIDs -1027 /// require a unique 4-tuple, so at most one client connection with zero-length local CIDs -1028 /// may be established per remote. We must omit the local address from the key because we don't -1029 /// necessarily know what address we're sending from, and hence receiving at. -1030 /// -1031 /// Uses a standard `HashMap` to protect against hash collision attacks. -1032 // TODO(matheus23): It's possible this could be changed now that we track the full 4-tuple on the client side, too. -1033 outgoing_connection_remotes: HashMap<SocketAddr, ConnectionHandle>, -1034 /// Reset tokens provided by the peer for the CID each connection is currently sending to -1035 /// -1036 /// Incoming stateless resets do not have correct CIDs, so we need this to identify the correct -1037 /// recipient, if any. -1038 connection_reset_tokens: ResetTokenTable, -1039} -1040 -1041impl ConnectionIndex { -1042 /// Associate an incoming connection with its initial destination CID -1043 fn insert_initial_incoming(&mut self, dst_cid: ConnectionId, incoming_key: usize) { -1044 if dst_cid.is_empty() { -1045 return; -1046 } -1047 self.connection_ids_initial -1048 .insert(dst_cid, RouteDatagramTo::Incoming(incoming_key)); -1049 } -1050 -1051 /// Remove an association with an initial destination CID -1052 fn remove_initial(&mut self, dst_cid: ConnectionId) { -1053 if dst_cid.is_empty() { -1054 return; -1055 } -1056 let removed = self.connection_ids_initial.remove(&dst_cid); -1057 debug_assert!(removed.is_some()); -1058 } -1059 -1060 /// Associate a connection with its initial destination CID -1061 fn insert_initial(&mut self, dst_cid: ConnectionId, connection: ConnectionHandle) { -1062 if dst_cid.is_empty() { -1063 return; -1064 } -1065 self.connection_ids_initial.insert( -1066 dst_cid, -1067 RouteDatagramTo::Connection(connection, PathId::ZERO), -1068 ); -1069 } -1070 -1071 /// Associate a connection with its first locally-chosen destination CID if used, or otherwise -1072 /// its current 4-tuple -1073 fn insert_conn( -1074 &mut self, -1075 network_path: FourTuple, -1076 dst_cid: ConnectionId, -1077 connection: ConnectionHandle, -1078 side: Side, -1079 ) { -1080 match dst_cid.len() { -1081 0 => match side { -1082 Side::Server => { -1083 self.incoming_connection_remotes -1084 .insert(network_path, connection); -1085 } -1086 Side::Client => { -1087 self.outgoing_connection_remotes -1088 .insert(network_path.remote, connection); -1089 } -1090 }, -1091 _ => { -1092 self.connection_ids -1093 .insert(dst_cid, (connection, PathId::ZERO)); -1094 } -1095 } -1096 } -1097 -1098 /// Discard a connection ID -1099 fn retire(&mut self, dst_cid: ConnectionId) { -1100 self.connection_ids.remove(&dst_cid); -1101 } -1102 -1103 /// Remove all references to a connection -1104 fn remove(&mut self, conn: &ConnectionMeta) { -1105 if conn.side.is_server() { -1106 self.remove_initial(conn.init_cid); -1107 } -1108 for cid in conn -1109 .local_cids -1110 .values() -1111 .flat_map(|pcids| pcids.cids.values()) -1112 { -1113 self.connection_ids.remove(cid); -1114 } -1115 self.incoming_connection_remotes.remove(&conn.network_path); -1116 self.outgoing_connection_remotes -1117 .remove(&conn.network_path.remote); -1118 for (remote, token) in conn.reset_token.values() { -1119 self.connection_reset_tokens.remove(*remote, *token); -1120 } -1121 } -1122 -1123 /// Find the existing connection that `datagram` should be routed to, if any -1124 fn get(&self, network_path: &FourTuple, datagram: &PartialDecode) -> Option<RouteDatagramTo> { -1125 if !datagram.dst_cid().is_empty() -1126 && let Some(&(ch, path_id)) = self.connection_ids.get(&datagram.dst_cid()) -1127 { -1128 return Some(RouteDatagramTo::Connection(ch, path_id)); -1129 } -1130 if (datagram.is_initial() || datagram.is_0rtt()) -1131 && let Some(&ch) = self.connection_ids_initial.get(&datagram.dst_cid()) -1132 { -1133 return Some(ch); -1134 } -1135 if datagram.dst_cid().is_empty() { -1136 if let Some(&ch) = self.incoming_connection_remotes.get(network_path) { -1137 // Never multipath because QUIC-MULTIPATH 1.1 mandates the use of non-zero -1138 // length CIDs. So this is always PathId::ZERO. -1139 return Some(RouteDatagramTo::Connection(ch, PathId::ZERO)); -1140 } -1141 if let Some(&ch) = self.outgoing_connection_remotes.get(&network_path.remote) { -1142 // Like above, QUIC-MULTIPATH 1.1 mandates the use of non-zero length CIDs. -1143 return Some(RouteDatagramTo::Connection(ch, PathId::ZERO)); -1144 } -1145 } -1146 let data = datagram.data(); -1147 if data.len() < RESET_TOKEN_SIZE { -1148 return None; -1149 } -1150 // For stateless resets the PathId is meaningless since it closes the entire -1151 // connection regardless of path. So use PathId::ZERO. -1152 self.connection_reset_tokens -1153 .get(network_path.remote, &data[data.len() - RESET_TOKEN_SIZE..]) -1154 .cloned() -1155 .map(|ch| RouteDatagramTo::Connection(ch, PathId::ZERO)) -1156 } -1157} -1158 -1159#[derive(Debug)] -1160pub(crate) struct ConnectionMeta { -1161 init_cid: ConnectionId, -1162 /// Locally issues CIDs for each path -1163 local_cids: FxHashMap<PathId, PathLocalCids>, -1164 /// Remote/local addresses the connection began with -1165 /// -1166 /// Only needed to support connections with zero-length CIDs, which cannot migrate, so we don't -1167 /// bother keeping it up to date. -1168 network_path: FourTuple, -1169 side: Side, -1170 /// Reset tokens provided by the peer for CIDs we're currently sending to -1171 /// -1172 /// Since each reset token is for a CID, it is also for a fixed remote address which is -1173 /// also stored. This allows us to look up which reset tokens we might expect from a -1174 /// given remote address, see [`ResetTokenTable`]. -1175 /// -1176 /// Each path has its own active CID. We use the [`PathId`] as a unique index, allowing -1177 /// us to retire the reset token when a path is abandoned. -1178 // TODO(matheus23): Should be migrated to make reset tokens per 4-tuple instead of per remote addr -1179 reset_token: FxHashMap<PathId, (SocketAddr, ResetToken)>, -1180} -1181 -1182/// Local connection IDs for a single path -1183#[derive(Debug, Default)] -1184struct PathLocalCids { -1185 /// Number of connection IDs that have been issued in (PATH_)NEW_CONNECTION_ID frames -1186 /// -1187 /// Another way of saying this is that this is the next sequence number to be issued. -1188 issued: u64, -1189 /// Issues CIDs indexed by their sequence number. -1190 cids: FxHashMap<u64, ConnectionId>, -1191} -1192 -1193/// Internal identifier for a `Connection` currently associated with an endpoint -1194#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] -1195pub struct ConnectionHandle(pub usize); -1196 -1197impl From<ConnectionHandle> for usize { -1198 fn from(x: ConnectionHandle) -> Self { -1199 x.0 -1200 } -1201} -1202 -1203impl Index<ConnectionHandle> for Slab<ConnectionMeta> { -1204 type Output = ConnectionMeta; -1205 fn index(&self, ch: ConnectionHandle) -> &ConnectionMeta { -1206 &self[ch.0] -1207 } -1208} -1209 -1210impl IndexMut<ConnectionHandle> for Slab<ConnectionMeta> { -1211 fn index_mut(&mut self, ch: ConnectionHandle) -> &mut ConnectionMeta { -1212 &mut self[ch.0] -1213 } -1214} -1215 -1216/// Event resulting from processing a single datagram -1217pub enum DatagramEvent { -1218 /// The datagram is redirected to its `Connection` -1219 ConnectionEvent(ConnectionHandle, ConnectionEvent), -1220 /// The datagram may result in starting a new `Connection` -1221 NewConnection(Incoming), -1222 /// Response generated directly by the endpoint -1223 Response(Transmit), -1224} -1225 -1226/// An incoming connection for which the server has not yet begun its part of the handshake. -1227#[derive(derive_more::Debug)] -1228pub struct Incoming { -1229 #[debug(skip)] -1230 received_at: Instant, -1231 network_path: FourTuple, -1232 ecn: Option<EcnCodepoint>, -1233 #[debug(skip)] -1234 packet: InitialPacket, -1235 #[debug(skip)] -1236 rest: Option<BytesMut>, -1237 #[debug(skip)] -1238 crypto: Keys, -1239 token: IncomingToken, -1240 incoming_idx: usize, -1241 #[debug(skip)] -1242 improper_drop_warner: IncomingImproperDropWarner, -1243} -1244 -1245impl Incoming { -1246 /// The local IP address which was used when the peer established the connection -1247 pub fn local_ip(&self) -> Option<IpAddr> { -1248 self.network_path.local_ip -1249 } -1250 -1251 /// The peer's UDP address -1252 pub fn remote_address(&self) -> SocketAddr { -1253 self.network_path.remote -1254 } -1255 -1256 /// Whether the socket address that is initiating this connection has been validated -1257 /// -1258 /// This means that the sender of the initial packet has proved that they can receive traffic -1259 /// sent to `self.remote_address()`. +881 conn +882 } +883 +884 fn initial_close( +885 &mut self, +886 version: u32, +887 network_path: FourTuple, +888 crypto: &Keys, +889 remote_id: ConnectionId, +890 reason: TransportError, +891 buf: &mut Vec<u8>, +892 ) -> Transmit { +893 // We don't need to worry about CID collisions in initial closes because the peer +894 // shouldn't respond, and if it does, and the CID collides, we'll just drop the +895 // unexpected response. +896 let local_id = self.local_cid_generator.generate_cid(); +897 let number = PacketNumber::U8(0); +898 let header = Header::Initial(InitialHeader { +899 dst_cid: remote_id, +900 src_cid: local_id, +901 number, +902 token: Bytes::new(), +903 version, +904 }); +905 +906 let partial_encode = header.encode(buf); +907 let max_len = +908 INITIAL_MTU as usize - partial_encode.header_len - crypto.packet.local.tag_len(); +909 frame::Close::from(reason).encoder(max_len).encode(buf); +910 buf.resize(buf.len() + crypto.packet.local.tag_len(), 0); +911 partial_encode.finish( +912 buf, +913 &*crypto.header.local, +914 Some((0, Default::default(), &*crypto.packet.local)), +915 ); +916 Transmit { +917 destination: network_path.remote, +918 ecn: None, +919 size: buf.len(), +920 segment_size: None, +921 src_ip: network_path.local_ip, +922 } +923 } +924 +925 /// Access the configuration used by this endpoint +926 pub fn config(&self) -> &EndpointConfig { +927 &self.config +928 } +929 +930 /// Number of connections that are currently open +931 pub fn open_connections(&self) -> usize { +932 self.connections.len() +933 } +934 +935 /// Counter for the number of bytes currently used +936 /// in the buffers for Initial and 0-RTT messages for pending incoming connections +937 pub fn incoming_buffer_bytes(&self) -> u64 { +938 self.all_incoming_buffers_total_bytes +939 } +940 +941 #[cfg(test)] +942 pub(crate) fn known_connections(&self) -> usize { +943 let x = self.connections.len(); +944 debug_assert_eq!(x, self.index.connection_ids_initial.len()); +945 // Not all connections have known reset tokens +946 debug_assert!(x >= self.index.connection_reset_tokens.0.len()); +947 // Not all connections have unique remotes, and 0-length CIDs might not be in use. +948 debug_assert!(x >= self.index.incoming_connection_remotes.len()); +949 debug_assert!(x >= self.index.outgoing_connection_remotes.len()); +950 x +951 } +952 +953 #[cfg(test)] +954 pub(crate) fn known_cids(&self) -> usize { +955 self.index.connection_ids.len() +956 } +957 +958 /// Whether we've used up 3/4 of the available CID space +959 /// +960 /// We leave some space unused so that `new_cid` can be relied upon to finish quickly. We don't +961 /// bother to check when CID longer than 4 bytes are used because 2^40 connections is a lot. +962 fn cids_exhausted(&self) -> bool { +963 let cid_len = self.local_cid_generator.cid_len(); +964 if cid_len == 0 || cid_len > 4 { +965 return false; +966 } +967 +968 // Keep this architecture-independent: on 32-bit targets, 2usize.pow(32) overflows. +969 let bits = (cid_len * 8) as u32; +970 let space = 1u64 << bits; +971 let reserve = 1u64 << (bits - 2); +972 let len = self.index.connection_ids.len() as u64; +973 +974 len > (space - reserve) +975 } +976} +977 +978impl fmt::Debug for Endpoint { +979 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { +980 fmt.debug_struct("Endpoint") +981 .field("rng", &self.rng) +982 .field("index", &self.index) +983 .field("connections", &self.connections) +984 .field("config", &self.config) +985 .field("server_config", &self.server_config) +986 // incoming_buffers too large +987 .field("incoming_buffers.len", &self.incoming_buffers.len()) +988 .field( +989 "all_incoming_buffers_total_bytes", +990 &self.all_incoming_buffers_total_bytes, +991 ) +992 .finish() +993 } +994} +995 +996/// Buffered Initial and 0-RTT messages for a pending incoming connection +997#[derive(Default)] +998struct IncomingBuffer { +999 datagrams: Vec<DatagramConnectionEvent>, +1000 total_bytes: u64, +1001} +1002 +1003/// Part of protocol state incoming datagrams can be routed to +1004#[derive(Copy, Clone, Debug)] +1005enum RouteDatagramTo { +1006 Incoming(usize), +1007 Connection(ConnectionHandle, PathId), +1008} +1009 +1010/// Maps packets to existing connections +1011#[derive(Default, Debug)] +1012struct ConnectionIndex { +1013 /// Identifies connections based on the initial DCID the peer utilized +1014 /// +1015 /// Uses a standard `HashMap` to protect against hash collision attacks. +1016 /// +1017 /// Used by the server, not the client. +1018 connection_ids_initial: HashMap<ConnectionId, RouteDatagramTo>, +1019 /// Identifies connections based on locally created CIDs +1020 /// +1021 /// Uses a cheaper hash function since keys are locally created +1022 connection_ids: FxHashMap<ConnectionId, (ConnectionHandle, PathId)>, +1023 /// Identifies incoming connections with zero-length CIDs +1024 /// +1025 /// Uses a standard `HashMap` to protect against hash collision attacks. +1026 incoming_connection_remotes: HashMap<FourTuple, ConnectionHandle>, +1027 /// Identifies outgoing connections with zero-length CIDs +1028 /// +1029 /// We don't yet support explicit source addresses for client connections, and zero-length CIDs +1030 /// require a unique 4-tuple, so at most one client connection with zero-length local CIDs +1031 /// may be established per remote. We must omit the local address from the key because we don't +1032 /// necessarily know what address we're sending from, and hence receiving at. +1033 /// +1034 /// Uses a standard `HashMap` to protect against hash collision attacks. +1035 // TODO(matheus23): It's possible this could be changed now that we track the full 4-tuple on the client side, too. +1036 outgoing_connection_remotes: HashMap<SocketAddr, ConnectionHandle>, +1037 /// Reset tokens provided by the peer for the CID each connection is currently sending to +1038 /// +1039 /// Incoming stateless resets do not have correct CIDs, so we need this to identify the correct +1040 /// recipient, if any. +1041 connection_reset_tokens: ResetTokenTable, +1042} +1043 +1044impl ConnectionIndex { +1045 /// Associate an incoming connection with its initial destination CID +1046 fn insert_initial_incoming(&mut self, dst_cid: ConnectionId, incoming_key: usize) { +1047 if dst_cid.is_empty() { +1048 return; +1049 } +1050 self.connection_ids_initial +1051 .insert(dst_cid, RouteDatagramTo::Incoming(incoming_key)); +1052 } +1053 +1054 /// Remove an association with an initial destination CID +1055 fn remove_initial(&mut self, dst_cid: ConnectionId) { +1056 if dst_cid.is_empty() { +1057 return; +1058 } +1059 let removed = self.connection_ids_initial.remove(&dst_cid); +1060 debug_assert!(removed.is_some()); +1061 } +1062 +1063 /// Associate a connection with its initial destination CID +1064 fn insert_initial(&mut self, dst_cid: ConnectionId, connection: ConnectionHandle) { +1065 if dst_cid.is_empty() { +1066 return; +1067 } +1068 self.connection_ids_initial.insert( +1069 dst_cid, +1070 RouteDatagramTo::Connection(connection, PathId::ZERO), +1071 ); +1072 } +1073 +1074 /// Associate a connection with its first locally-chosen destination CID if used, or otherwise +1075 /// its current 4-tuple +1076 fn insert_conn( +1077 &mut self, +1078 network_path: FourTuple, +1079 dst_cid: ConnectionId, +1080 connection: ConnectionHandle, +1081 side: Side, +1082 ) { +1083 match dst_cid.len() { +1084 0 => match side { +1085 Side::Server => { +1086 self.incoming_connection_remotes +1087 .insert(network_path, connection); +1088 } +1089 Side::Client => { +1090 self.outgoing_connection_remotes +1091 .insert(network_path.remote, connection); +1092 } +1093 }, +1094 _ => { +1095 self.connection_ids +1096 .insert(dst_cid, (connection, PathId::ZERO)); +1097 } +1098 } +1099 } +1100 +1101 /// Discard a connection ID +1102 fn retire(&mut self, dst_cid: ConnectionId) { +1103 self.connection_ids.remove(&dst_cid); +1104 } +1105 +1106 /// Remove all references to a connection +1107 fn remove(&mut self, conn: &ConnectionMeta) { +1108 if conn.side.is_server() { +1109 self.remove_initial(conn.init_cid); +1110 } +1111 for cid in conn +1112 .local_cids +1113 .values() +1114 .flat_map(|pcids| pcids.cids.values()) +1115 { +1116 self.connection_ids.remove(cid); +1117 } +1118 self.incoming_connection_remotes.remove(&conn.network_path); +1119 self.outgoing_connection_remotes +1120 .remove(&conn.network_path.remote); +1121 for (remote, token) in conn.reset_token.values() { +1122 self.connection_reset_tokens.remove(*remote, *token); +1123 } +1124 } +1125 +1126 /// Find the existing connection that `datagram` should be routed to, if any +1127 fn get(&self, network_path: &FourTuple, datagram: &PartialDecode) -> Option<RouteDatagramTo> { +1128 if !datagram.dst_cid().is_empty() +1129 && let Some(&(ch, path_id)) = self.connection_ids.get(&datagram.dst_cid()) +1130 { +1131 return Some(RouteDatagramTo::Connection(ch, path_id)); +1132 } +1133 if (datagram.is_initial() || datagram.is_0rtt()) +1134 && let Some(&ch) = self.connection_ids_initial.get(&datagram.dst_cid()) +1135 { +1136 return Some(ch); +1137 } +1138 if datagram.dst_cid().is_empty() { +1139 if let Some(&ch) = self.incoming_connection_remotes.get(network_path) { +1140 // Never multipath because QUIC-MULTIPATH 1.1 mandates the use of non-zero +1141 // length CIDs. So this is always PathId::ZERO. +1142 return Some(RouteDatagramTo::Connection(ch, PathId::ZERO)); +1143 } +1144 if let Some(&ch) = self.outgoing_connection_remotes.get(&network_path.remote) { +1145 // Like above, QUIC-MULTIPATH 1.1 mandates the use of non-zero length CIDs. +1146 return Some(RouteDatagramTo::Connection(ch, PathId::ZERO)); +1147 } +1148 } +1149 let data = datagram.data(); +1150 if data.len() < RESET_TOKEN_SIZE { +1151 return None; +1152 } +1153 // For stateless resets the PathId is meaningless since it closes the entire +1154 // connection regardless of path. So use PathId::ZERO. +1155 self.connection_reset_tokens +1156 .get(network_path.remote, &data[data.len() - RESET_TOKEN_SIZE..]) +1157 .cloned() +1158 .map(|ch| RouteDatagramTo::Connection(ch, PathId::ZERO)) +1159 } +1160} +1161 +1162#[derive(Debug)] +1163pub(crate) struct ConnectionMeta { +1164 init_cid: ConnectionId, +1165 /// Locally issues CIDs for each path +1166 local_cids: FxHashMap<PathId, PathLocalCids>, +1167 /// Remote/local addresses the connection began with +1168 /// +1169 /// Only needed to support connections with zero-length CIDs, which cannot migrate, so we don't +1170 /// bother keeping it up to date. +1171 network_path: FourTuple, +1172 side: Side, +1173 /// Reset tokens provided by the peer for CIDs we're currently sending to +1174 /// +1175 /// Since each reset token is for a CID, it is also for a fixed remote address which is +1176 /// also stored. This allows us to look up which reset tokens we might expect from a +1177 /// given remote address, see [`ResetTokenTable`]. +1178 /// +1179 /// Each path has its own active CID. We use the [`PathId`] as a unique index, allowing +1180 /// us to retire the reset token when a path is abandoned. +1181 // TODO(matheus23): Should be migrated to make reset tokens per 4-tuple instead of per remote addr +1182 reset_token: FxHashMap<PathId, (SocketAddr, ResetToken)>, +1183} +1184 +1185/// Local connection IDs for a single path +1186#[derive(Debug, Default)] +1187struct PathLocalCids { +1188 /// Number of connection IDs that have been issued in (PATH_)NEW_CONNECTION_ID frames +1189 /// +1190 /// Another way of saying this is that this is the next sequence number to be issued. +1191 issued: u64, +1192 /// Issues CIDs indexed by their sequence number. +1193 cids: FxHashMap<u64, ConnectionId>, +1194} +1195 +1196/// Internal identifier for a `Connection` currently associated with an endpoint +1197#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] +1198pub struct ConnectionHandle(pub usize); +1199 +1200impl From<ConnectionHandle> for usize { +1201 fn from(x: ConnectionHandle) -> Self { +1202 x.0 +1203 } +1204} +1205 +1206impl Index<ConnectionHandle> for Slab<ConnectionMeta> { +1207 type Output = ConnectionMeta; +1208 fn index(&self, ch: ConnectionHandle) -> &ConnectionMeta { +1209 &self[ch.0] +1210 } +1211} +1212 +1213impl IndexMut<ConnectionHandle> for Slab<ConnectionMeta> { +1214 fn index_mut(&mut self, ch: ConnectionHandle) -> &mut ConnectionMeta { +1215 &mut self[ch.0] +1216 } +1217} +1218 +1219/// Event resulting from processing a single datagram +1220pub enum DatagramEvent { +1221 /// The datagram is redirected to its `Connection` +1222 ConnectionEvent(ConnectionHandle, ConnectionEvent), +1223 /// The datagram may result in starting a new `Connection` +1224 NewConnection(Incoming), +1225 /// Response generated directly by the endpoint +1226 Response(Transmit), +1227} +1228 +1229/// An incoming connection for which the server has not yet begun its part of the handshake. +1230#[derive(derive_more::Debug)] +1231pub struct Incoming { +1232 #[debug(skip)] +1233 received_at: Instant, +1234 network_path: FourTuple, +1235 ecn: Option<EcnCodepoint>, +1236 #[debug(skip)] +1237 packet: InitialPacket, +1238 #[debug(skip)] +1239 rest: Option<BytesMut>, +1240 #[debug(skip)] +1241 crypto: Keys, +1242 token: IncomingToken, +1243 incoming_idx: usize, +1244 #[debug(skip)] +1245 improper_drop_warner: IncomingImproperDropWarner, +1246} +1247 +1248impl Incoming { +1249 /// The local IP address which was used when the peer established the connection +1250 pub fn local_ip(&self) -> Option<IpAddr> { +1251 self.network_path.local_ip +1252 } +1253 +1254 /// The peer's UDP address +1255 pub fn remote_address(&self) -> SocketAddr { +1256 self.network_path.remote +1257 } +1258 +1259 /// Whether the socket address that is initiating this connection has been validated 1260 /// -1261 /// If `self.remote_address_validated()` is false, `self.may_retry()` is guaranteed to be true. -1262 /// The inverse is not guaranteed. -1263 pub fn remote_address_validated(&self) -> bool { -1264 self.token.validated -1265 } -1266 -1267 /// Whether it is legal to respond with a retry packet -1268 /// -1269 /// If `self.remote_address_validated()` is false, `self.may_retry()` is guaranteed to be true. -1270 /// The inverse is not guaranteed. -1271 pub fn may_retry(&self) -> bool { -1272 self.token.retry_src_cid.is_none() -1273 } -1274 -1275 /// The original destination connection ID sent by the client -1276 pub fn orig_dst_cid(&self) -> ConnectionId { -1277 self.token.orig_dst_cid -1278 } -1279 -1280 /// Decrypt the Initial packet payload -1281 /// -1282 /// This clones and decrypts the packet payload (~1200 bytes). -1283 /// Can be used to extract information from the TLS ClientHello without completing the handshake. -1284 pub fn decrypt(&self) -> Option<DecryptedInitial> { -1285 let packet_number = self.packet.header.number.expand(0); -1286 let mut payload = self.packet.payload.clone(); -1287 self.crypto -1288 .packet -1289 .remote -1290 .decrypt( -1291 PathId::ZERO, -1292 packet_number, -1293 &self.packet.header_data, -1294 &mut payload, -1295 ) -1296 .ok()?; -1297 Some(DecryptedInitial(payload.freeze())) -1298 } -1299} -1300 -1301/// Decrypted payload of a QUIC Initial packet -1302/// -1303/// Obtained via [`Incoming::decrypt`]. Can be used to extract information from -1304/// the TLS ClientHello without completing the handshake. -1305pub struct DecryptedInitial(Bytes); -1306 -1307impl DecryptedInitial { -1308 /// Best-effort extraction of the ALPN protocols from the TLS ClientHello -1309 /// -1310 /// Parses the CRYPTO frames to extract the ALPN extension. This is intended -1311 /// for routing and filtering; it is not guaranteed to succeed if the -1312 /// ClientHello spans multiple packets. Returns `None` if parsing fails. -1313 pub fn alpns(&self) -> Option<IncomingAlpns> { -1314 let frames = frame::Iter::new(self.0.clone()).ok()?; -1315 let mut first = None; -1316 let mut rest = Vec::new(); -1317 for frame in frames { -1318 match frame { -1319 Ok(frame::Frame::Crypto(crypto)) => match first { -1320 None => first = Some(crypto), -1321 Some(_) => rest.push(crypto), -1322 }, -1323 Err(_) => return None, -1324 _ => {} -1325 } -1326 } -1327 let first = first?; -1328 -1329 // Fast path: single CRYPTO frame at offset 0 (no extra allocation) -1330 if rest.is_empty() && first.offset == 0 { -1331 let data = find_alpn_data(&first.data).ok()?; -1332 return Some(IncomingAlpns { data, pos: 0 }); -1333 } -1334 -1335 // Slow path: reassemble multiple CRYPTO frames -1336 rest.push(first); -1337 let source = assemble_crypto_frames(&mut rest)?; -1338 let data = find_alpn_data(&source).ok()?; -1339 Some(IncomingAlpns { data, pos: 0 }) -1340 } -1341} -1342 -1343/// TLS handshake type for ClientHello messages -1344/// <https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2> -1345const TLS_HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01; -1346/// TLS extension type for Application-Layer Protocol Negotiation -1347/// <https://www.rfc-editor.org/rfc/rfc7301#section-3.1> -1348const TLS_EXTENSION_TYPE_ALPN: u16 = 0x0010; -1349/// Size of the fixed-length fields in a ClientHello (client_version + random) -1350/// <https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2> -1351const TLS_CLIENT_HELLO_FIXED_LEN: usize = 2 + 32; -1352 -1353/// Iterator over ALPN protocol names from a TLS ClientHello -1354/// -1355/// Yields protocol names as [`Bytes`] slices. On the common fast path (single -1356/// CRYPTO frame), the only allocation is the payload clone for decryption. -1357pub struct IncomingAlpns { -1358 data: Bytes, -1359 pos: usize, -1360} -1361 -1362impl Iterator for IncomingAlpns { -1363 type Item = Result<Bytes, UnexpectedEnd>; +1261 /// This means that the sender of the initial packet has proved that they can receive traffic +1262 /// sent to `self.remote_address()`. +1263 /// +1264 /// If `self.remote_address_validated()` is false, `self.may_retry()` is guaranteed to be true. +1265 /// The inverse is not guaranteed. +1266 pub fn remote_address_validated(&self) -> bool { +1267 self.token.validated +1268 } +1269 +1270 /// Whether it is legal to respond with a retry packet +1271 /// +1272 /// If `self.remote_address_validated()` is false, `self.may_retry()` is guaranteed to be true. +1273 /// The inverse is not guaranteed. +1274 pub fn may_retry(&self) -> bool { +1275 self.token.retry_src_cid.is_none() +1276 } +1277 +1278 /// The original destination connection ID sent by the client +1279 pub fn orig_dst_cid(&self) -> ConnectionId { +1280 self.token.orig_dst_cid +1281 } +1282 +1283 /// Decrypt the Initial packet payload +1284 /// +1285 /// This clones and decrypts the packet payload (~1200 bytes). +1286 /// Can be used to extract information from the TLS ClientHello without completing the handshake. +1287 pub fn decrypt(&self) -> Option<DecryptedInitial> { +1288 let packet_number = self.packet.header.number.expand(0); +1289 let mut payload = self.packet.payload.clone(); +1290 self.crypto +1291 .packet +1292 .remote +1293 .decrypt( +1294 PathId::ZERO, +1295 packet_number, +1296 &self.packet.header_data, +1297 &mut payload, +1298 ) +1299 .ok()?; +1300 Some(DecryptedInitial(payload.freeze())) +1301 } +1302} +1303 +1304/// Decrypted payload of a QUIC Initial packet +1305/// +1306/// Obtained via [`Incoming::decrypt`]. Can be used to extract information from +1307/// the TLS ClientHello without completing the handshake. +1308pub struct DecryptedInitial(Bytes); +1309 +1310impl DecryptedInitial { +1311 /// Best-effort extraction of the ALPN protocols from the TLS ClientHello +1312 /// +1313 /// Parses the CRYPTO frames to extract the ALPN extension. This is intended +1314 /// for routing and filtering; it is not guaranteed to succeed if the +1315 /// ClientHello spans multiple packets. Returns `None` if parsing fails. +1316 pub fn alpns(&self) -> Option<IncomingAlpns> { +1317 let frames = frame::Iter::new(self.0.clone()).ok()?; +1318 let mut first = None; +1319 let mut rest = Vec::new(); +1320 for frame in frames { +1321 match frame { +1322 Ok(frame::Frame::Crypto(crypto)) => match first { +1323 None => first = Some(crypto), +1324 Some(_) => rest.push(crypto), +1325 }, +1326 Err(_) => return None, +1327 _ => {} +1328 } +1329 } +1330 let first = first?; +1331 +1332 // Fast path: single CRYPTO frame at offset 0 (no extra allocation) +1333 if rest.is_empty() && first.offset == 0 { +1334 let data = find_alpn_data(&first.data).ok()?; +1335 return Some(IncomingAlpns { data, pos: 0 }); +1336 } +1337 +1338 // Slow path: reassemble multiple CRYPTO frames +1339 rest.push(first); +1340 let source = assemble_crypto_frames(&mut rest)?; +1341 let data = find_alpn_data(&source).ok()?; +1342 Some(IncomingAlpns { data, pos: 0 }) +1343 } +1344} +1345 +1346/// TLS handshake type for ClientHello messages +1347/// <https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2> +1348const TLS_HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01; +1349/// TLS extension type for Application-Layer Protocol Negotiation +1350/// <https://www.rfc-editor.org/rfc/rfc7301#section-3.1> +1351const TLS_EXTENSION_TYPE_ALPN: u16 = 0x0010; +1352/// Size of the fixed-length fields in a ClientHello (client_version + random) +1353/// <https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2> +1354const TLS_CLIENT_HELLO_FIXED_LEN: usize = 2 + 32; +1355 +1356/// Iterator over ALPN protocol names from a TLS ClientHello +1357/// +1358/// Yields protocol names as [`Bytes`] slices. On the common fast path (single +1359/// CRYPTO frame), the only allocation is the payload clone for decryption. +1360pub struct IncomingAlpns { +1361 data: Bytes, +1362 pos: usize, +1363} 1364 -1365 fn next(&mut self) -> Option<Self::Item> { -1366 if self.pos >= self.data.len() { -1367 return None; -1368 } -1369 let len = self.data[self.pos] as usize; -1370 self.pos += 1; -1371 if self.pos + len > self.data.len() { -1372 return Some(Err(UnexpectedEnd)); -1373 } -1374 let proto = self.data.slice(self.pos..self.pos + len); -1375 self.pos += len; -1376 Some(Ok(proto)) -1377 } -1378} -1379 -1380/// Sort CRYPTO frames by offset and concatenate into a contiguous `Bytes` -1381/// -1382/// Returns `None` if there are gaps in the stream. -1383fn assemble_crypto_frames(frames: &mut [frame::Crypto]) -> Option<Bytes> { -1384 frames.sort_by_key(|f| f.offset); -1385 let capacity = frames.iter().map(|f| f.data.len()).sum(); -1386 let mut buf = Vec::with_capacity(capacity); -1387 for f in frames.iter() { -1388 let start = f.offset as usize; -1389 if start > buf.len() { -1390 return None; -1391 } -1392 let end = start + f.data.len(); -1393 if end > buf.len() { -1394 buf.extend_from_slice(&f.data[buf.len() - start..]); -1395 } -1396 } -1397 Some(Bytes::from(buf)) -1398} -1399 -1400/// Locate the raw ALPN protocol list data within a TLS ClientHello message -1401/// -1402/// Parses the ClientHello in `source` and returns a [`Bytes`] containing the -1403/// u8-length-prefixed protocol names (after the outer ProtocolNameList u16 -1404/// length prefix). The returned `Bytes` is a zero-copy slice of `source`. -1405fn find_alpn_data(source: &Bytes) -> Result<Bytes, UnexpectedEnd> { -1406 let mut r = &**source; -1407 -1408 if u8::decode(&mut r)? != TLS_HANDSHAKE_TYPE_CLIENT_HELLO { -1409 return Err(UnexpectedEnd); -1410 } -1411 -1412 // Handshake message length (u24), scopes the remainder -1413 let len = decode_u24(&mut r)?; -1414 let mut body = take(&mut r, len)?; -1415 -1416 // Client version + random -1417 skip(&mut body, TLS_CLIENT_HELLO_FIXED_LEN)?; +1365impl Iterator for IncomingAlpns { +1366 type Item = Result<Bytes, UnexpectedEnd>; +1367 +1368 fn next(&mut self) -> Option<Self::Item> { +1369 if self.pos >= self.data.len() { +1370 return None; +1371 } +1372 let len = self.data[self.pos] as usize; +1373 self.pos += 1; +1374 if self.pos + len > self.data.len() { +1375 return Some(Err(UnexpectedEnd)); +1376 } +1377 let proto = self.data.slice(self.pos..self.pos + len); +1378 self.pos += len; +1379 Some(Ok(proto)) +1380 } +1381} +1382 +1383/// Sort CRYPTO frames by offset and concatenate into a contiguous `Bytes` +1384/// +1385/// Returns `None` if there are gaps in the stream. +1386fn assemble_crypto_frames(frames: &mut [frame::Crypto]) -> Option<Bytes> { +1387 frames.sort_by_key(|f| f.offset); +1388 let capacity = frames.iter().map(|f| f.data.len()).sum(); +1389 let mut buf = Vec::with_capacity(capacity); +1390 for f in frames.iter() { +1391 let start = f.offset as usize; +1392 if start > buf.len() { +1393 return None; +1394 } +1395 let end = start + f.data.len(); +1396 if end > buf.len() { +1397 buf.extend_from_slice(&f.data[buf.len() - start..]); +1398 } +1399 } +1400 Some(Bytes::from(buf)) +1401} +1402 +1403/// Locate the raw ALPN protocol list data within a TLS ClientHello message +1404/// +1405/// Parses the ClientHello in `source` and returns a [`Bytes`] containing the +1406/// u8-length-prefixed protocol names (after the outer ProtocolNameList u16 +1407/// length prefix). The returned `Bytes` is a zero-copy slice of `source`. +1408fn find_alpn_data(source: &Bytes) -> Result<Bytes, UnexpectedEnd> { +1409 let mut r = &**source; +1410 +1411 if u8::decode(&mut r)? != TLS_HANDSHAKE_TYPE_CLIENT_HELLO { +1412 return Err(UnexpectedEnd); +1413 } +1414 +1415 // Handshake message length (u24), scopes the remainder +1416 let len = decode_u24(&mut r)?; +1417 let mut body = take(&mut r, len)?; 1418 -1419 // Session ID, cipher suites, compression methods -1420 skip_u8_prefixed(&mut body)?; -1421 skip_u16_prefixed(&mut body)?; -1422 skip_u8_prefixed(&mut body)?; -1423 -1424 // Extensions -1425 let mut exts = take_u16_prefixed(&mut body)?; -1426 while exts.has_remaining() { -1427 let ext_type = u16::decode(&mut exts)?; -1428 let ext_data = take_u16_prefixed(&mut exts)?; -1429 if ext_type == TLS_EXTENSION_TYPE_ALPN { -1430 let list = take_u16_prefixed(&mut &*ext_data)?; -1431 return Ok(source.slice_ref(list)); -1432 } -1433 } -1434 Err(UnexpectedEnd) -1435} -1436 -1437/// Decode a big-endian u24 as usize -1438fn decode_u24(r: &mut &[u8]) -> Result<usize, UnexpectedEnd> { -1439 let a = u8::decode(r)?; -1440 let b = u8::decode(r)?; -1441 let c = u8::decode(r)?; -1442 Ok(u32::from_be_bytes([0, a, b, c]) as usize) -1443} -1444 -1445/// Take `len` bytes from the front and return them as a sub-slice -1446fn take<'a>(r: &mut &'a [u8], len: usize) -> Result<&'a [u8], UnexpectedEnd> { -1447 if r.remaining() < len { -1448 return Err(UnexpectedEnd); -1449 } -1450 let data = &r[..len]; -1451 r.advance(len); -1452 Ok(data) -1453} -1454 -1455/// Read a u16 length prefix and return the sub-slice it covers -1456fn take_u16_prefixed<'a>(r: &mut &'a [u8]) -> Result<&'a [u8], UnexpectedEnd> { -1457 let len = u16::decode(r)? as usize; -1458 take(r, len) -1459} -1460 -1461/// Advance past `n` bytes -1462fn skip(r: &mut &[u8], len: usize) -> Result<(), UnexpectedEnd> { -1463 take(r, len)?; -1464 Ok(()) -1465} -1466 -1467/// Skip a u8-length-prefixed field -1468fn skip_u8_prefixed(r: &mut &[u8]) -> Result<(), UnexpectedEnd> { -1469 let len = u8::decode(r)? as usize; -1470 skip(r, len) -1471} -1472 -1473/// Skip a u16-length-prefixed field -1474fn skip_u16_prefixed(r: &mut &[u8]) -> Result<(), UnexpectedEnd> { -1475 let len = u16::decode(r)? as usize; -1476 skip(r, len) -1477} -1478 -1479struct IncomingImproperDropWarner; -1480 -1481impl IncomingImproperDropWarner { -1482 fn dismiss(self) { -1483 mem::forget(self); -1484 } -1485} -1486 -1487impl Drop for IncomingImproperDropWarner { -1488 fn drop(&mut self) { -1489 warn!( -1490 "noq_proto::Incoming dropped without passing to Endpoint::accept/refuse/retry/ignore \ -1491 (may cause memory leak and eventual inability to accept new connections)" -1492 ); -1493 } -1494} -1495 -1496/// Errors in the parameters being used to create a new connection -1497/// -1498/// These arise before any I/O has been performed. -1499#[derive(Debug, Error, Clone, PartialEq, Eq)] -1500pub enum ConnectError { -1501 /// The endpoint can no longer create new connections -1502 /// -1503 /// Indicates that a necessary component of the endpoint has been dropped or otherwise disabled. -1504 #[error("endpoint stopping")] -1505 EndpointStopping, -1506 /// The connection could not be created because not enough of the CID space is available -1507 /// -1508 /// Try using longer connection IDs -1509 #[error("CIDs exhausted")] -1510 CidsExhausted, -1511 /// The given server name was malformed -1512 #[error("invalid server name: {0}")] -1513 InvalidServerName(String), -1514 /// The remote [`SocketAddr`] supplied was malformed -1515 /// -1516 /// Examples include attempting to connect to port 0, or using an inappropriate address family. -1517 #[error("invalid remote address: {0}")] -1518 InvalidRemoteAddress(SocketAddr), -1519 /// No default client configuration was set up -1520 /// -1521 /// Use `Endpoint::connect_with` to specify a client configuration. -1522 #[error("no default client config")] -1523 NoDefaultClientConfig, -1524 /// The local endpoint does not support the QUIC version specified in the client configuration -1525 #[error("unsupported QUIC version")] -1526 UnsupportedVersion, -1527} -1528 -1529/// Error type for attempting to accept an [`Incoming`] -1530#[derive(Debug)] -1531pub struct AcceptError { -1532 /// Underlying error describing reason for failure -1533 pub cause: ConnectionError, -1534 /// Optional response to transmit back -1535 pub response: Option<Transmit>, -1536} -1537 -1538/// Error for attempting to retry an [`Incoming`] which already bears a token from a previous retry -1539#[derive(Debug, Error)] -1540#[error("retry() with validated Incoming")] -1541pub struct RetryError(Box<Incoming>); -1542 -1543impl RetryError { -1544 /// Get the [`Incoming`] -1545 pub fn into_incoming(self) -> Incoming { -1546 *self.0 -1547 } -1548} -1549 -1550/// Reset Tokens which are associated with peer socket addresses -1551/// -1552/// The standard `HashMap` is used since both `SocketAddr` and `ResetToken` are -1553/// peer generated and might be usable for hash collision attacks. -1554#[derive(Default, Debug)] -1555struct ResetTokenTable(HashMap<SocketAddr, HashMap<ResetToken, ConnectionHandle>>); -1556 -1557impl ResetTokenTable { -1558 fn insert(&mut self, remote: SocketAddr, token: ResetToken, ch: ConnectionHandle) -> bool { -1559 self.0 -1560 .entry(remote) -1561 .or_default() -1562 .insert(token, ch) -1563 .is_some() -1564 } -1565 -1566 fn remove(&mut self, remote: SocketAddr, token: ResetToken) { -1567 use std::collections::hash_map::Entry; -1568 match self.0.entry(remote) { -1569 Entry::Vacant(_) => {} -1570 Entry::Occupied(mut e) => { -1571 e.get_mut().remove(&token); -1572 if e.get().is_empty() { -1573 e.remove_entry(); -1574 } -1575 } -1576 } -1577 } -1578 -1579 fn get(&self, remote: SocketAddr, token: &[u8]) -> Option<&ConnectionHandle> { -1580 let token = ResetToken::from(<[u8; RESET_TOKEN_SIZE]>::try_from(token).ok()?); -1581 self.0.get(&remote)?.get(&token) -1582 } -1583} -1584 -1585#[cfg(test)] -1586mod tests { -1587 use super::*; -1588 -1589 #[test] -1590 fn assemble_contiguous() { -1591 let data = b"hello world"; -1592 let mut frames = vec![ -1593 frame::Crypto { -1594 offset: 0, -1595 data: Bytes::from_static(&data[..5]), -1596 }, -1597 frame::Crypto { -1598 offset: 5, -1599 data: Bytes::from_static(&data[5..]), -1600 }, -1601 ]; -1602 assert_eq!(&assemble_crypto_frames(&mut frames).unwrap()[..], &data[..]); -1603 } -1604 -1605 #[test] -1606 fn assemble_out_of_order() { -1607 let data = b"hello world"; -1608 let mut frames = vec![ -1609 frame::Crypto { -1610 offset: 5, -1611 data: Bytes::from_static(&data[5..]), -1612 }, -1613 frame::Crypto { -1614 offset: 0, -1615 data: Bytes::from_static(&data[..5]), -1616 }, -1617 ]; -1618 assert_eq!(&assemble_crypto_frames(&mut frames).unwrap()[..], &data[..]); -1619 } -1620 -1621 #[test] -1622 fn assemble_with_overlap() { -1623 let data = b"hello world"; -1624 let mut frames = vec![ -1625 frame::Crypto { -1626 offset: 0, -1627 data: Bytes::from_static(&data[..7]), -1628 }, -1629 frame::Crypto { -1630 offset: 5, -1631 data: Bytes::from_static(&data[5..]), -1632 }, -1633 ]; -1634 assert_eq!(&assemble_crypto_frames(&mut frames).unwrap()[..], &data[..]); -1635 } -1636 -1637 #[test] -1638 fn assemble_with_gap() { -1639 let mut frames = vec![frame::Crypto { -1640 offset: 10, -1641 data: Bytes::from_static(b"world"), -1642 }]; -1643 assert!(assemble_crypto_frames(&mut frames).is_none()); -1644 } -1645}

\ No newline at end of file +1419 // Client version + random +1420 skip(&mut body, TLS_CLIENT_HELLO_FIXED_LEN)?; +1421 +1422 // Session ID, cipher suites, compression methods +1423 skip_u8_prefixed(&mut body)?; +1424 skip_u16_prefixed(&mut body)?; +1425 skip_u8_prefixed(&mut body)?; +1426 +1427 // Extensions +1428 let mut exts = take_u16_prefixed(&mut body)?; +1429 while exts.has_remaining() { +1430 let ext_type = u16::decode(&mut exts)?; +1431 let ext_data = take_u16_prefixed(&mut exts)?; +1432 if ext_type == TLS_EXTENSION_TYPE_ALPN { +1433 let list = take_u16_prefixed(&mut &*ext_data)?; +1434 return Ok(source.slice_ref(list)); +1435 } +1436 } +1437 Err(UnexpectedEnd) +1438} +1439 +1440/// Decode a big-endian u24 as usize +1441fn decode_u24(r: &mut &[u8]) -> Result<usize, UnexpectedEnd> { +1442 let a = u8::decode(r)?; +1443 let b = u8::decode(r)?; +1444 let c = u8::decode(r)?; +1445 Ok(u32::from_be_bytes([0, a, b, c]) as usize) +1446} +1447 +1448/// Take `len` bytes from the front and return them as a sub-slice +1449fn take<'a>(r: &mut &'a [u8], len: usize) -> Result<&'a [u8], UnexpectedEnd> { +1450 if r.remaining() < len { +1451 return Err(UnexpectedEnd); +1452 } +1453 let data = &r[..len]; +1454 r.advance(len); +1455 Ok(data) +1456} +1457 +1458/// Read a u16 length prefix and return the sub-slice it covers +1459fn take_u16_prefixed<'a>(r: &mut &'a [u8]) -> Result<&'a [u8], UnexpectedEnd> { +1460 let len = u16::decode(r)? as usize; +1461 take(r, len) +1462} +1463 +1464/// Advance past `n` bytes +1465fn skip(r: &mut &[u8], len: usize) -> Result<(), UnexpectedEnd> { +1466 take(r, len)?; +1467 Ok(()) +1468} +1469 +1470/// Skip a u8-length-prefixed field +1471fn skip_u8_prefixed(r: &mut &[u8]) -> Result<(), UnexpectedEnd> { +1472 let len = u8::decode(r)? as usize; +1473 skip(r, len) +1474} +1475 +1476/// Skip a u16-length-prefixed field +1477fn skip_u16_prefixed(r: &mut &[u8]) -> Result<(), UnexpectedEnd> { +1478 let len = u16::decode(r)? as usize; +1479 skip(r, len) +1480} +1481 +1482struct IncomingImproperDropWarner; +1483 +1484impl IncomingImproperDropWarner { +1485 fn dismiss(self) { +1486 mem::forget(self); +1487 } +1488} +1489 +1490impl Drop for IncomingImproperDropWarner { +1491 fn drop(&mut self) { +1492 warn!( +1493 "noq_proto::Incoming dropped without passing to Endpoint::accept/refuse/retry/ignore \ +1494 (may cause memory leak and eventual inability to accept new connections)" +1495 ); +1496 } +1497} +1498 +1499/// Errors in the parameters being used to create a new connection +1500/// +1501/// These arise before any I/O has been performed. +1502#[derive(Debug, Error, Clone, PartialEq, Eq)] +1503pub enum ConnectError { +1504 /// The endpoint can no longer create new connections +1505 /// +1506 /// Indicates that a necessary component of the endpoint has been dropped or otherwise disabled. +1507 #[error("endpoint stopping")] +1508 EndpointStopping, +1509 /// The connection could not be created because not enough of the CID space is available +1510 /// +1511 /// Try using longer connection IDs +1512 #[error("CIDs exhausted")] +1513 CidsExhausted, +1514 /// The given server name was malformed +1515 #[error("invalid server name: {0}")] +1516 InvalidServerName(String), +1517 /// The remote [`SocketAddr`] supplied was malformed +1518 /// +1519 /// Examples include attempting to connect to port 0, or using an inappropriate address family. +1520 #[error("invalid remote address: {0}")] +1521 InvalidRemoteAddress(SocketAddr), +1522 /// No default client configuration was set up +1523 /// +1524 /// Use `Endpoint::connect_with` to specify a client configuration. +1525 #[error("no default client config")] +1526 NoDefaultClientConfig, +1527 /// The local endpoint does not support the QUIC version specified in the client configuration +1528 #[error("unsupported QUIC version")] +1529 UnsupportedVersion, +1530} +1531 +1532/// Error type for attempting to accept an [`Incoming`] +1533#[derive(Debug)] +1534pub struct AcceptError { +1535 /// Underlying error describing reason for failure +1536 pub cause: ConnectionError, +1537 /// Optional response to transmit back +1538 pub response: Option<Transmit>, +1539} +1540 +1541/// Error for attempting to retry an [`Incoming`] which already bears a token from a previous retry +1542#[derive(Debug, Error)] +1543#[error("retry() with validated Incoming")] +1544pub struct RetryError(Box<Incoming>); +1545 +1546impl RetryError { +1547 /// Get the [`Incoming`] +1548 pub fn into_incoming(self) -> Incoming { +1549 *self.0 +1550 } +1551} +1552 +1553/// Reset Tokens which are associated with peer socket addresses +1554/// +1555/// The standard `HashMap` is used since both `SocketAddr` and `ResetToken` are +1556/// peer generated and might be usable for hash collision attacks. +1557#[derive(Default, Debug)] +1558struct ResetTokenTable(HashMap<SocketAddr, HashMap<ResetToken, ConnectionHandle>>); +1559 +1560impl ResetTokenTable { +1561 fn insert(&mut self, remote: SocketAddr, token: ResetToken, ch: ConnectionHandle) -> bool { +1562 self.0 +1563 .entry(remote) +1564 .or_default() +1565 .insert(token, ch) +1566 .is_some() +1567 } +1568 +1569 fn remove(&mut self, remote: SocketAddr, token: ResetToken) { +1570 use std::collections::hash_map::Entry; +1571 match self.0.entry(remote) { +1572 Entry::Vacant(_) => {} +1573 Entry::Occupied(mut e) => { +1574 e.get_mut().remove(&token); +1575 if e.get().is_empty() { +1576 e.remove_entry(); +1577 } +1578 } +1579 } +1580 } +1581 +1582 fn get(&self, remote: SocketAddr, token: &[u8]) -> Option<&ConnectionHandle> { +1583 let token = ResetToken::from(<[u8; RESET_TOKEN_SIZE]>::try_from(token).ok()?); +1584 self.0.get(&remote)?.get(&token) +1585 } +1586} +1587 +1588#[cfg(test)] +1589mod tests { +1590 use super::*; +1591 +1592 #[test] +1593 fn assemble_contiguous() { +1594 let data = b"hello world"; +1595 let mut frames = vec![ +1596 frame::Crypto { +1597 offset: 0, +1598 data: Bytes::from_static(&data[..5]), +1599 }, +1600 frame::Crypto { +1601 offset: 5, +1602 data: Bytes::from_static(&data[5..]), +1603 }, +1604 ]; +1605 assert_eq!(&assemble_crypto_frames(&mut frames).unwrap()[..], &data[..]); +1606 } +1607 +1608 #[test] +1609 fn assemble_out_of_order() { +1610 let data = b"hello world"; +1611 let mut frames = vec![ +1612 frame::Crypto { +1613 offset: 5, +1614 data: Bytes::from_static(&data[5..]), +1615 }, +1616 frame::Crypto { +1617 offset: 0, +1618 data: Bytes::from_static(&data[..5]), +1619 }, +1620 ]; +1621 assert_eq!(&assemble_crypto_frames(&mut frames).unwrap()[..], &data[..]); +1622 } +1623 +1624 #[test] +1625 fn assemble_with_overlap() { +1626 let data = b"hello world"; +1627 let mut frames = vec![ +1628 frame::Crypto { +1629 offset: 0, +1630 data: Bytes::from_static(&data[..7]), +1631 }, +1632 frame::Crypto { +1633 offset: 5, +1634 data: Bytes::from_static(&data[5..]), +1635 }, +1636 ]; +1637 assert_eq!(&assemble_crypto_frames(&mut frames).unwrap()[..], &data[..]); +1638 } +1639 +1640 #[test] +1641 fn assemble_with_gap() { +1642 let mut frames = vec![frame::Crypto { +1643 offset: 10, +1644 data: Bytes::from_static(b"world"), +1645 }]; +1646 assert!(assemble_crypto_frames(&mut frames).is_none()); +1647 } +1648}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/frame.rs.html b/pr/451/docs/src/noq_proto/frame.rs.html index 3a12df1b4..f622d0741 100644 --- a/pr/451/docs/src/noq_proto/frame.rs.html +++ b/pr/451/docs/src/noq_proto/frame.rs.html @@ -1102,7 +1102,7 @@ 1102 buf.write(*path_id); 1103 buf.write_var(largest); 1104 buf.write_var(*delay); -1105 buf.write_var(ranges.len() as u64 - 1); +1105 buf.write_var(ranges.range_count() as u64 - 1); 1106 buf.write_var(first_size - 1); 1107 let mut prev = first.start; 1108 for block in rest { @@ -1201,7 +1201,7 @@ 1201 buf.write(kind); 1202 buf.write_var(largest); 1203 buf.write_var(*delay); -1204 buf.write_var(ranges.len() as u64 - 1); +1204 buf.write_var(ranges.range_count() as u64 - 1); 1205 buf.write_var(first_size - 1); 1206 let mut prev = first.start; 1207 for block in rest { @@ -2181,7 +2181,7 @@ 2181 2182#[derive(Debug, PartialEq, Eq, derive_more::Display)] 2183#[cfg_attr(test, derive(Arbitrary))] -2184#[display("PATH_ABANDON path_id: {path_id}")] +2184#[display("PATH_ABANDON path_id: {path_id}, error: {error_code}")] 2185pub(crate) struct PathAbandon { 2186 pub(crate) path_id: PathId, 2187 pub(crate) error_code: TransportErrorCode, diff --git a/pr/451/docs/src/noq_proto/lib.rs.html b/pr/451/docs/src/noq_proto/lib.rs.html index 5d4afe9d1..e3227141a 100644 --- a/pr/451/docs/src/noq_proto/lib.rs.html +++ b/pr/451/docs/src/noq_proto/lib.rs.html @@ -13,463 +13,454 @@ 13//! managing a single connection and all the related state (such as streams). 14 15#![cfg_attr(not(fuzzing), warn(missing_docs))] -16#![cfg_attr(test, allow(dead_code))] -17// Fixes welcome: -18#![allow(clippy::too_many_arguments)] -19#![warn(unreachable_pub)] -20#![warn(clippy::use_self)] -21 -22use std::{ -23 fmt, -24 net::{IpAddr, SocketAddr}, -25 ops, -26}; -27 -28mod cid_queue; -29pub mod coding; -30mod constant_time; -31mod range_set; -32#[cfg(all(test, feature = "rustls", any(feature = "aws-lc-rs", feature = "ring")))] -33mod tests; -34pub mod transport_parameters; -35mod varint; -36 -37pub use varint::{VarInt, VarIntBoundsExceeded}; -38 -39#[cfg(feature = "bloom")] -40mod bloom_token_log; -41#[cfg(feature = "bloom")] -42pub use bloom_token_log::BloomTokenLog; -43 -44pub(crate) mod connection; -45pub use crate::connection::{ -46 Chunk, Chunks, ClosePathError, ClosedPath, ClosedStream, Connection, ConnectionError, -47 ConnectionStats, Datagrams, Event, FinishError, FrameStats, MultipathNotNegotiated, -48 NetworkChangeHint, PathAbandonReason, PathError, PathEvent, PathId, PathStats, PathStatus, -49 ReadError, ReadableError, RecvStream, RttEstimator, SendDatagramError, SendStream, -50 SetPathStatusError, ShouldTransmit, StreamEvent, Streams, UdpStats, WriteError, -51}; -52#[cfg(test)] -53use test_strategy::Arbitrary; -54 -55#[cfg(feature = "rustls")] -56pub use rustls; -57 -58mod config; -59pub use config::{ -60 AckFrequencyConfig, ClientConfig, ConfigError, EndpointConfig, IdleTimeout, MtuDiscoveryConfig, -61 ServerConfig, StdSystemTime, TimeSource, TransportConfig, ValidationTokenConfig, -62}; -63#[cfg(feature = "qlog")] -64pub use config::{QlogConfig, QlogFactory, QlogFileFactory}; -65 -66pub mod crypto; -67 -68mod frame; -69pub use crate::frame::{ -70 ApplicationClose, ConnectionClose, Datagram, DatagramInfo, FrameType, InvalidFrameId, -71 MaybeFrame, StreamInfo, -72}; -73use crate::{ -74 coding::{Decodable, Encodable}, -75 frame::Frame, -76}; -77 -78mod endpoint; -79pub use crate::endpoint::{ -80 AcceptError, ConnectError, ConnectionHandle, DatagramEvent, DecryptedInitial, Endpoint, -81 Incoming, IncomingAlpns, RetryError, -82}; -83 -84mod packet; -85pub use packet::{ -86 ConnectionIdParser, FixedLengthConnectionIdParser, LongType, PacketDecodeError, PartialDecode, -87 ProtectedHeader, ProtectedInitialHeader, -88}; -89 -90mod shared; -91pub use crate::shared::{ConnectionEvent, ConnectionId, EcnCodepoint, EndpointEvent}; -92 -93mod transport_error; -94pub use crate::transport_error::{Code as TransportErrorCode, Error as TransportError}; -95 -96pub mod congestion; -97 -98mod cid_generator; -99pub use crate::cid_generator::{ -100 ConnectionIdGenerator, HashedConnectionIdGenerator, InvalidCid, RandomConnectionIdGenerator, -101}; -102 -103mod token; -104use token::ResetToken; -105pub use token::{NoneTokenLog, NoneTokenStore, TokenLog, TokenReuseError, TokenStore}; -106 -107mod address_discovery; -108 -109mod token_memory_cache; -110pub use token_memory_cache::TokenMemoryCache; -111 -112pub mod n0_nat_traversal; -113 -114// Deal with time -115#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] -116pub(crate) use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -117#[cfg(all(target_family = "wasm", target_os = "unknown"))] -118pub(crate) use web_time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -119 -120#[cfg(feature = "bench")] -121pub mod bench_exports { -122 //! Exports for benchmarks -123 pub use crate::connection::send_buffer::send_buffer_benches; -124} -125 -126#[cfg(fuzzing)] -127pub mod fuzzing { -128 pub use crate::connection::{Retransmits, State as ConnectionState, StreamsState}; -129 pub use crate::frame::ResetStream; -130 pub use crate::packet::PartialDecode; -131 pub use crate::transport_parameters::TransportParameters; -132 pub use bytes::{BufMut, BytesMut}; -133 -134 #[cfg(feature = "arbitrary")] -135 use arbitrary::{Arbitrary, Result, Unstructured}; -136 -137 #[cfg(feature = "arbitrary")] -138 impl<'arbitrary> Arbitrary<'arbitrary> for TransportParameters { -139 fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self> { -140 Ok(Self { -141 initial_max_streams_bidi: u.arbitrary()?, -142 initial_max_streams_uni: u.arbitrary()?, -143 ack_delay_exponent: u.arbitrary()?, -144 max_udp_payload_size: u.arbitrary()?, -145 ..Self::default() -146 }) -147 } -148 } -149 -150 #[derive(Debug)] -151 pub struct PacketParams { -152 pub local_cid_len: usize, -153 pub buf: BytesMut, -154 pub grease_quic_bit: bool, -155 } -156 -157 #[cfg(feature = "arbitrary")] -158 impl<'arbitrary> Arbitrary<'arbitrary> for PacketParams { -159 fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self> { -160 let local_cid_len: usize = u.int_in_range(0..=crate::MAX_CID_SIZE)?; -161 let bytes: Vec<u8> = Vec::arbitrary(u)?; -162 let mut buf = BytesMut::new(); -163 buf.put_slice(&bytes[..]); -164 Ok(Self { -165 local_cid_len, -166 buf, -167 grease_quic_bit: bool::arbitrary(u)?, -168 }) -169 } -170 } -171} -172 -173/// The QUIC protocol version implemented. -174pub const DEFAULT_SUPPORTED_VERSIONS: &[u32] = &[ -175 0x00000001, -176 0xff00_001d, -177 0xff00_001e, -178 0xff00_001f, -179 0xff00_0020, -180 0xff00_0021, -181 0xff00_0022, -182]; -183 -184/// Whether an endpoint was the initiator of a connection -185#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -186#[cfg_attr(test, derive(Arbitrary))] -187#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -188pub enum Side { -189 /// The initiator of a connection -190 Client = 0, -191 /// The acceptor of a connection -192 Server = 1, -193} -194 -195impl Side { -196 #[inline] -197 /// Shorthand for `self == Side::Client` -198 pub fn is_client(self) -> bool { -199 self == Self::Client -200 } -201 -202 #[inline] -203 /// Shorthand for `self == Side::Server` -204 pub fn is_server(self) -> bool { -205 self == Self::Server -206 } -207} -208 -209impl ops::Not for Side { -210 type Output = Self; -211 fn not(self) -> Self { -212 match self { -213 Self::Client => Self::Server, -214 Self::Server => Self::Client, -215 } -216 } -217} -218 -219/// Whether a stream communicates data in both directions or only from the initiator -220#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -221#[cfg_attr(test, derive(Arbitrary))] -222#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -223pub enum Dir { -224 /// Data flows in both directions -225 Bi = 0, -226 /// Data flows only from the stream's initiator -227 Uni = 1, -228} -229 -230impl Dir { -231 fn iter() -> impl Iterator<Item = Self> { -232 [Self::Bi, Self::Uni].iter().cloned() -233 } -234} -235 -236impl fmt::Display for Dir { -237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -238 use Dir::*; -239 f.pad(match *self { -240 Bi => "bidirectional", -241 Uni => "unidirectional", -242 }) -243 } -244} -245 -246/// Identifier for a stream within a particular connection -247#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -248#[cfg_attr(test, derive(Arbitrary))] -249pub struct StreamId(#[cfg_attr(test, strategy(crate::varint::varint_u64()))] u64); -250 -251impl fmt::Display for StreamId { -252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -253 let initiator = match self.initiator() { -254 Side::Client => "client", -255 Side::Server => "server", -256 }; -257 let dir = match self.dir() { -258 Dir::Uni => "uni", -259 Dir::Bi => "bi", -260 }; -261 write!( -262 f, -263 "{} {}directional stream {}", -264 initiator, -265 dir, -266 self.index() -267 ) -268 } -269} -270 -271impl StreamId { -272 /// Create a new StreamId -273 pub fn new(initiator: Side, dir: Dir, index: u64) -> Self { -274 Self((index << 2) | ((dir as u64) << 1) | initiator as u64) -275 } -276 /// Which side of a connection initiated the stream -277 pub fn initiator(self) -> Side { -278 if self.0 & 0x1 == 0 { -279 Side::Client -280 } else { -281 Side::Server -282 } -283 } -284 /// Which directions data flows in -285 pub fn dir(self) -> Dir { -286 if self.0 & 0x2 == 0 { Dir::Bi } else { Dir::Uni } -287 } -288 /// Distinguishes streams of the same initiator and directionality -289 pub fn index(self) -> u64 { -290 self.0 >> 2 -291 } -292} -293 -294impl From<StreamId> for VarInt { -295 fn from(x: StreamId) -> Self { -296 unsafe { Self::from_u64_unchecked(x.0) } -297 } -298} -299 -300impl From<VarInt> for StreamId { -301 fn from(v: VarInt) -> Self { -302 Self(v.0) -303 } -304} -305 -306impl From<StreamId> for u64 { -307 fn from(x: StreamId) -> Self { -308 x.0 -309 } -310} -311 -312impl Decodable for StreamId { -313 fn decode<B: bytes::Buf>(buf: &mut B) -> coding::Result<Self> { -314 VarInt::decode(buf).map(|x| Self(x.into_inner())) -315 } -316} -317 -318impl Encodable for StreamId { -319 fn encode<B: bytes::BufMut>(&self, buf: &mut B) { -320 VarInt::from_u64(self.0).unwrap().encode(buf); -321 } -322} -323 -324#[cfg(feature = "arbitrary")] -325impl<'arbitrary> arbitrary::Arbitrary<'arbitrary> for StreamId { -326 fn arbitrary(u: &mut arbitrary::Unstructured<'arbitrary>) -> arbitrary::Result<Self> { -327 Ok(VarInt::arbitrary(u)?.into()) -328 } -329} -330 -331/// An outgoing packet -332#[derive(Debug)] -333#[must_use] -334pub struct Transmit { -335 /// The socket this datagram should be sent to -336 pub destination: SocketAddr, -337 /// Explicit congestion notification bits to set on the packet -338 pub ecn: Option<EcnCodepoint>, -339 /// Amount of data written to the caller-supplied buffer -340 pub size: usize, -341 /// The segment size if this transmission contains multiple datagrams. -342 /// This is `None` if the transmit only contains a single datagram -343 pub segment_size: Option<usize>, -344 /// Optional source IP address for the datagram -345 pub src_ip: Option<IpAddr>, -346} -347 -348// -349// Useful internal constants -350// -351 -352/// The maximum number of CIDs we bother to issue per path -353const LOCAL_CID_COUNT: u64 = 12; -354const RESET_TOKEN_SIZE: usize = 16; -355const MAX_CID_SIZE: usize = 20; -356const MIN_INITIAL_SIZE: u16 = 1200; -357/// <https://www.rfc-editor.org/rfc/rfc9000.html#name-datagram-size> -358const INITIAL_MTU: u16 = 1200; -359const MAX_UDP_PAYLOAD: u16 = 65527; -360const TIMER_GRANULARITY: Duration = Duration::from_millis(1); -361/// Maximum number of streams that can be uniquely identified by a stream ID -362const MAX_STREAM_COUNT: u64 = 1 << 60; -363 -364/// Identifies a network path by the combination of remote and local addresses -365/// -366/// Including the local ensures good behavior when the host has multiple IP addresses on the same -367/// subnet and zero-length connection IDs are in use or when multipath is enabled and multiple -368/// paths exist with the same remote, but different local IP interfaces. -369#[derive(Hash, Eq, PartialEq, Copy, Clone)] -370pub struct FourTuple { -371 /// The remote side of this tuple. -372 remote: SocketAddr, -373 /// The local side of this tuple. -374 /// -375 /// The socket is irrelevant for our intents and purposes: -376 /// When we send, we can only specify the `src_ip`, not the source port. -377 /// So even if we track the port, we won't be able to make use of it. -378 local_ip: Option<IpAddr>, -379} -380 -381impl FourTuple { -382 /// Creates a new [`FourTuple`]. -383 pub fn new(mut remote: SocketAddr, local_ip: Option<IpAddr>) -> Self { -384 if let SocketAddr::V6(socket_addr) = &mut remote { -385 // RFC3493 §3.3 -386 // > (…) applications should set this field to zero when constructing a sockaddr_in6, -387 // > and ignore this field in a sockaddr_in6 structure constructed by the system. -388 // -389 // This is cleared so that comparisons of remotes are guaranteed to be meaningful: two -390 // socket addresses with the same contents should not differ if only the flow label -391 // differs -392 socket_addr.set_flowinfo(0); -393 -394 // NOTE: not all multicast addresses require a scope. Use `Ipv6Addr::multicast_scope` -395 // when stabilized (<https://github.com/rust-lang/rust/issues/27709>) -396 let requires_scope_id = -397 socket_addr.ip().is_unicast_link_local() || socket_addr.ip().is_multicast(); -398 if !requires_scope_id { -399 // Keep the scope id only when it might be relevant. This ensure network paths can -400 // be compared meaningfully while keeping it when it's important (mainly link local -401 // addresses) -402 socket_addr.set_scope_id(0); -403 } -404 } -405 -406 Self { remote, local_ip } -407 } -408 -409 /// Creates a new [`FourTuple`] without a known local address. -410 pub fn from_remote(remote: SocketAddr) -> Self { -411 Self::new(remote, None) -412 } -413 -414 /// Returns the remote address of the network path. -415 pub fn remote(&self) -> SocketAddr { -416 self.remote -417 } -418 -419 /// Returns the local address of the network path. -420 pub fn local_ip(&self) -> Option<IpAddr> { -421 self.local_ip -422 } -423 -424 /// Returns whether we think the other address probably represents the same path -425 /// as ours. -426 /// -427 /// If we have a local IP set, then we're exact and only match if the 4-tuples are -428 /// exactly equal. -429 /// If we don't have a local IP set, then we only check the remote addresses for equality. -430 /// -431 /// Note that because of this, the following calls might differ: -432 /// - `a.is_probably_same_path(b)` -433 /// - `b.is_probably_same_path(a)` -434 pub fn is_probably_same_path(&self, other: &Self) -> bool { -435 self.remote == other.remote && (self.local_ip.is_none() || self.local_ip == other.local_ip) -436 } -437 -438 /// Updates this tuple's local address iff -439 /// - it was unset before, -440 /// - the other tuple has the same remote, and -441 /// - the other tuple has a local address set. -442 /// -443 /// Returns whether this and the other remote are now fully equal. -444 pub fn update_local_if_same_remote(&mut self, other: &Self) -> bool { -445 if self.remote != other.remote { -446 return false; -447 } -448 if self.local_ip.is_some() && self.local_ip != other.local_ip { -449 return false; -450 } -451 self.local_ip = other.local_ip; -452 true -453 } -454} -455 -456impl fmt::Display for FourTuple { -457 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -458 f.write_str("(local: ")?; -459 if let Some(local_ip) = &self.local_ip { -460 local_ip.fmt(f)?; -461 f.write_str(", ")?; -462 } else { -463 f.write_str("<unspecified>, ")?; -464 } -465 f.write_str("remote: ")?; -466 self.remote.fmt(f)?; -467 f.write_str(")") -468 } -469} -470 -471impl fmt::Debug for FourTuple { -472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -473 fmt::Display::fmt(&self, f) -474 } -475}
\ No newline at end of file +16// Fixes welcome: +17#![allow(clippy::too_many_arguments)] +18#![warn(unreachable_pub)] +19#![warn(clippy::use_self)] +20 +21use std::{ +22 fmt, +23 net::{IpAddr, SocketAddr}, +24 ops, +25}; +26 +27mod cid_queue; +28pub mod coding; +29mod constant_time; +30mod range_set; +31#[cfg(all(test, feature = "rustls", any(feature = "aws-lc-rs", feature = "ring")))] +32mod tests; +33pub mod transport_parameters; +34mod varint; +35 +36pub use varint::{VarInt, VarIntBoundsExceeded}; +37 +38#[cfg(feature = "bloom")] +39mod bloom_token_log; +40#[cfg(feature = "bloom")] +41pub use bloom_token_log::BloomTokenLog; +42 +43pub(crate) mod connection; +44pub use crate::connection::{ +45 Chunk, Chunks, ClosePathError, ClosedPath, ClosedStream, Connection, ConnectionError, +46 ConnectionStats, Datagrams, Event, FinishError, FrameStats, MultipathNotNegotiated, +47 NetworkChangeHint, PathAbandonReason, PathError, PathEvent, PathId, PathStats, PathStatus, +48 ReadError, ReadableError, RecvStream, RttEstimator, SendDatagramError, SendStream, +49 SetPathStatusError, ShouldTransmit, StreamEvent, Streams, UdpStats, WriteError, +50}; +51#[cfg(test)] +52use test_strategy::Arbitrary; +53 +54#[cfg(feature = "rustls")] +55pub use rustls; +56 +57mod config; +58pub use config::{ +59 AckFrequencyConfig, ClientConfig, ConfigError, EndpointConfig, IdleTimeout, MtuDiscoveryConfig, +60 ServerConfig, StdSystemTime, TimeSource, TransportConfig, ValidationTokenConfig, +61}; +62#[cfg(feature = "qlog")] +63pub use config::{QlogConfig, QlogFactory, QlogFileFactory}; +64 +65pub mod crypto; +66 +67mod frame; +68pub use crate::frame::{ +69 ApplicationClose, ConnectionClose, Datagram, DatagramInfo, FrameType, InvalidFrameId, +70 MaybeFrame, StreamInfo, +71}; +72use crate::{ +73 coding::{Decodable, Encodable}, +74 frame::Frame, +75}; +76 +77mod endpoint; +78pub use crate::endpoint::{ +79 AcceptError, ConnectError, ConnectionHandle, DatagramEvent, DecryptedInitial, Endpoint, +80 Incoming, IncomingAlpns, RetryError, +81}; +82 +83mod packet; +84pub use packet::{ +85 ConnectionIdParser, FixedLengthConnectionIdParser, LongType, PacketDecodeError, PartialDecode, +86 ProtectedHeader, ProtectedInitialHeader, +87}; +88 +89mod shared; +90pub use crate::shared::{ConnectionEvent, ConnectionId, EcnCodepoint, EndpointEvent}; +91 +92mod transport_error; +93pub use crate::transport_error::{Code as TransportErrorCode, Error as TransportError}; +94 +95pub mod congestion; +96 +97mod cid_generator; +98pub use crate::cid_generator::{ +99 ConnectionIdGenerator, HashedConnectionIdGenerator, InvalidCid, RandomConnectionIdGenerator, +100}; +101 +102mod token; +103use token::ResetToken; +104pub use token::{NoneTokenLog, NoneTokenStore, TokenLog, TokenReuseError, TokenStore}; +105 +106mod address_discovery; +107 +108mod token_memory_cache; +109pub use token_memory_cache::TokenMemoryCache; +110 +111pub mod n0_nat_traversal; +112 +113// Deal with time +114#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] +115pub(crate) use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +116#[cfg(all(target_family = "wasm", target_os = "unknown"))] +117pub(crate) use web_time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +118 +119#[cfg(feature = "bench")] +120pub mod bench_exports { +121 //! Exports for benchmarks +122 pub use crate::connection::send_buffer::send_buffer_benches; +123} +124 +125#[cfg(fuzzing)] +126pub mod fuzzing { +127 pub use crate::connection::{Retransmits, State as ConnectionState, StreamsState}; +128 pub use crate::frame::ResetStream; +129 pub use crate::packet::PartialDecode; +130 pub use crate::transport_parameters::TransportParameters; +131 pub use bytes::{BufMut, BytesMut}; +132 +133 #[cfg(feature = "arbitrary")] +134 use arbitrary::{Arbitrary, Result, Unstructured}; +135 +136 #[cfg(feature = "arbitrary")] +137 impl<'arbitrary> Arbitrary<'arbitrary> for TransportParameters { +138 fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self> { +139 Ok(Self { +140 initial_max_streams_bidi: u.arbitrary()?, +141 initial_max_streams_uni: u.arbitrary()?, +142 ack_delay_exponent: u.arbitrary()?, +143 max_udp_payload_size: u.arbitrary()?, +144 ..Self::default() +145 }) +146 } +147 } +148 +149 #[derive(Debug)] +150 pub struct PacketParams { +151 pub local_cid_len: usize, +152 pub buf: BytesMut, +153 pub grease_quic_bit: bool, +154 } +155 +156 #[cfg(feature = "arbitrary")] +157 impl<'arbitrary> Arbitrary<'arbitrary> for PacketParams { +158 fn arbitrary(u: &mut Unstructured<'arbitrary>) -> Result<Self> { +159 let local_cid_len: usize = u.int_in_range(0..=crate::MAX_CID_SIZE)?; +160 let bytes: Vec<u8> = Vec::arbitrary(u)?; +161 let mut buf = BytesMut::new(); +162 buf.put_slice(&bytes[..]); +163 Ok(Self { +164 local_cid_len, +165 buf, +166 grease_quic_bit: bool::arbitrary(u)?, +167 }) +168 } +169 } +170} +171 +172/// The QUIC protocol version implemented. +173pub const DEFAULT_SUPPORTED_VERSIONS: &[u32] = &[ +174 0x00000001, +175 0xff00_001d, +176 0xff00_001e, +177 0xff00_001f, +178 0xff00_0020, +179 0xff00_0021, +180 0xff00_0022, +181]; +182 +183/// Whether an endpoint was the initiator of a connection +184#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +185#[cfg_attr(test, derive(Arbitrary))] +186#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +187pub enum Side { +188 /// The initiator of a connection +189 Client = 0, +190 /// The acceptor of a connection +191 Server = 1, +192} +193 +194impl Side { +195 #[inline] +196 /// Shorthand for `self == Side::Client` +197 pub fn is_client(self) -> bool { +198 self == Self::Client +199 } +200 +201 #[inline] +202 /// Shorthand for `self == Side::Server` +203 pub fn is_server(self) -> bool { +204 self == Self::Server +205 } +206} +207 +208impl ops::Not for Side { +209 type Output = Self; +210 fn not(self) -> Self { +211 match self { +212 Self::Client => Self::Server, +213 Self::Server => Self::Client, +214 } +215 } +216} +217 +218/// Whether a stream communicates data in both directions or only from the initiator +219#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +220#[cfg_attr(test, derive(Arbitrary))] +221#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +222pub enum Dir { +223 /// Data flows in both directions +224 Bi = 0, +225 /// Data flows only from the stream's initiator +226 Uni = 1, +227} +228 +229impl Dir { +230 fn iter() -> impl Iterator<Item = Self> { +231 [Self::Bi, Self::Uni].iter().cloned() +232 } +233} +234 +235impl fmt::Display for Dir { +236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +237 use Dir::*; +238 f.pad(match *self { +239 Bi => "bidirectional", +240 Uni => "unidirectional", +241 }) +242 } +243} +244 +245/// Identifier for a stream within a particular connection +246#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +247#[cfg_attr(test, derive(Arbitrary))] +248pub struct StreamId(#[cfg_attr(test, strategy(crate::varint::varint_u64()))] u64); +249 +250impl fmt::Display for StreamId { +251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +252 let initiator = match self.initiator() { +253 Side::Client => "client", +254 Side::Server => "server", +255 }; +256 let dir = match self.dir() { +257 Dir::Uni => "uni", +258 Dir::Bi => "bi", +259 }; +260 write!( +261 f, +262 "{} {}directional stream {}", +263 initiator, +264 dir, +265 self.index() +266 ) +267 } +268} +269 +270impl StreamId { +271 /// Create a new StreamId +272 pub fn new(initiator: Side, dir: Dir, index: u64) -> Self { +273 Self((index << 2) | ((dir as u64) << 1) | initiator as u64) +274 } +275 /// Which side of a connection initiated the stream +276 pub fn initiator(self) -> Side { +277 if self.0 & 0x1 == 0 { +278 Side::Client +279 } else { +280 Side::Server +281 } +282 } +283 /// Which directions data flows in +284 pub fn dir(self) -> Dir { +285 if self.0 & 0x2 == 0 { Dir::Bi } else { Dir::Uni } +286 } +287 /// Distinguishes streams of the same initiator and directionality +288 pub fn index(self) -> u64 { +289 self.0 >> 2 +290 } +291} +292 +293impl From<StreamId> for VarInt { +294 fn from(x: StreamId) -> Self { +295 unsafe { Self::from_u64_unchecked(x.0) } +296 } +297} +298 +299impl From<VarInt> for StreamId { +300 fn from(v: VarInt) -> Self { +301 Self(v.0) +302 } +303} +304 +305impl From<StreamId> for u64 { +306 fn from(x: StreamId) -> Self { +307 x.0 +308 } +309} +310 +311impl Decodable for StreamId { +312 fn decode<B: bytes::Buf>(buf: &mut B) -> coding::Result<Self> { +313 VarInt::decode(buf).map(|x| Self(x.into_inner())) +314 } +315} +316 +317impl Encodable for StreamId { +318 fn encode<B: bytes::BufMut>(&self, buf: &mut B) { +319 VarInt::from_u64(self.0).unwrap().encode(buf); +320 } +321} +322 +323#[cfg(feature = "arbitrary")] +324impl<'arbitrary> arbitrary::Arbitrary<'arbitrary> for StreamId { +325 fn arbitrary(u: &mut arbitrary::Unstructured<'arbitrary>) -> arbitrary::Result<Self> { +326 Ok(VarInt::arbitrary(u)?.into()) +327 } +328} +329 +330/// An outgoing packet +331#[derive(Debug)] +332#[must_use] +333pub struct Transmit { +334 /// The socket this datagram should be sent to +335 pub destination: SocketAddr, +336 /// Explicit congestion notification bits to set on the packet +337 pub ecn: Option<EcnCodepoint>, +338 /// Amount of data written to the caller-supplied buffer +339 pub size: usize, +340 /// The segment size if this transmission contains multiple datagrams. +341 /// This is `None` if the transmit only contains a single datagram +342 pub segment_size: Option<usize>, +343 /// Optional source IP address for the datagram +344 pub src_ip: Option<IpAddr>, +345} +346 +347// +348// Useful internal constants +349// +350 +351/// The maximum number of CIDs we bother to issue per path +352const LOCAL_CID_COUNT: u64 = 12; +353const RESET_TOKEN_SIZE: usize = 16; +354const MAX_CID_SIZE: usize = 20; +355const MIN_INITIAL_SIZE: u16 = 1200; +356/// <https://www.rfc-editor.org/rfc/rfc9000.html#name-datagram-size> +357const INITIAL_MTU: u16 = 1200; +358const MAX_UDP_PAYLOAD: u16 = 65527; +359const TIMER_GRANULARITY: Duration = Duration::from_millis(1); +360/// Maximum number of streams that can be uniquely identified by a stream ID +361const MAX_STREAM_COUNT: u64 = 1 << 60; +362 +363/// Identifies a network path by the combination of remote and local addresses +364/// +365/// Including the local ensures good behavior when the host has multiple IP addresses on the same +366/// subnet and zero-length connection IDs are in use or when multipath is enabled and multiple +367/// paths exist with the same remote, but different local IP interfaces. +368/// +369/// `FourTuple` implements `From<SocketAddr>`, which expands to [`Self::from_remote`]. +370#[derive(Hash, Eq, PartialEq, Copy, Clone)] +371pub struct FourTuple { +372 /// The remote side of this tuple. +373 remote: SocketAddr, +374 /// The local side of this tuple. +375 /// +376 /// The socket is irrelevant for our intents and purposes: +377 /// When we send, we can only specify the `src_ip`, not the source port. +378 /// So even if we track the port, we won't be able to make use of it. +379 local_ip: Option<IpAddr>, +380} +381 +382impl FourTuple { +383 /// Creates a new [`FourTuple`]. +384 pub fn new(mut remote: SocketAddr, local_ip: Option<IpAddr>) -> Self { +385 if let SocketAddr::V6(socket_addr) = &mut remote { +386 // RFC3493 §3.3 +387 // > (…) applications should set this field to zero when constructing a sockaddr_in6, +388 // > and ignore this field in a sockaddr_in6 structure constructed by the system. +389 // +390 // This is cleared so that comparisons of remotes are guaranteed to be meaningful: two +391 // socket addresses with the same contents should not differ if only the flow label +392 // differs +393 socket_addr.set_flowinfo(0); +394 +395 // NOTE: not all multicast addresses require a scope. Use `Ipv6Addr::multicast_scope` +396 // when stabilized (<https://github.com/rust-lang/rust/issues/27709>) +397 let requires_scope_id = +398 socket_addr.ip().is_unicast_link_local() || socket_addr.ip().is_multicast(); +399 if !requires_scope_id { +400 // Keep the scope id only when it might be relevant. This ensure network paths can +401 // be compared meaningfully while keeping it when it's important (mainly link local +402 // addresses) +403 socket_addr.set_scope_id(0); +404 } +405 } +406 +407 Self { remote, local_ip } +408 } +409 +410 /// Creates a new [`FourTuple`] without a known local address. +411 pub fn from_remote(remote: SocketAddr) -> Self { +412 Self::new(remote, None) +413 } +414 +415 /// Returns the remote address of the network path. +416 pub fn remote(&self) -> SocketAddr { +417 self.remote +418 } +419 +420 /// Returns the local address of the network path. +421 pub fn local_ip(&self) -> Option<IpAddr> { +422 self.local_ip +423 } +424 +425 /// Returns whether we think the other address probably represents the same path +426 /// as ours. +427 /// +428 /// If we have a local IP set, then we're exact and only match if the 4-tuples are +429 /// exactly equal. +430 /// If we don't have a local IP set, then we only check the remote addresses for equality. +431 /// +432 /// Note that because of this, the following calls might differ: +433 /// - `a.is_probably_same_path(b)` +434 /// - `b.is_probably_same_path(a)` +435 pub(crate) fn is_probably_same_path(&self, other: &Self) -> bool { +436 self.remote == other.remote && (self.local_ip.is_none() || self.local_ip == other.local_ip) +437 } +438} +439 +440impl fmt::Display for FourTuple { +441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +442 f.write_str("(local: ")?; +443 if let Some(local_ip) = &self.local_ip { +444 local_ip.fmt(f)?; +445 f.write_str(", ")?; +446 } else { +447 f.write_str("<unspecified>, ")?; +448 } +449 f.write_str("remote: ")?; +450 self.remote.fmt(f)?; +451 f.write_str(")") +452 } +453} +454 +455impl fmt::Debug for FourTuple { +456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +457 fmt::Display::fmt(&self, f) +458 } +459} +460 +461/// Converts a [`SocketAddr`] to a [`FourTuple`] via [`FourTuple::from_remote`]. +462impl From<SocketAddr> for FourTuple { +463 fn from(value: SocketAddr) -> Self { +464 Self::from_remote(value) +465 } +466}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/n0_nat_traversal.rs.html b/pr/451/docs/src/noq_proto/n0_nat_traversal.rs.html index 477704228..4d2434121 100644 --- a/pr/451/docs/src/noq_proto/n0_nat_traversal.rs.html +++ b/pr/451/docs/src/noq_proto/n0_nat_traversal.rs.html @@ -616,456 +616,460 @@ 616 let remote = (network_path.remote().ip(), network_path.remote().port()); 617 if *entry.get() == remote { 618 entry.remove(); -619 -620 // self.remote_addresses is stored in canonical form. -621 let remote = CanonicalIpPort::from(remote); -622 // TODO: linear search is sad. -623 if let Some(seq) = self -624 .remote_addresses -625 .iter() -626 .filter_map( -627 |(seq, (addr, _))| { -628 if *addr == remote { Some(*seq) } else { None } -629 }, -630 ) -631 .next() -632 { -633 trace!( -634 ?network_path, -635 challenge = %display(format_args!("0x{challenge:x}")), -636 "Received valid NAT traversal probe response", -637 ); -638 self.remote_addresses -639 .insert(seq, (remote, ProbeState::Succeeded)); -640 self.paths_to_be_opened.push(network_path); -641 return true; -642 } else { -643 debug!("inconsistent remote addrs and seq"); -644 } -645 } else { -646 debug!( -647 ?network_path.remote, -648 expected_remote = ?entry.get(), -649 challenge = %display(format_args!("0x{challenge:x}")), -650 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr", -651 ) -652 } -653 } -654 false -655 } -656 -657 /// Returns a path that was NAT traversed and needs to be opened. -658 pub(crate) fn pop_pending_path_open(&mut self) -> Option<FourTuple> { -659 self.paths_to_be_opened.pop() -660 } -661 -662 /// Put back a path that needs to be opened, e.g. for a temporary failure. -663 pub(crate) fn push_pending_path_open(&mut self, network_path: FourTuple) { -664 self.paths_to_be_opened.push(network_path) -665 } -666} -667 -668/// State of an off-path NAT traversal probe to a remote address. -669#[derive(Debug)] -670enum ProbeState { -671 /// The remote still needs to be probed in this round. -672 /// -673 /// The remaining number of retries are stored in the `u8`. -674 Active(u8), -675 /// We received a probe response for this remote. -676 Succeeded, -677} -678 -679impl ProbeState { -680 /// Returns the remaining number of probes to try for this remote. -681 fn remaining(&self) -> u8 { -682 match self { -683 Self::Active(remaining) => *remaining, -684 Self::Succeeded => 0, -685 } -686 } -687} -688 -689#[derive(Debug)] -690pub(crate) struct ServerState { -691 /// Max number of remote addresses we allow. -692 /// -693 /// This is set by the local endpoint. -694 max_remote_addresses: usize, -695 /// Max number of local addresses allowed. +619 trace!( +620 ?network_path, +621 challenge = %display(format_args!("0x{challenge:x}")), +622 "Received valid NAT traversal probe response", +623 ); +624 self.paths_to_be_opened.push(network_path); +625 +626 // TODO: linear search is sad. +627 let remote = CanonicalIpPort::from(remote); +628 if let Some(seq) = self +629 .remote_addresses +630 .iter() +631 .filter_map( +632 |(seq, (addr, _))| { +633 if *addr == remote { Some(*seq) } else { None } +634 }, +635 ) +636 .next() +637 { +638 // Stop probing this remote address. +639 self.remote_addresses +640 .insert(seq, (remote, ProbeState::Succeeded)); +641 } else { +642 // Nothing to stop probing, the remote was only challenged because a +643 // PATH_RESPONSE was being sent to it. These are not retried locally +644 // since the peer is responsible for retrying the challenges until it +645 // receives a response, at which time the local challenge is delivered. +646 trace!("probe opened un-advertised address, peer likely behind DEDN"); +647 } +648 return true; +649 } else { +650 debug!( +651 ?network_path.remote, +652 expected_remote = ?entry.get(), +653 challenge = %display(format_args!("0x{challenge:x}")), +654 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr", +655 ) +656 } +657 } +658 false +659 } +660 +661 /// Returns a path that was NAT traversed and needs to be opened. +662 pub(crate) fn pop_pending_path_open(&mut self) -> Option<FourTuple> { +663 self.paths_to_be_opened.pop() +664 } +665 +666 /// Put back a path that needs to be opened, e.g. for a temporary failure. +667 pub(crate) fn push_pending_path_open(&mut self, network_path: FourTuple) { +668 self.paths_to_be_opened.push(network_path) +669 } +670} +671 +672/// State of an off-path NAT traversal probe to a remote address. +673#[derive(Debug)] +674enum ProbeState { +675 /// The remote still needs to be probed in this round. +676 /// +677 /// The remaining number of retries are stored in the `u8`. +678 Active(u8), +679 /// We received a probe response for this remote. +680 Succeeded, +681} +682 +683impl ProbeState { +684 /// Returns the remaining number of probes to try for this remote. +685 fn remaining(&self) -> u8 { +686 match self { +687 Self::Active(remaining) => *remaining, +688 Self::Succeeded => 0, +689 } +690 } +691} +692 +693#[derive(Debug)] +694pub(crate) struct ServerState { +695 /// Max number of remote addresses we allow. 696 /// -697 /// This is set by the remote endpoint. -698 max_local_addresses: usize, -699 /// Candidate addresses the server reports as potentially reachable, to use for nat -700 /// traversal attempts. -701 /// -702 /// They are stored in **canonical form**, not in socket-native form as usual. We may -703 /// nave a reflexive address that is IPv6 even if our local socket can only handle IPv4. -704 local_addresses: FxHashMap<CanonicalIpPort, VarInt>, -705 /// The next id to use for local addresses sent to the client. -706 next_local_addr_id: VarInt, -707 /// Current nat traversal round -708 /// -709 /// Servers keep track of the client's most recent round and cancel probing related to previous -710 /// rounds. -711 round: VarInt, -712 /// The probing attempt in the round. -713 /// -714 /// Probes are sent to all remotes at the same time in a round, at intervals from -715 /// [`State::retry_delay`]. This is the number of times probes have been sent. -716 attempt: u8, -717 /// The remote addresses participating in this round. -718 /// -719 /// The set is cleared when a new round starts. -720 /// -721 /// These are stored in the usual local-socket native form. -722 remotes: FxHashMap<IpPort, ProbeState>, -723 /// The data of PATH_CHALLENGE frames sent in probes. +697 /// This is set by the local endpoint. +698 max_remote_addresses: usize, +699 /// Max number of local addresses allowed. +700 /// +701 /// This is set by the remote endpoint. +702 max_local_addresses: usize, +703 /// Candidate addresses the server reports as potentially reachable, to use for nat +704 /// traversal attempts. +705 /// +706 /// They are stored in **canonical form**, not in socket-native form as usual. We may +707 /// nave a reflexive address that is IPv6 even if our local socket can only handle IPv4. +708 local_addresses: FxHashMap<CanonicalIpPort, VarInt>, +709 /// The next id to use for local addresses sent to the client. +710 next_local_addr_id: VarInt, +711 /// Current nat traversal round +712 /// +713 /// Servers keep track of the client's most recent round and cancel probing related to previous +714 /// rounds. +715 round: VarInt, +716 /// The probing attempt in the round. +717 /// +718 /// Probes are sent to all remotes at the same time in a round, at intervals from +719 /// [`State::retry_delay`]. This is the number of times probes have been sent. +720 attempt: u8, +721 /// The remote addresses participating in this round. +722 /// +723 /// The set is cleared when a new round starts. 724 /// -725 /// These are cleared when a new round starts, so any late-arriving PATH_RESPONSEs will -726 /// have no effect. -727 sent_challenges: FxHashMap<u64, IpPort>, -728 /// Queued probes to be sent in the next [`poll_transmit`] call. -729 /// -730 /// At the beginning of a round this is populated from REACH_OUT frames and at every -731 /// retry this is populated from [`Self::remotes`]. -732 /// -733 /// [`poll_transmit`]: crate::connection::Connection::poll_transmit -734 pending_probes: FxHashSet<IpPort>, -735} -736 -737impl ServerState { -738 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { -739 Self { -740 max_remote_addresses, -741 max_local_addresses, -742 local_addresses: Default::default(), -743 next_local_addr_id: Default::default(), -744 round: Default::default(), -745 attempt: 0, -746 remotes: Default::default(), -747 sent_challenges: Default::default(), -748 pending_probes: Default::default(), -749 } -750 } -751 -752 fn add_local_address(&mut self, address: SocketAddr) -> Result<Option<AddAddress>, Error> { -753 let address = CanonicalIpPort::from(address); -754 let allow_new = self.local_addresses.len() < self.max_local_addresses; -755 match self.local_addresses.entry(address) { -756 Entry::Occupied(_) => Ok(None), -757 Entry::Vacant(vacant_entry) if allow_new => { -758 let id = self.next_local_addr_id; -759 self.next_local_addr_id = self.next_local_addr_id.saturating_add(1u8); -760 vacant_entry.insert(id); -761 Ok(Some(AddAddress::new((address.ip(), address.port()), id))) -762 } -763 _ => Err(Error::TooManyAddresses), -764 } -765 } -766 -767 fn remove_local_address(&mut self, address: &IpPort) -> Option<RemoveAddress> { -768 let address = CanonicalIpPort::from(*address); -769 self.local_addresses -770 .remove(&address) -771 .map(RemoveAddress::new) -772 } -773 -774 /// Returns the current NAT traversal round number. -775 pub(crate) fn current_round(&self) -> VarInt { -776 self.round -777 } -778 -779 /// Handles a received REACH_OUT frame. -780 /// -781 /// This might ignore the reach out frame if it belongs to an older round or if the -782 /// frame contains an IPv6 address while the local socket is IPv4-only. -783 /// -784 /// If a new round was started, the `NatTraversalProbeRetry` timer needs to be reset. -785 pub(crate) fn handle_reach_out( -786 &mut self, -787 reach_out: ReachOut, -788 ipv6: bool, -789 ) -> Result<(), Error> { -790 let ReachOut { round, ip, port } = reach_out; -791 -792 if round < self.round { -793 trace!(current_round=%self.round, "ignoring REACH_OUT for previous round"); -794 return Ok(()); -795 } -796 let Some(ip) = map_to_local_socket_family(ip, ipv6) else { -797 trace!("Ignoring IPv6 REACH_OUT frame due to not supporting IPv6 locally"); +725 /// These are stored in the usual local-socket native form. +726 remotes: FxHashMap<IpPort, ProbeState>, +727 /// The data of PATH_CHALLENGE frames sent in probes. +728 /// +729 /// These are cleared when a new round starts, so any late-arriving PATH_RESPONSEs will +730 /// have no effect. +731 sent_challenges: FxHashMap<u64, IpPort>, +732 /// Queued probes to be sent in the next [`poll_transmit`] call. +733 /// +734 /// At the beginning of a round this is populated from REACH_OUT frames and at every +735 /// retry this is populated from [`Self::remotes`]. +736 /// +737 /// [`poll_transmit`]: crate::connection::Connection::poll_transmit +738 pending_probes: FxHashSet<IpPort>, +739} +740 +741impl ServerState { +742 fn new(max_remote_addresses: usize, max_local_addresses: usize) -> Self { +743 Self { +744 max_remote_addresses, +745 max_local_addresses, +746 local_addresses: Default::default(), +747 next_local_addr_id: Default::default(), +748 round: Default::default(), +749 attempt: 0, +750 remotes: Default::default(), +751 sent_challenges: Default::default(), +752 pending_probes: Default::default(), +753 } +754 } +755 +756 fn add_local_address(&mut self, address: SocketAddr) -> Result<Option<AddAddress>, Error> { +757 let address = CanonicalIpPort::from(address); +758 let allow_new = self.local_addresses.len() < self.max_local_addresses; +759 match self.local_addresses.entry(address) { +760 Entry::Occupied(_) => Ok(None), +761 Entry::Vacant(vacant_entry) if allow_new => { +762 let id = self.next_local_addr_id; +763 self.next_local_addr_id = self.next_local_addr_id.saturating_add(1u8); +764 vacant_entry.insert(id); +765 Ok(Some(AddAddress::new((address.ip(), address.port()), id))) +766 } +767 _ => Err(Error::TooManyAddresses), +768 } +769 } +770 +771 fn remove_local_address(&mut self, address: &IpPort) -> Option<RemoveAddress> { +772 let address = CanonicalIpPort::from(*address); +773 self.local_addresses +774 .remove(&address) +775 .map(RemoveAddress::new) +776 } +777 +778 /// Returns the current NAT traversal round number. +779 pub(crate) fn current_round(&self) -> VarInt { +780 self.round +781 } +782 +783 /// Handles a received REACH_OUT frame. +784 /// +785 /// This might ignore the reach out frame if it belongs to an older round or if the +786 /// frame contains an IPv6 address while the local socket is IPv4-only. +787 /// +788 /// If a new round was started, the `NatTraversalProbeRetry` timer needs to be reset. +789 pub(crate) fn handle_reach_out( +790 &mut self, +791 reach_out: ReachOut, +792 ipv6: bool, +793 ) -> Result<(), Error> { +794 let ReachOut { round, ip, port } = reach_out; +795 +796 if round < self.round { +797 trace!(current_round=%self.round, "ignoring REACH_OUT for previous round"); 798 return Ok(()); -799 }; -800 -801 if round > self.round { -802 self.round = round; -803 self.attempt = 0; -804 self.remotes.clear(); -805 self.sent_challenges.clear(); -806 self.pending_probes.clear(); -807 } else if self.remotes.contains_key(&(ip, port)) { -808 // Retransmitted frame. -809 return Ok(()); -810 } else if self.remotes.len() >= self.max_remote_addresses { -811 return Err(Error::TooManyAddresses); -812 } -813 self.remotes -814 .entry((ip, port)) -815 .or_insert(ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS - 1)); -816 self.pending_probes.insert((ip, port)); -817 Ok(()) -818 } -819 -820 /// Re-queues probes that have not yet succeeded or reached [`MAX_NAT_PROBE_ATTEMPTS`]. -821 /// -822 /// Returns whether any probes are now queued to send. In this case the -823 /// `NatTraversalProbeRetry` timer needs to be reset. -824 pub(crate) fn queue_retries(&mut self) { -825 self.attempt += 1; -826 self.remotes -827 .iter_mut() -828 .for_each(|(remote, state)| match state { -829 ProbeState::Active(remaining) if *remaining > 0 => { -830 *remaining -= 1; -831 self.pending_probes.insert(*remote); -832 } -833 ProbeState::Active(_) | ProbeState::Succeeded => (), -834 }); -835 } -836 -837 /// Returns the next ready probe's address. -838 /// -839 /// If this is actually sent you must call [`Self::mark_probe_sent`]. -840 fn next_probe_addr(&self) -> Option<IpPort> { -841 self.pending_probes.iter().next().cloned() -842 } -843 -844 /// Marks a probe as sent to the address with the challenge. -845 fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { -846 self.pending_probes.remove(&remote); -847 self.sent_challenges.insert(challenge, remote); -848 } -849 -850 /// Marks a remote as successful if the response matches a sent probe. -851 /// -852 /// Returns `true` if it was a response to one of the NAT traversal probes. -853 fn handle_path_response(&mut self, src: FourTuple, challenge: u64) -> bool { -854 if let Entry::Occupied(entry) = self.sent_challenges.entry(challenge) { -855 let remote = (src.remote().ip(), src.remote().port()); -856 if *entry.get() == remote { -857 entry.remove(); -858 self.remotes.insert(remote, ProbeState::Succeeded); -859 return true; -860 } else { -861 debug!( -862 ?challenge, -863 ?src.remote, -864 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr", -865 ) -866 } -867 } -868 false -869 } -870} -871 -872/// Returns the given address as canonicalized IP address. -873/// -874/// This checks that the address family is supported by our local socket. -875/// If it is supported, then the address is mapped to the respective IP address. -876/// If the given address is an IPv6 address, but our local socket doesn't support -877/// IPv6, then this returns `None`. -878pub(crate) fn map_to_local_socket_family(address: IpAddr, ipv6: bool) -> Option<IpAddr> { -879 let ip = match address { -880 IpAddr::V4(addr) if ipv6 => IpAddr::V6(addr.to_ipv6_mapped()), -881 IpAddr::V4(_) => address, -882 IpAddr::V6(_) if ipv6 => address, -883 IpAddr::V6(addr) => IpAddr::V4(addr.to_ipv4_mapped()?), -884 }; -885 Some(ip) -886} -887 -888#[cfg(test)] -889mod tests { -890 use testresult::TestResult; +799 } +800 let Some(ip) = map_to_local_socket_family(ip, ipv6) else { +801 trace!("Ignoring IPv6 REACH_OUT frame due to not supporting IPv6 locally"); +802 return Ok(()); +803 }; +804 +805 if round > self.round { +806 self.round = round; +807 self.attempt = 0; +808 self.remotes.clear(); +809 self.sent_challenges.clear(); +810 self.pending_probes.clear(); +811 } else if self.remotes.contains_key(&(ip, port)) { +812 // Retransmitted frame. +813 return Ok(()); +814 } else if self.remotes.len() >= self.max_remote_addresses { +815 return Err(Error::TooManyAddresses); +816 } +817 self.remotes +818 .entry((ip, port)) +819 .or_insert(ProbeState::Active(MAX_NAT_PROBE_ATTEMPTS - 1)); +820 self.pending_probes.insert((ip, port)); +821 Ok(()) +822 } +823 +824 /// Re-queues probes that have not yet succeeded or reached [`MAX_NAT_PROBE_ATTEMPTS`]. +825 /// +826 /// Returns whether any probes are now queued to send. In this case the +827 /// `NatTraversalProbeRetry` timer needs to be reset. +828 pub(crate) fn queue_retries(&mut self) { +829 self.attempt += 1; +830 self.remotes +831 .iter_mut() +832 .for_each(|(remote, state)| match state { +833 ProbeState::Active(remaining) if *remaining > 0 => { +834 *remaining -= 1; +835 self.pending_probes.insert(*remote); +836 } +837 ProbeState::Active(_) | ProbeState::Succeeded => (), +838 }); +839 } +840 +841 /// Returns the next ready probe's address. +842 /// +843 /// If this is actually sent you must call [`Self::mark_probe_sent`]. +844 fn next_probe_addr(&self) -> Option<IpPort> { +845 self.pending_probes.iter().next().cloned() +846 } +847 +848 /// Marks a probe as sent to the address with the challenge. +849 fn mark_probe_sent(&mut self, remote: IpPort, challenge: u64) { +850 self.pending_probes.remove(&remote); +851 self.sent_challenges.insert(challenge, remote); +852 } +853 +854 /// Marks a remote as successful if the response matches a sent probe. +855 /// +856 /// Returns `true` if it was a response to one of the NAT traversal probes. +857 fn handle_path_response(&mut self, src: FourTuple, challenge: u64) -> bool { +858 if let Entry::Occupied(entry) = self.sent_challenges.entry(challenge) { +859 let remote = (src.remote().ip(), src.remote().port()); +860 if *entry.get() == remote { +861 entry.remove(); +862 self.remotes.insert(remote, ProbeState::Succeeded); +863 return true; +864 } else { +865 debug!( +866 ?challenge, +867 ?src.remote, +868 "PATH_RESPONSE matched a NAT traversal probe but mismatching addr", +869 ) +870 } +871 } +872 false +873 } +874} +875 +876/// Returns the given address as canonicalized IP address. +877/// +878/// This checks that the address family is supported by our local socket. +879/// If it is supported, then the address is mapped to the respective IP address. +880/// If the given address is an IPv6 address, but our local socket doesn't support +881/// IPv6, then this returns `None`. +882pub(crate) fn map_to_local_socket_family(address: IpAddr, ipv6: bool) -> Option<IpAddr> { +883 let ip = match address { +884 IpAddr::V4(addr) if ipv6 => IpAddr::V6(addr.to_ipv6_mapped()), +885 IpAddr::V4(_) => address, +886 IpAddr::V6(_) if ipv6 => address, +887 IpAddr::V6(addr) => IpAddr::V4(addr.to_ipv4_mapped()?), +888 }; +889 Some(ip) +890} 891 -892 use super::*; -893 -894 #[test] -895 fn test_basic_server_state() { -896 let mut state = ServerState::new(2, 2); +892#[cfg(test)] +893mod tests { +894 use testresult::TestResult; +895 +896 use super::*; 897 -898 state -899 .handle_reach_out( -900 ReachOut { -901 round: 1u32.into(), -902 ip: std::net::Ipv4Addr::LOCALHOST.into(), -903 port: 1, -904 }, -905 true, -906 ) -907 .unwrap(); -908 -909 state -910 .handle_reach_out( -911 ReachOut { -912 round: 1u32.into(), -913 ip: "1.1.1.1".parse().unwrap(), //std::net::Ipv4Addr::LOCALHOST.into(), -914 port: 2, -915 }, -916 true, -917 ) -918 .unwrap(); -919 -920 dbg!(&state); -921 assert_eq!(state.pending_probes.len(), 2); -922 -923 // Helper: send next ready probe -924 let mut challenge = 0; -925 let mut send_probe = |state: &mut ServerState| { -926 let remote = state.next_probe_addr().unwrap(); -927 challenge += 1; -928 state.mark_probe_sent(remote, challenge); -929 }; -930 -931 send_probe(&mut state); -932 send_probe(&mut state); -933 -934 // After sending both probes, no ready probes remain but they're still tracked. -935 assert!(state.next_probe_addr().is_none()); -936 -937 // After queuing retries, probes become available again -938 state.queue_retries(); -939 send_probe(&mut state); -940 send_probe(&mut state); -941 -942 // After 2 attempts each, retries still available (max is 10) -943 state.queue_retries(); +898 #[test] +899 fn test_basic_server_state() { +900 let mut state = ServerState::new(2, 2); +901 +902 state +903 .handle_reach_out( +904 ReachOut { +905 round: 1u32.into(), +906 ip: std::net::Ipv4Addr::LOCALHOST.into(), +907 port: 1, +908 }, +909 true, +910 ) +911 .unwrap(); +912 +913 state +914 .handle_reach_out( +915 ReachOut { +916 round: 1u32.into(), +917 ip: "1.1.1.1".parse().unwrap(), //std::net::Ipv4Addr::LOCALHOST.into(), +918 port: 2, +919 }, +920 true, +921 ) +922 .unwrap(); +923 +924 dbg!(&state); +925 assert_eq!(state.pending_probes.len(), 2); +926 +927 // Helper: send next ready probe +928 let mut challenge = 0; +929 let mut send_probe = |state: &mut ServerState| { +930 let remote = state.next_probe_addr().unwrap(); +931 challenge += 1; +932 state.mark_probe_sent(remote, challenge); +933 }; +934 +935 send_probe(&mut state); +936 send_probe(&mut state); +937 +938 // After sending both probes, no ready probes remain but they're still tracked. +939 assert!(state.next_probe_addr().is_none()); +940 +941 // After queuing retries, probes become available again +942 state.queue_retries(); +943 send_probe(&mut state); 944 send_probe(&mut state); -945 send_probe(&mut state); -946 -947 // Exhaust remaining attempts -948 for _ in 3..MAX_NAT_PROBE_ATTEMPTS { -949 state.queue_retries(); -950 send_probe(&mut state); -951 send_probe(&mut state); -952 } -953 -954 // After max attempts, probes are removed -955 state.queue_retries(); -956 assert!(state.next_probe_addr().is_none()); -957 } -958 -959 #[test] -960 fn test_map_to_local_socket() { -961 assert_eq!( -962 map_to_local_socket_family("1.1.1.1".parse().unwrap(), false), -963 Some("1.1.1.1".parse().unwrap()) -964 ); +945 +946 // After 2 attempts each, retries still available (max is 10) +947 state.queue_retries(); +948 send_probe(&mut state); +949 send_probe(&mut state); +950 +951 // Exhaust remaining attempts +952 for _ in 3..MAX_NAT_PROBE_ATTEMPTS { +953 state.queue_retries(); +954 send_probe(&mut state); +955 send_probe(&mut state); +956 } +957 +958 // After max attempts, probes are removed +959 state.queue_retries(); +960 assert!(state.next_probe_addr().is_none()); +961 } +962 +963 #[test] +964 fn test_map_to_local_socket() { 965 assert_eq!( -966 map_to_local_socket_family("1.1.1.1".parse().unwrap(), true), -967 Some("::ffff:1.1.1.1".parse().unwrap()) +966 map_to_local_socket_family("1.1.1.1".parse().unwrap(), false), +967 Some("1.1.1.1".parse().unwrap()) 968 ); 969 assert_eq!( -970 map_to_local_socket_family("::1".parse().unwrap(), true), -971 Some("::1".parse().unwrap()) +970 map_to_local_socket_family("1.1.1.1".parse().unwrap(), true), +971 Some("::ffff:1.1.1.1".parse().unwrap()) 972 ); 973 assert_eq!( -974 map_to_local_socket_family("::1".parse().unwrap(), false), -975 None -976 ); +974 map_to_local_socket_family("::1".parse().unwrap(), true), +975 Some("::1".parse().unwrap()) +976 ); 977 assert_eq!( -978 map_to_local_socket_family("::ffff:1.1.1.1".parse().unwrap(), false), -979 Some("1.1.1.1".parse().unwrap()) -980 ) -981 } -982 -983 #[test] -984 fn test_retry_delay_server_ipv6() -> TestResult { -985 let initial_rtt = Duration::from_millis(333); -986 let ipv6 = true; -987 let remote = SocketAddr::from(("::2".parse::<IpAddr>()?, 2)); -988 let remote_ipp = (remote.ip(), remote.port()); -989 -990 let mut nat = State::new(8, 8, Side::Server); -991 -992 nat.server_side_mut()?.handle_reach_out( -993 ReachOut { -994 round: 1u8.into(), -995 ip: remote.ip(), -996 port: remote.port(), -997 }, -998 ipv6, -999 )?; -1000 -1001 let challenges = [1u64, 2, 3, 4, 5, 6, 7]; -1002 let delays = [ -1003 33_300u64, 66_600, 133_200, 266_400, 532_800, 1_065_600, 2_000_000, -1004 ]; -1005 for (challenge, delay) in challenges.into_iter().zip(delays) { -1006 nat.queue_retries(ipv6); -1007 assert_eq!(nat.next_probe_addr(), Some(remote_ipp)); -1008 nat.mark_probe_sent(remote_ipp, challenge); -1009 assert_eq!( -1010 nat.retry_delay(initial_rtt), -1011 Some(Duration::from_micros(delay)), -1012 "challenge: {challenge}" -1013 ); -1014 } -1015 -1016 assert!(nat.handle_path_response( -1017 FourTuple { -1018 remote, -1019 local_ip: Some("::3".parse::<IpAddr>()?), -1020 }, -1021 challenges[6] -1022 )); -1023 assert_eq!(nat.retry_delay(initial_rtt), None); -1024 -1025 Ok(()) -1026 } -1027 -1028 #[test] -1029 fn test_retry_delay_client_ipv6() -> TestResult { -1030 let initial_rtt = Duration::from_millis(333); -1031 let ipv6 = true; -1032 let remote = SocketAddr::from(("::2".parse::<IpAddr>()?, 2)); -1033 let remote_ipp = (remote.ip(), remote.port()); -1034 let local_addr = SocketAddr::from(("::3".parse::<IpAddr>()?, 3)); -1035 -1036 let mut nat = State::new(8, 8, Side::Client); -1037 nat.add_local_address(local_addr)?; -1038 nat.client_side_mut()?.add_remote_address(AddAddress { -1039 seq_no: 1u8.into(), -1040 ip: remote.ip(), -1041 port: remote.port(), -1042 })?; -1043 nat.client_side_mut()?.initiate_nat_traversal_round(ipv6)?; -1044 -1045 let challenges = [1u64, 2, 3, 4, 5, 6, 7]; -1046 let delays = [ -1047 33_300u64, 66_600, 133_200, 266_400, 532_800, 1_065_600, 2_000_000, -1048 ]; -1049 for (challenge, delay) in challenges.into_iter().zip(delays) { -1050 nat.queue_retries(ipv6); -1051 assert_eq!(nat.next_probe_addr(), Some(remote_ipp)); -1052 nat.mark_probe_sent(remote_ipp, challenge); -1053 assert_eq!( -1054 nat.retry_delay(initial_rtt), -1055 Some(Duration::from_micros(delay)), -1056 "challenge: {challenge}" -1057 ); -1058 } -1059 -1060 assert!(nat.handle_path_response( -1061 FourTuple { -1062 remote, -1063 local_ip: Some(local_addr.ip()), -1064 }, -1065 challenges[6] -1066 )); -1067 assert_eq!(nat.retry_delay(initial_rtt), None); -1068 -1069 Ok(()) -1070 } -1071}

\ No newline at end of file +978 map_to_local_socket_family("::1".parse().unwrap(), false), +979 None +980 ); +981 assert_eq!( +982 map_to_local_socket_family("::ffff:1.1.1.1".parse().unwrap(), false), +983 Some("1.1.1.1".parse().unwrap()) +984 ) +985 } +986 +987 #[test] +988 fn test_retry_delay_server_ipv6() -> TestResult { +989 let initial_rtt = Duration::from_millis(333); +990 let ipv6 = true; +991 let remote = SocketAddr::from(("::2".parse::<IpAddr>()?, 2)); +992 let remote_ipp = (remote.ip(), remote.port()); +993 +994 let mut nat = State::new(8, 8, Side::Server); +995 +996 nat.server_side_mut()?.handle_reach_out( +997 ReachOut { +998 round: 1u8.into(), +999 ip: remote.ip(), +1000 port: remote.port(), +1001 }, +1002 ipv6, +1003 )?; +1004 +1005 let challenges = [1u64, 2, 3, 4, 5, 6, 7]; +1006 let delays = [ +1007 33_300u64, 66_600, 133_200, 266_400, 532_800, 1_065_600, 2_000_000, +1008 ]; +1009 for (challenge, delay) in challenges.into_iter().zip(delays) { +1010 nat.queue_retries(ipv6); +1011 assert_eq!(nat.next_probe_addr(), Some(remote_ipp)); +1012 nat.mark_probe_sent(remote_ipp, challenge); +1013 assert_eq!( +1014 nat.retry_delay(initial_rtt), +1015 Some(Duration::from_micros(delay)), +1016 "challenge: {challenge}" +1017 ); +1018 } +1019 +1020 assert!(nat.handle_path_response( +1021 FourTuple { +1022 remote, +1023 local_ip: Some("::3".parse::<IpAddr>()?), +1024 }, +1025 challenges[6] +1026 )); +1027 assert_eq!(nat.retry_delay(initial_rtt), None); +1028 +1029 Ok(()) +1030 } +1031 +1032 #[test] +1033 fn test_retry_delay_client_ipv6() -> TestResult { +1034 let initial_rtt = Duration::from_millis(333); +1035 let ipv6 = true; +1036 let remote = SocketAddr::from(("::2".parse::<IpAddr>()?, 2)); +1037 let remote_ipp = (remote.ip(), remote.port()); +1038 let local_addr = SocketAddr::from(("::3".parse::<IpAddr>()?, 3)); +1039 +1040 let mut nat = State::new(8, 8, Side::Client); +1041 nat.add_local_address(local_addr)?; +1042 nat.client_side_mut()?.add_remote_address(AddAddress { +1043 seq_no: 1u8.into(), +1044 ip: remote.ip(), +1045 port: remote.port(), +1046 })?; +1047 nat.client_side_mut()?.initiate_nat_traversal_round(ipv6)?; +1048 +1049 let challenges = [1u64, 2, 3, 4, 5, 6, 7]; +1050 let delays = [ +1051 33_300u64, 66_600, 133_200, 266_400, 532_800, 1_065_600, 2_000_000, +1052 ]; +1053 for (challenge, delay) in challenges.into_iter().zip(delays) { +1054 nat.queue_retries(ipv6); +1055 assert_eq!(nat.next_probe_addr(), Some(remote_ipp)); +1056 nat.mark_probe_sent(remote_ipp, challenge); +1057 assert_eq!( +1058 nat.retry_delay(initial_rtt), +1059 Some(Duration::from_micros(delay)), +1060 "challenge: {challenge}" +1061 ); +1062 } +1063 +1064 assert!(nat.handle_path_response( +1065 FourTuple { +1066 remote, +1067 local_ip: Some(local_addr.ip()), +1068 }, +1069 challenges[6] +1070 )); +1071 assert_eq!(nat.retry_delay(initial_rtt), None); +1072 +1073 Ok(()) +1074 } +1075}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/range_set/array_range_set.rs.html b/pr/451/docs/src/noq_proto/range_set/array_range_set.rs.html index afb3736b8..b94e01874 100644 --- a/pr/451/docs/src/noq_proto/range_set/array_range_set.rs.html +++ b/pr/451/docs/src/noq_proto/range_set/array_range_set.rs.html @@ -1,253 +1,278 @@ array_range_set.rs - source

noq_proto/range_set/
array_range_set.rs

1use std::fmt::{self, Write};
-2use std::ops::Range;
-3
-4use tinyvec::TinyVec;
-5
-6/// A set of u64 values optimized for long runs and random insert/delete/contains
-7///
-8/// `ArrayRangeSet` uses an array representation, where each array entry represents
-9/// a range.
-10///
-11/// The array-based RangeSet provides 2 benefits:
-12/// - There exists an inline representation, which avoids the need of heap
-13///   allocating ACK ranges for SentFrames for small ranges.
-14/// - Iterating over ranges should usually be faster since there is only
-15///   a single cache-friendly contiguous range.
-16///
-17/// `ArrayRangeSet` is especially useful for tracking ACK ranges where the amount
-18/// of ranges is usually very low (since ACK numbers are in consecutive fashion
-19/// unless reordering or packet loss occur).
-20#[derive(Default, PartialEq, Eq)]
-21pub(crate) struct ArrayRangeSet(TinyVec<[Range<u64>; ARRAY_RANGE_SET_INLINE_CAPACITY]>);
-22
-23impl fmt::Debug for ArrayRangeSet {
-24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-25        f.write_char('[')?;
-26        let mut first = true;
-27        for range in self.0.iter() {
-28            if !first {
-29                f.write_char(',')?;
-30            }
-31            write!(f, "{range:?}")?;
-32            first = false;
-33        }
-34        f.write_char(']')?;
-35        Ok(())
-36    }
-37}
-38
-39/// The capacity of elements directly stored in [`ArrayRangeSet`]
-40///
-41/// An inline capacity of 2 is chosen to keep `SentFrame` below 128 bytes.
-42const ARRAY_RANGE_SET_INLINE_CAPACITY: usize = 2;
-43
-44impl Clone for ArrayRangeSet {
-45    fn clone(&self) -> Self {
-46        // tinyvec keeps the heap representation after clones.
-47        // We rather prefer the inline representation for clones if possible,
-48        // since clones (e.g. for storage in `SentFrames`) are rarely mutated
-49        if self.0.is_inline() || self.0.len() > ARRAY_RANGE_SET_INLINE_CAPACITY {
-50            return Self(self.0.clone());
-51        }
-52
-53        let mut vec = TinyVec::new();
-54        vec.extend_from_slice(self.0.as_slice());
-55        Self(vec)
-56    }
-57}
-58
-59impl ArrayRangeSet {
-60    pub(crate) fn new() -> Self {
-61        Default::default()
-62    }
-63
-64    pub(crate) fn iter(&self) -> impl DoubleEndedIterator<Item = Range<u64>> + '_ {
-65        self.0.iter().cloned()
-66    }
-67
-68    pub(crate) fn elts(&self) -> impl Iterator<Item = u64> + '_ {
-69        self.iter().flatten()
-70    }
-71
-72    pub(crate) fn len(&self) -> usize {
-73        self.0.len()
-74    }
-75
-76    #[cfg(test)]
-77    pub(crate) fn contains(&self, x: u64) -> bool {
-78        for range in self.0.iter() {
-79            if range.start > x {
-80                // We only get here if there was no prior range that contained x
-81                return false;
-82            } else if range.contains(&x) {
-83                return true;
-84            }
-85        }
-86        false
-87    }
-88
-89    pub(crate) fn iter_range(&self, range: Range<u64>) -> impl Iterator<Item = Range<u64>> + '_ {
-90        self.iter().filter_map(move |r| {
-91            if r.end > range.start && r.start < range.end {
-92                Some(r.start.max(range.start)..r.end.min(range.end))
-93            } else {
-94                None
-95            }
-96        })
-97    }
-98
-99    pub(crate) fn insert_one(&mut self, x: u64) -> bool {
-100        self.insert(x..x + 1)
-101    }
-102
-103    pub(crate) fn insert(&mut self, x: Range<u64>) -> bool {
-104        let mut result = false;
-105
-106        if x.is_empty() {
-107            // Don't try to deal with ranges where x.end <= x.start
-108            return false;
-109        }
-110
-111        let mut idx = 0;
-112        while idx != self.0.len() {
-113            let range = &mut self.0[idx];
-114
-115            if range.start > x.end {
-116                // The range is fully before this range and therefore not extensible.
-117                // Add a new range to the left
-118                self.0.insert(idx, x);
-119                return true;
-120            } else if range.start > x.start {
-121                // The new range starts before this range but overlaps.
-122                // Extend the current range to the left
-123                // Note that we don't have to merge a potential left range, since
-124                // this case would have been captured by merging the right range
-125                // in the previous loop iteration
-126                result = true;
-127                range.start = x.start;
-128            }
-129
-130            // At this point we have handled all parts of the new range which
-131            // are in front of the current range. Now we handle everything from
-132            // the start of the current range
-133
-134            if x.end <= range.end {
-135                // Fully contained
-136                return result;
-137            } else if x.start <= range.end {
-138                // Extend the current range to the end of the new range.
-139                // Since it's not contained it must be bigger
-140                range.end = x.end;
-141
-142                // Merge all follow-up ranges which overlap
-143                while idx != self.0.len() - 1 {
-144                    let curr = self.0[idx].clone();
-145                    let next = self.0[idx + 1].clone();
-146                    if curr.end >= next.start {
-147                        self.0[idx].end = next.end.max(curr.end);
-148                        self.0.remove(idx + 1);
-149                    } else {
-150                        break;
-151                    }
-152                }
-153
-154                return true;
-155            }
-156
-157            idx += 1;
-158        }
-159
-160        // Insert a range at the end
-161        self.0.push(x);
-162        true
-163    }
-164
-165    pub(crate) fn remove(&mut self, x: Range<u64>) -> bool {
-166        let mut result = false;
-167
-168        if x.is_empty() {
-169            // Don't try to deal with ranges where x.end <= x.start
-170            return false;
+2use std::iter::Sum;
+3use std::ops::{Add, Range, Sub};
+4
+5use tinyvec::TinyVec;
+6
+7/// A set of u64 values optimized for long runs and random insert/delete/contains
+8///
+9/// `ArrayRangeSet` uses an array representation, where each array entry represents
+10/// a range.
+11///
+12/// The array-based RangeSet provides 2 benefits:
+13/// - There exists an inline representation, which avoids the need of heap
+14///   allocating ACK ranges for SentFrames for small ranges.
+15/// - Iterating over ranges should usually be faster since there is only
+16///   a single cache-friendly contiguous range.
+17///
+18/// `ArrayRangeSet` is especially useful for tracking ACK ranges where the amount
+19/// of ranges is usually very low (since ACK numbers are in consecutive fashion
+20/// unless reordering or packet loss occur).
+21#[derive(Default, PartialEq, Eq)]
+22pub(crate) struct ArrayRangeSet<const N: usize = ARRAY_RANGE_SET_INLINE_CAPACITY, T: Default = u64>(
+23    TinyVec<[Range<T>; N]>,
+24);
+25
+26impl<const N: usize, T: fmt::Debug + Default> fmt::Debug for ArrayRangeSet<N, T> {
+27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+28        f.write_char('[')?;
+29        let mut first = true;
+30        for range in self.0.iter() {
+31            if !first {
+32                f.write_char(',')?;
+33            }
+34            write!(f, "{range:?}")?;
+35            first = false;
+36        }
+37        f.write_char(']')?;
+38        Ok(())
+39    }
+40}
+41
+42/// The capacity of elements directly stored in [`ArrayRangeSet`]
+43///
+44/// An inline capacity of 2 is chosen to keep `SentFrame` below 128 bytes.
+45pub(crate) const ARRAY_RANGE_SET_INLINE_CAPACITY: usize = 2;
+46
+47impl<const N: usize> Clone for ArrayRangeSet<N> {
+48    fn clone(&self) -> Self {
+49        // tinyvec keeps the heap representation after clones.
+50        // We rather prefer the inline representation for clones if possible,
+51        // since clones (e.g. for storage in `SentFrames`) are rarely mutated
+52        if self.0.is_inline() || self.0.len() > ARRAY_RANGE_SET_INLINE_CAPACITY {
+53            return Self(self.0.clone());
+54        }
+55
+56        let mut vec = TinyVec::new();
+57        vec.extend_from_slice(self.0.as_slice());
+58        Self(vec)
+59    }
+60}
+61
+62impl<const N: usize, T> ArrayRangeSet<N, T>
+63where
+64    T: Default
+65        + Clone
+66        + Copy
+67        + PartialOrd
+68        + Ord
+69        + From<u32>
+70        + Add<T, Output = T>
+71        + Sub<T, Output = T>
+72        + Sum,
+73{
+74    pub(crate) fn new() -> Self {
+75        Default::default()
+76    }
+77
+78    pub(crate) fn iter(&self) -> impl DoubleEndedIterator<Item = Range<T>> + '_ {
+79        self.0.iter().cloned()
+80    }
+81
+82    pub(crate) fn range_count(&self) -> usize {
+83        self.0.len()
+84    }
+85
+86    pub(crate) fn elts_count(&self) -> T {
+87        self.0.iter().map(|r| r.end - r.start).sum()
+88    }
+89
+90    pub(crate) fn contains(&self, x: T) -> bool {
+91        for range in self.0.iter() {
+92            if range.start > x {
+93                // We only get here if there was no prior range that contained x
+94                return false;
+95            } else if range.contains(&x) {
+96                return true;
+97            }
+98        }
+99        false
+100    }
+101
+102    pub(crate) fn iter_range(&self, range: Range<T>) -> impl Iterator<Item = Range<T>> + '_ {
+103        self.iter().filter_map(move |r| {
+104            if r.end > range.start && r.start < range.end {
+105                Some(r.start.max(range.start)..r.end.min(range.end))
+106            } else {
+107                None
+108            }
+109        })
+110    }
+111
+112    pub(crate) fn insert_one(&mut self, x: T) -> bool {
+113        self.insert(x..x + T::from(1u32))
+114    }
+115
+116    pub(crate) fn insert(&mut self, x: Range<T>) -> bool {
+117        let mut result = false;
+118
+119        if x.is_empty() {
+120            // Don't try to deal with ranges where x.end <= x.start
+121            return false;
+122        }
+123
+124        let mut idx = 0;
+125        while idx != self.0.len() {
+126            let range = &mut self.0[idx];
+127
+128            if range.start > x.end {
+129                // The range is fully before this range and therefore not extensible.
+130                // Add a new range to the left
+131                self.0.insert(idx, x);
+132                return true;
+133            } else if range.start > x.start {
+134                // The new range starts before this range but overlaps.
+135                // Extend the current range to the left
+136                // Note that we don't have to merge a potential left range, since
+137                // this case would have been captured by merging the right range
+138                // in the previous loop iteration
+139                result = true;
+140                range.start = x.start;
+141            }
+142
+143            // At this point we have handled all parts of the new range which
+144            // are in front of the current range. Now we handle everything from
+145            // the start of the current range
+146
+147            if x.end <= range.end {
+148                // Fully contained
+149                return result;
+150            } else if x.start <= range.end {
+151                // Extend the current range to the end of the new range.
+152                // Since it's not contained it must be bigger
+153                range.end = x.end;
+154
+155                // Merge all follow-up ranges which overlap
+156                while idx != self.0.len() - 1 {
+157                    let curr = self.0[idx].clone();
+158                    let next = self.0[idx + 1].clone();
+159                    if curr.end >= next.start {
+160                        self.0[idx].end = next.end.max(curr.end);
+161                        self.0.remove(idx + 1);
+162                    } else {
+163                        break;
+164                    }
+165                }
+166
+167                return true;
+168            }
+169
+170            idx += 1;
 171        }
 172
-173        let mut idx = 0;
-174        while idx != self.0.len() && x.start != x.end {
-175            let range = self.0[idx].clone();
-176
-177            if x.end <= range.start {
-178                // The range is fully before this range
-179                return result;
-180            } else if x.start >= range.end {
-181                // The range is fully after this range
-182                idx += 1;
-183                continue;
-184            }
+173        // Insert a range at the end
+174        self.0.push(x);
+175        true
+176    }
+177
+178    pub(crate) fn remove(&mut self, x: Range<T>) -> bool {
+179        let mut result = false;
+180
+181        if x.is_empty() {
+182            // Don't try to deal with ranges where x.end <= x.start
+183            return false;
+184        }
 185
-186            // The range overlaps with this range
-187            result = true;
-188
-189            let left = range.start..x.start;
-190            let right = x.end..range.end;
-191            if left.is_empty() && right.is_empty() {
-192                self.0.remove(idx);
-193            } else if left.is_empty() {
-194                self.0[idx] = right;
-195                idx += 1;
-196            } else if right.is_empty() {
-197                self.0[idx] = left;
-198                idx += 1;
-199            } else {
-200                self.0[idx] = right;
-201                self.0.insert(idx, left);
-202                idx += 2;
-203            }
-204        }
-205
-206        result
-207    }
-208
-209    pub(crate) fn is_empty(&self) -> bool {
-210        self.0.is_empty()
-211    }
-212
-213    pub(crate) fn pop_min(&mut self) -> Option<Range<u64>> {
-214        if !self.0.is_empty() {
-215            Some(self.0.remove(0))
-216        } else {
-217            None
-218        }
-219    }
-220
-221    pub(crate) fn min(&self) -> Option<u64> {
-222        self.iter().next().map(|x| x.start)
-223    }
-224
-225    pub(crate) fn max(&self) -> Option<u64> {
-226        self.iter().next_back().map(|x| x.end - 1)
-227    }
-228}
-229
-230#[cfg(test)]
-231impl proptest::arbitrary::Arbitrary for ArrayRangeSet {
-232    type Parameters = ();
-233    type Strategy = proptest::strategy::BoxedStrategy<Self>;
-234
-235    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
-236        use proptest::prelude::*;
-237        // Generate 1-8 ranges. Each range is defined by a gap from the previous and a size.
-238        // We use small values to keep encoding reasonable.
-239        prop::collection::vec((1u64..100, 1u64..50), 1..8)
-240            .prop_map(|gaps_and_sizes| {
-241                let mut ranges = Self::new();
-242                let mut pos = 0u64;
-243                for (gap, size) in gaps_and_sizes {
-244                    let start = pos + gap;
-245                    let end = start + size;
-246                    ranges.insert(start..end);
-247                    pos = end;
-248                }
-249                ranges
-250            })
-251            .boxed()
+186        let mut idx = 0;
+187        while idx != self.0.len() && x.start != x.end {
+188            let range = self.0[idx].clone();
+189
+190            if x.end <= range.start {
+191                // The range is fully before this range
+192                return result;
+193            } else if x.start >= range.end {
+194                // The range is fully after this range
+195                idx += 1;
+196                continue;
+197            }
+198
+199            // The range overlaps with this range
+200            result = true;
+201
+202            let left = range.start..x.start;
+203            let right = x.end..range.end;
+204            if left.is_empty() && right.is_empty() {
+205                self.0.remove(idx);
+206            } else if left.is_empty() {
+207                self.0[idx] = right;
+208                idx += 1;
+209            } else if right.is_empty() {
+210                self.0[idx] = left;
+211                idx += 1;
+212            } else {
+213                self.0[idx] = right;
+214                self.0.insert(idx, left);
+215                idx += 2;
+216            }
+217        }
+218
+219        result
+220    }
+221
+222    pub(crate) fn is_empty(&self) -> bool {
+223        self.0.is_empty()
+224    }
+225
+226    pub(crate) fn pop_min(&mut self) -> Option<Range<T>> {
+227        if !self.0.is_empty() {
+228            Some(self.0.remove(0))
+229        } else {
+230            None
+231        }
+232    }
+233
+234    pub(crate) fn min(&self) -> Option<T> {
+235        self.iter().next().map(|x| x.start)
+236    }
+237
+238    pub(crate) fn max(&self) -> Option<T> {
+239        self.iter().next_back().map(|x| x.end - T::from(1))
+240    }
+241}
+242
+243/// Functions which need `Range<T>` to impl IntoIterator for u64.
+244///
+245/// `Range<T>` only implements [`IntoIterator`] for types implementing `std::iter::Step`,
+246/// but that trait is unstable. We can work around this by duplicating these functions for
+247/// [`u32`] and [`u64`]. Only we don't currently use the u32 version so it is u64-only for
+248/// now.
+249impl<const N: usize> ArrayRangeSet<N, u64> {
+250    pub(crate) fn elts(&self) -> impl Iterator<Item = u64> + '_ {
+251        self.iter().flatten()
 252    }
-253}
\ No newline at end of file +253} +254 +255#[cfg(test)] +256impl proptest::arbitrary::Arbitrary for ArrayRangeSet { +257 type Parameters = (); +258 type Strategy = proptest::strategy::BoxedStrategy<Self>; +259 +260 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { +261 use proptest::prelude::*; +262 // Generate 1-8 ranges. Each range is defined by a gap from the previous and a size. +263 // We use small values to keep encoding reasonable. +264 prop::collection::vec((1u64..100, 1u64..50), 1..8) +265 .prop_map(|gaps_and_sizes| { +266 let mut ranges = Self::new(); +267 let mut pos = 0u64; +268 for (gap, size) in gaps_and_sizes { +269 let start = pos + gap; +270 let end = start + size; +271 ranges.insert(start..end); +272 pos = end; +273 } +274 ranges +275 }) +276 .boxed() +277 } +278}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/shared.rs.html b/pr/451/docs/src/noq_proto/shared.rs.html index b874ff857..a1d42f5c4 100644 --- a/pr/451/docs/src/noq_proto/shared.rs.html +++ b/pr/451/docs/src/noq_proto/shared.rs.html @@ -48,161 +48,168 @@ 48 pub fn is_drained(&self) -> bool { 49 self.0 == EndpointEventInner::Drained 50 } -51} -52 -53#[derive(Clone, Debug, Eq, PartialEq)] -54pub(crate) enum EndpointEventInner { -55 /// The connection has been drained -56 Drained, -57 /// The connection has a new active reset token -58 /// -59 /// Whenever the connection switches to a new remote CID issued by the peer, it also -60 /// switches the matching reset token that can be used to abort this connection. This -61 /// event provides a new reset token for the active remote CID. -62 ResetToken(PathId, SocketAddr, ResetToken), -63 /// Retire the remotely issued reset token for a path, without replacing it with a new one -64 /// -65 /// This is like `ResetToken` above, but without replacing the `ResetToken` with a new -66 /// one. See `ConnectionIndex::connection_reset_tokens`. -67 RetireResetToken(PathId), -68 /// The connection needs connection identifiers. -69 /// -70 /// The fields are: -71 /// - The path ID for which the identifiers are needed. -72 /// - The time when the identifiers were needed, not used to generate the CIDs but sent -73 /// back via the [`ConnectionEventInner::NewIdentifiers`] so the connection can track -74 /// the lifetime of when it needs to be rotated. should be rotated. -75 /// - The number of CIDs needed. -76 NeedIdentifiers(PathId, Instant, u64), -77 /// Retire a locally issued CID -78 /// -79 /// Stop routing connection ID for this sequence number to the connection -80 /// When `bool == true`, a new connection ID will be issued to peer -81 RetireConnectionId(Instant, PathId, u64, bool), -82} -83 -84/// Protocol-level identifier for a connection. -85/// -86/// Mainly useful for identifying this connection's packets on the wire with tools like Wireshark. -87#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -88#[cfg_attr(test, derive(test_strategy::Arbitrary))] -89pub struct ConnectionId { -90 /// length of CID -91 #[cfg_attr(test, strategy(1u8..=MAX_CID_SIZE as u8))] -92 len: u8, -93 /// CID in byte array -94 bytes: [u8; MAX_CID_SIZE], -95} -96 -97impl ConnectionId { -98 /// Construct cid from byte array -99 pub fn new(bytes: &[u8]) -> Self { -100 debug_assert!(bytes.len() <= MAX_CID_SIZE); -101 let mut res = Self { -102 len: bytes.len() as u8, -103 bytes: [0; MAX_CID_SIZE], -104 }; -105 res.bytes[..bytes.len()].copy_from_slice(bytes); -106 res -107 } -108 -109 /// Constructs cid by reading `len` bytes from a `Buf` -110 /// -111 /// Callers need to assure that `buf.remaining() >= len` -112 pub fn from_buf(buf: &mut (impl Buf + ?Sized), len: usize) -> Self { -113 debug_assert!(len <= MAX_CID_SIZE); -114 let mut res = Self { -115 len: len as u8, -116 bytes: [0; MAX_CID_SIZE], -117 }; -118 buf.copy_to_slice(&mut res[..len]); -119 res -120 } -121 -122 pub(crate) fn len(&self) -> usize { -123 self.len as usize -124 } -125 -126 /// Decode from long header format -127 pub(crate) fn decode_long(buf: &mut impl Buf) -> Option<Self> { -128 let len = buf.get::<u8>().ok()? as usize; -129 match len > MAX_CID_SIZE || buf.remaining() < len { -130 false => Some(Self::from_buf(buf, len)), -131 true => None, -132 } -133 } -134 -135 /// Encode in long header format -136 pub(crate) fn encode_long(&self, buf: &mut impl BufMut) { -137 buf.put_u8(self.len() as u8); -138 buf.put_slice(self); -139 } -140} +51 +52 /// Whether this is the event is the event indicating the start of the draining period. +53 pub fn is_draining(&self) -> bool { +54 self.0 == EndpointEventInner::Draining +55 } +56} +57 +58#[derive(Clone, Debug, Eq, PartialEq)] +59pub(crate) enum EndpointEventInner { +60 /// The connection started draining +61 Draining, +62 /// The connection has been drained +63 Drained, +64 /// The connection has a new active reset token +65 /// +66 /// Whenever the connection switches to a new remote CID issued by the peer, it also +67 /// switches the matching reset token that can be used to abort this connection. This +68 /// event provides a new reset token for the active remote CID. +69 ResetToken(PathId, SocketAddr, ResetToken), +70 /// Retire the remotely issued reset token for a path, without replacing it with a new one +71 /// +72 /// This is like `ResetToken` above, but without replacing the `ResetToken` with a new +73 /// one. See `ConnectionIndex::connection_reset_tokens`. +74 RetireResetToken(PathId), +75 /// The connection needs connection identifiers. +76 /// +77 /// The fields are: +78 /// - The path ID for which the identifiers are needed. +79 /// - The time when the identifiers were needed, not used to generate the CIDs but sent +80 /// back via the [`ConnectionEventInner::NewIdentifiers`] so the connection can track +81 /// the lifetime of when it needs to be rotated. should be rotated. +82 /// - The number of CIDs needed. +83 NeedIdentifiers(PathId, Instant, u64), +84 /// Retire a locally issued CID +85 /// +86 /// Stop routing connection ID for this sequence number to the connection +87 /// When `bool == true`, a new connection ID will be issued to peer +88 RetireConnectionId(Instant, PathId, u64, bool), +89} +90 +91/// Protocol-level identifier for a connection. +92/// +93/// Mainly useful for identifying this connection's packets on the wire with tools like Wireshark. +94#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +95#[cfg_attr(test, derive(test_strategy::Arbitrary))] +96pub struct ConnectionId { +97 /// length of CID +98 #[cfg_attr(test, strategy(1u8..=MAX_CID_SIZE as u8))] +99 len: u8, +100 /// CID in byte array +101 bytes: [u8; MAX_CID_SIZE], +102} +103 +104impl ConnectionId { +105 /// Construct cid from byte array +106 pub fn new(bytes: &[u8]) -> Self { +107 debug_assert!(bytes.len() <= MAX_CID_SIZE); +108 let mut res = Self { +109 len: bytes.len() as u8, +110 bytes: [0; MAX_CID_SIZE], +111 }; +112 res.bytes[..bytes.len()].copy_from_slice(bytes); +113 res +114 } +115 +116 /// Constructs cid by reading `len` bytes from a `Buf` +117 /// +118 /// Callers need to assure that `buf.remaining() >= len` +119 pub fn from_buf(buf: &mut (impl Buf + ?Sized), len: usize) -> Self { +120 debug_assert!(len <= MAX_CID_SIZE); +121 let mut res = Self { +122 len: len as u8, +123 bytes: [0; MAX_CID_SIZE], +124 }; +125 buf.copy_to_slice(&mut res[..len]); +126 res +127 } +128 +129 pub(crate) fn len(&self) -> usize { +130 self.len as usize +131 } +132 +133 /// Decode from long header format +134 pub(crate) fn decode_long(buf: &mut impl Buf) -> Option<Self> { +135 let len = buf.get::<u8>().ok()? as usize; +136 match len > MAX_CID_SIZE || buf.remaining() < len { +137 false => Some(Self::from_buf(buf, len)), +138 true => None, +139 } +140 } 141 -142impl ::std::ops::Deref for ConnectionId { -143 type Target = [u8]; -144 fn deref(&self) -> &[u8] { -145 &self.bytes[0..self.len as usize] +142 /// Encode in long header format +143 pub(crate) fn encode_long(&self, buf: &mut impl BufMut) { +144 buf.put_u8(self.len() as u8); +145 buf.put_slice(self); 146 } 147} 148 -149impl ::std::ops::DerefMut for ConnectionId { -150 fn deref_mut(&mut self) -> &mut [u8] { -151 &mut self.bytes[0..self.len as usize] -152 } -153} -154 -155impl fmt::Debug for ConnectionId { -156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -157 self.bytes[0..self.len as usize].fmt(f) -158 } -159} -160 -161impl fmt::Display for ConnectionId { -162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -163 for byte in self.iter() { -164 write!(f, "{byte:02x}")?; -165 } -166 Ok(()) -167 } -168} -169 -170/// Explicit congestion notification codepoint -171#[repr(u8)] -172#[derive(Debug, Copy, Clone, Eq, PartialEq)] -173pub enum EcnCodepoint { -174 /// The ECT(0) codepoint, indicating that an endpoint is ECN-capable -175 Ect0 = 0b10, -176 /// The ECT(1) codepoint, indicating that an endpoint is ECN-capable -177 Ect1 = 0b01, -178 /// The CE codepoint, signalling that congestion was experienced -179 Ce = 0b11, -180} -181 -182impl EcnCodepoint { -183 /// Create new object from the given bits -184 pub fn from_bits(x: u8) -> Option<Self> { -185 use EcnCodepoint::*; -186 Some(match x & 0b11 { -187 0b10 => Ect0, -188 0b01 => Ect1, -189 0b11 => Ce, -190 _ => { -191 return None; -192 } -193 }) -194 } -195 -196 /// Returns whether the codepoint is a CE, signalling that congestion was experienced -197 pub fn is_ce(self) -> bool { -198 matches!(self, Self::Ce) -199 } -200} -201 -202#[derive(Debug, Copy, Clone)] -203pub(crate) struct IssuedCid { -204 pub(crate) path_id: PathId, -205 pub(crate) sequence: u64, -206 pub(crate) id: ConnectionId, -207 pub(crate) reset_token: ResetToken, -208}
\ No newline at end of file +149impl ::std::ops::Deref for ConnectionId { +150 type Target = [u8]; +151 fn deref(&self) -> &[u8] { +152 &self.bytes[0..self.len as usize] +153 } +154} +155 +156impl ::std::ops::DerefMut for ConnectionId { +157 fn deref_mut(&mut self) -> &mut [u8] { +158 &mut self.bytes[0..self.len as usize] +159 } +160} +161 +162impl fmt::Debug for ConnectionId { +163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +164 self.bytes[0..self.len as usize].fmt(f) +165 } +166} +167 +168impl fmt::Display for ConnectionId { +169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +170 for byte in self.iter() { +171 write!(f, "{byte:02x}")?; +172 } +173 Ok(()) +174 } +175} +176 +177/// Explicit congestion notification codepoint +178#[repr(u8)] +179#[derive(Debug, Copy, Clone, Eq, PartialEq)] +180pub enum EcnCodepoint { +181 /// The ECT(0) codepoint, indicating that an endpoint is ECN-capable +182 Ect0 = 0b10, +183 /// The ECT(1) codepoint, indicating that an endpoint is ECN-capable +184 Ect1 = 0b01, +185 /// The CE codepoint, signalling that congestion was experienced +186 Ce = 0b11, +187} +188 +189impl EcnCodepoint { +190 /// Create new object from the given bits +191 pub fn from_bits(x: u8) -> Option<Self> { +192 use EcnCodepoint::*; +193 Some(match x & 0b11 { +194 0b10 => Ect0, +195 0b01 => Ect1, +196 0b11 => Ce, +197 _ => { +198 return None; +199 } +200 }) +201 } +202 +203 /// Returns whether the codepoint is a CE, signalling that congestion was experienced +204 pub fn is_ce(self) -> bool { +205 matches!(self, Self::Ce) +206 } +207} +208 +209#[derive(Debug, Copy, Clone)] +210pub(crate) struct IssuedCid { +211 pub(crate) path_id: PathId, +212 pub(crate) sequence: u64, +213 pub(crate) id: ConnectionId, +214 pub(crate) reset_token: ResetToken, +215}
\ No newline at end of file diff --git a/pr/451/docs/src/noq_proto/transport_parameters.rs.html b/pr/451/docs/src/noq_proto/transport_parameters.rs.html index c2a6ff437..3020b77a6 100644 --- a/pr/451/docs/src/noq_proto/transport_parameters.rs.html +++ b/pr/451/docs/src/noq_proto/transport_parameters.rs.html @@ -145,7 +145,7 @@ 145 preferred_address: None, 146 grease_transport_parameter: None, 147 write_order: None, -148 address_discovery_role: address_discovery::Role::Disabled, +148 address_discovery_role: address_discovery::Role::default(), 149 initial_max_path_id: None, 150 max_remote_nat_traversal_addresses: None, 151 } @@ -840,7 +840,7 @@ 840 }), 841 grease_quic_bit: true, 842 min_ack_delay: Some(2_000u32.into()), -843 address_discovery_role: address_discovery::Role::SendOnly, +843 address_discovery_role: address_discovery::Role::send_only(), 844 initial_max_path_id: Some(PathId::MAX), 845 max_remote_nat_traversal_addresses: Some(5u8.try_into().unwrap()), 846 ..TransportParameters::default() diff --git a/pr/451/docs/src/perf/client.rs.html b/pr/451/docs/src/perf/client.rs.html index 9037e80a5..1ce45266e 100644 --- a/pr/451/docs/src/perf/client.rs.html +++ b/pr/451/docs/src/perf/client.rs.html @@ -201,7 +201,7 @@ 201 } 202 } 203 -204 endpoint.wait_idle().await; +204 endpoint.wait_all_draining().await; 205 206 #[cfg(feature = "json-output")] 207 if let Some(path) = opt.json { diff --git a/pr/451/docs/trait.impl/core/convert/trait.From.js b/pr/451/docs/trait.impl/core/convert/trait.From.js index 7ce0db891..45480683e 100644 --- a/pr/451/docs/trait.impl/core/convert/trait.From.js +++ b/pr/451/docs/trait.impl/core/convert/trait.From.js @@ -1,9 +1,9 @@ (function() { - var implementors = Object.fromEntries([["noq",[["impl From<ConnectionError> for ReadError"],["impl From<ConnectionError> for ResetError"],["impl From<ConnectionError> for SendDatagramError"],["impl From<ConnectionError> for StoppedError"],["impl From<ConnectionError> for WriteError"],["impl From<ReadError> for ReadExactError"],["impl From<ReadError> for ReadToEndError"],["impl From<ReadError> for Error"],["impl From<ResetError> for ReadError"],["impl From<ResetError> for Error"],["impl From<StoppedError> for WriteError"],["impl From<StoppedError> for Error"],["impl From<WriteError> for Error"],["impl From<ClosedStream> for WriteError"]]],["noq_proto",[["impl From<ConnectionError> for Error"],["impl From<Dir> for StreamType"],["impl From<PathEvent> for Event"],["impl From<Side> for Side"],["impl From<Error> for TransportError"],["impl From<u16> for VarInt"],["impl From<u32> for VarInt"],["impl From<u8> for VarInt"],["impl From<TryFromIntError> for ConfigError"],["impl From<UnexpectedEnd> for PacketDecodeError"],["impl From<UnexpectedEnd> for Error"],["impl From<UnsupportedVersion> for ConnectError"],["impl From<ClosedStream> for Error"],["impl From<ConnectionHandle> for usize"],["impl From<StreamId> for u64"],["impl From<StreamId> for VarInt"],["impl From<Error> for ConnectionError"],["impl From<Error> for ConnectionClose"],["impl From<Code> for u64"],["impl From<Code> for VarInt"],["impl From<VarInt> for u64"],["impl From<VarInt> for IdleTimeout"],["impl From<VarInt> for StreamId"],["impl From<VarInt> for Code"],["impl From<VarIntBoundsExceeded> for ConfigError"],["impl<T: Into<u32>> From<T> for PathId"]]],["noq_udp",[["impl<'s, S> From<&'s S> for UdpSockRef<'s>
where\n S: AsFd,
"]]]]); + var implementors = Object.fromEntries([["noq",[["impl From<ConnectionError> for ReadError"],["impl From<ConnectionError> for ResetError"],["impl From<ConnectionError> for SendDatagramError"],["impl From<ConnectionError> for StoppedError"],["impl From<ConnectionError> for WriteError"],["impl From<ReadError> for ReadExactError"],["impl From<ReadError> for ReadToEndError"],["impl From<ReadError> for Error"],["impl From<ResetError> for ReadError"],["impl From<ResetError> for Error"],["impl From<StoppedError> for WriteError"],["impl From<StoppedError> for Error"],["impl From<WriteError> for Error"],["impl From<ClosedStream> for WriteError"]]],["noq_proto",[["impl From<SocketAddr> for FourTuple"],["impl From<ConnectionError> for Error"],["impl From<Dir> for StreamType"],["impl From<PathEvent> for Event"],["impl From<Side> for Side"],["impl From<Error> for TransportError"],["impl From<u16> for VarInt"],["impl From<u32> for VarInt"],["impl From<u8> for VarInt"],["impl From<TryFromIntError> for ConfigError"],["impl From<UnexpectedEnd> for PacketDecodeError"],["impl From<UnexpectedEnd> for Error"],["impl From<UnsupportedVersion> for ConnectError"],["impl From<ClosedStream> for Error"],["impl From<ConnectionHandle> for usize"],["impl From<StreamId> for u64"],["impl From<StreamId> for VarInt"],["impl From<Error> for ConnectionError"],["impl From<Error> for ConnectionClose"],["impl From<Code> for u64"],["impl From<Code> for VarInt"],["impl From<VarInt> for u64"],["impl From<VarInt> for IdleTimeout"],["impl From<VarInt> for StreamId"],["impl From<VarInt> for Code"],["impl From<VarIntBoundsExceeded> for ConfigError"],["impl<T: Into<u32>> From<T> for PathId"]]],["noq_udp",[["impl<'s, S> From<&'s S> for UdpSockRef<'s>
where\n S: AsFd,
"]]]]); if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } })() -//{"start":57,"fragment_lengths":[5252,10067,599]} \ No newline at end of file +//{"start":57,"fragment_lengths":[5252,10504,599]} \ No newline at end of file diff --git a/pr/451/docs/trait.impl/identity_hash/trait.IdentityHashable.js b/pr/451/docs/trait.impl/identity_hash/trait.IdentityHashable.js deleted file mode 100644 index 25f0ad1b6..000000000 --- a/pr/451/docs/trait.impl/identity_hash/trait.IdentityHashable.js +++ /dev/null @@ -1,9 +0,0 @@ -(function() { - var implementors = Object.fromEntries([["noq_proto",[["impl IdentityHashable for PathId"]]]]); - if (window.register_implementors) { - window.register_implementors(implementors); - } else { - window.pending_implementors = implementors; - } -})() -//{"start":57,"fragment_lengths":[149]} \ No newline at end of file